Skip to content

Commit d8e0c4d

Browse files
Dean KarnDean Karn
authored andcommitted
Merge pull request #90 from bluesuncorp/v5-development
Update Flatten logic to handle new dive logic
2 parents d627af8 + 4f5ff0f commit d8e0c4d

File tree

3 files changed

+284
-16
lines changed

3 files changed

+284
-16
lines changed

doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Here is a list of the current built in validators:
176176
dive
177177
This tells the validator to dive into a slice, array or map and validate that
178178
level of the slice, array or map with the validation tags that follow.
179-
Multidimensional nesting is also supported, each level you with to dive will
179+
Multidimensional nesting is also supported, each level you wish to dive will
180180
require another dive tag. (Usage: dive)
181181
Example: [][]string with validation tag "gt=0,dive,len=1,dive,required"
182182
gt=0 will be applied to []

validator.go

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,19 @@ import (
2020
)
2121

2222
const (
23-
utf8HexComma = "0x2C"
24-
tagSeparator = ","
25-
orSeparator = "|"
26-
noValidationTag = "-"
27-
tagKeySeparator = "="
28-
structOnlyTag = "structonly"
29-
omitempty = "omitempty"
30-
required = "required"
31-
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag"
32-
sliceErrMsg = "Field validation for \"%s\" failed at index \"%d\" with error(s): %s"
33-
mapErrMsg = "Field validation for \"%s\" failed on key \"%v\" with error(s): %s"
34-
structErrMsg = "Struct:%s\n"
35-
diveTag = "dive"
36-
// diveSplit = "," + diveTag
23+
utf8HexComma = "0x2C"
24+
tagSeparator = ","
25+
orSeparator = "|"
26+
noValidationTag = "-"
27+
tagKeySeparator = "="
28+
structOnlyTag = "structonly"
29+
omitempty = "omitempty"
30+
required = "required"
31+
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag"
32+
sliceErrMsg = "Field validation for \"%s\" failed at index \"%d\" with error(s): %s"
33+
mapErrMsg = "Field validation for \"%s\" failed on key \"%v\" with error(s): %s"
34+
structErrMsg = "Struct:%s\n"
35+
diveTag = "dive"
3736
arrayIndexFieldName = "%s[%d]"
3837
mapIndexFieldName = "%s[%v]"
3938
)
@@ -193,6 +192,82 @@ func (e *FieldError) Error() string {
193192
return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag)
194193
}
195194

195+
// Flatten flattens the FieldError hierarchical structure into a flat namespace style field name
196+
// for those that want/need it.
197+
// This is now needed because of the new dive functionality
198+
func (e *FieldError) Flatten() map[string]*FieldError {
199+
200+
errs := map[string]*FieldError{}
201+
202+
if e.IsPlaceholderErr {
203+
204+
if e.IsSliceOrArray {
205+
for key, err := range e.SliceOrArrayErrs {
206+
207+
fe, ok := err.(*FieldError)
208+
209+
if ok {
210+
211+
if flat := fe.Flatten(); flat != nil && len(flat) > 0 {
212+
for k, v := range flat {
213+
if fe.IsPlaceholderErr {
214+
errs[fmt.Sprintf("[%#v]%s", key, k)] = v
215+
} else {
216+
errs[fmt.Sprintf("[%#v]", key)] = v
217+
}
218+
219+
}
220+
}
221+
} else {
222+
223+
se := err.(*StructErrors)
224+
225+
if flat := se.Flatten(); flat != nil && len(flat) > 0 {
226+
for k, v := range flat {
227+
errs[fmt.Sprintf("[%#v].%s.%s", key, se.Struct, k)] = v
228+
}
229+
}
230+
}
231+
}
232+
}
233+
234+
if e.IsMap {
235+
for key, err := range e.MapErrs {
236+
237+
fe, ok := err.(*FieldError)
238+
239+
if ok {
240+
241+
if flat := fe.Flatten(); flat != nil && len(flat) > 0 {
242+
for k, v := range flat {
243+
if fe.IsPlaceholderErr {
244+
errs[fmt.Sprintf("[%#v]%s", key, k)] = v
245+
} else {
246+
errs[fmt.Sprintf("[%#v]", key)] = v
247+
}
248+
}
249+
}
250+
} else {
251+
252+
se := err.(*StructErrors)
253+
254+
if flat := se.Flatten(); flat != nil && len(flat) > 0 {
255+
for k, v := range flat {
256+
errs[fmt.Sprintf("[%#v].%s.%s", key, se.Struct, k)] = v
257+
}
258+
}
259+
}
260+
}
261+
}
262+
263+
return errs
264+
}
265+
266+
errs[e.Field] = e
267+
268+
return errs
269+
}
270+
196271
// StructErrors is hierarchical list of field and struct validation errors
197272
// for a non hierarchical representation please see the Flatten method for StructErrors
198273
type StructErrors struct {
@@ -234,7 +309,17 @@ func (e *StructErrors) Flatten() map[string]*FieldError {
234309

235310
for _, f := range e.Errors {
236311

237-
errs[f.Field] = f
312+
if flat := f.Flatten(); flat != nil && len(flat) > 0 {
313+
314+
for k, fe := range flat {
315+
316+
if f.IsPlaceholderErr {
317+
errs[f.Field+k] = fe
318+
} else {
319+
errs[k] = fe
320+
}
321+
}
322+
}
238323
}
239324

240325
for key, val := range e.StructErrors {

validator_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,163 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e
226226
EqualSkip(t, 2, val.Tag, expectedTag)
227227
}
228228

229+
func TestFlattenValidation(t *testing.T) {
230+
231+
type Inner struct {
232+
Name string `validate:"required"`
233+
}
234+
235+
type TestMultiDimensionalStructsPtr struct {
236+
Errs [][]*Inner `validate:"gt=0,dive,dive,required"`
237+
}
238+
239+
var errStructPtrArray [][]*Inner
240+
241+
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{"ok"}})
242+
243+
tmsp := &TestMultiDimensionalStructsPtr{
244+
Errs: errStructPtrArray,
245+
}
246+
247+
errs := validate.Struct(tmsp)
248+
NotEqual(t, errs, nil)
249+
Equal(t, len(errs.Errors), 1)
250+
// for full test coverage
251+
fmt.Sprint(errs.Error())
252+
253+
fieldErr := errs.Errors["Errs"]
254+
Equal(t, fieldErr.IsPlaceholderErr, true)
255+
Equal(t, fieldErr.IsSliceOrArray, true)
256+
Equal(t, fieldErr.Field, "Errs")
257+
Equal(t, len(fieldErr.SliceOrArrayErrs), 1)
258+
259+
innerSlice1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError)
260+
Equal(t, ok, true)
261+
Equal(t, innerSlice1.IsPlaceholderErr, true)
262+
Equal(t, innerSlice1.Field, "Errs[0]")
263+
264+
flatFieldErr, ok := fieldErr.Flatten()["[0][1].Inner.Name"]
265+
Equal(t, ok, true)
266+
Equal(t, flatFieldErr.Field, "Name")
267+
Equal(t, flatFieldErr.Tag, "required")
268+
269+
structErrFlatten, ok := errs.Flatten()["Errs[0][1].Inner.Name"]
270+
Equal(t, ok, true)
271+
Equal(t, structErrFlatten.Field, "Name")
272+
Equal(t, structErrFlatten.Tag, "required")
273+
274+
errStructPtrArray = [][]*Inner{}
275+
errStructPtrArray = append(errStructPtrArray, []*Inner{&Inner{"ok"}, nil, &Inner{"ok"}})
276+
277+
tmsp = &TestMultiDimensionalStructsPtr{
278+
Errs: errStructPtrArray,
279+
}
280+
281+
errs = validate.Struct(tmsp)
282+
NotEqual(t, errs, nil)
283+
Equal(t, len(errs.Errors), 1)
284+
// for full test coverage
285+
fmt.Sprint(errs.Error())
286+
287+
fieldErr = errs.Errors["Errs"]
288+
Equal(t, fieldErr.IsPlaceholderErr, true)
289+
Equal(t, fieldErr.IsSliceOrArray, true)
290+
Equal(t, fieldErr.Field, "Errs")
291+
Equal(t, len(fieldErr.SliceOrArrayErrs), 1)
292+
293+
innerSlice1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
294+
Equal(t, ok, true)
295+
Equal(t, innerSlice1.IsPlaceholderErr, true)
296+
Equal(t, innerSlice1.Field, "Errs[0]")
297+
298+
flatFieldErr, ok = fieldErr.Flatten()["[0][1]"]
299+
Equal(t, ok, true)
300+
Equal(t, flatFieldErr.Field, "Errs[0][1]")
301+
Equal(t, flatFieldErr.Tag, "required")
302+
303+
type TestMapStructPtr struct {
304+
Errs map[int]*Inner `validate:"gt=0,dive,required"`
305+
}
306+
307+
mip := map[int]*Inner{0: &Inner{"ok"}, 3: &Inner{""}, 4: &Inner{"ok"}}
308+
309+
msp := &TestMapStructPtr{
310+
Errs: mip,
311+
}
312+
313+
errs = validate.Struct(msp)
314+
NotEqual(t, errs, nil)
315+
Equal(t, len(errs.Errors), 1)
316+
317+
fieldError := errs.Errors["Errs"]
318+
Equal(t, fieldError.IsPlaceholderErr, true)
319+
Equal(t, fieldError.IsMap, true)
320+
Equal(t, len(fieldError.MapErrs), 1)
321+
322+
innerStructError, ok := fieldError.MapErrs[3].(*StructErrors)
323+
Equal(t, ok, true)
324+
Equal(t, innerStructError.Struct, "Inner")
325+
Equal(t, len(innerStructError.Errors), 1)
326+
327+
innerInnerFieldError, ok := innerStructError.Errors["Name"]
328+
Equal(t, ok, true)
329+
Equal(t, innerInnerFieldError.IsPlaceholderErr, false)
330+
Equal(t, innerInnerFieldError.IsSliceOrArray, false)
331+
Equal(t, innerInnerFieldError.Field, "Name")
332+
Equal(t, innerInnerFieldError.Tag, "required")
333+
334+
flatErrs, ok := errs.Flatten()["Errs[3].Inner.Name"]
335+
Equal(t, ok, true)
336+
Equal(t, flatErrs.Field, "Name")
337+
Equal(t, flatErrs.Tag, "required")
338+
339+
mip2 := map[int]*Inner{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}}
340+
341+
msp2 := &TestMapStructPtr{
342+
Errs: mip2,
343+
}
344+
345+
errs = validate.Struct(msp2)
346+
NotEqual(t, errs, nil)
347+
Equal(t, len(errs.Errors), 1)
348+
349+
fieldError = errs.Errors["Errs"]
350+
Equal(t, fieldError.IsPlaceholderErr, true)
351+
Equal(t, fieldError.IsMap, true)
352+
Equal(t, len(fieldError.MapErrs), 1)
353+
354+
innerFieldError, ok := fieldError.MapErrs[3].(*FieldError)
355+
Equal(t, ok, true)
356+
Equal(t, innerFieldError.IsPlaceholderErr, false)
357+
Equal(t, innerFieldError.IsSliceOrArray, false)
358+
Equal(t, innerFieldError.Field, "Errs[3]")
359+
Equal(t, innerFieldError.Tag, "required")
360+
361+
flatErrs, ok = errs.Flatten()["Errs[3]"]
362+
Equal(t, ok, true)
363+
Equal(t, flatErrs.Field, "Errs[3]")
364+
Equal(t, flatErrs.Tag, "required")
365+
366+
type TestMapInnerArrayStruct struct {
367+
Errs map[int][]string `validate:"gt=0,dive,dive,required"`
368+
}
369+
370+
mias := map[int][]string{0: []string{"ok"}, 3: []string{"ok", ""}, 4: []string{"ok"}}
371+
372+
mia := &TestMapInnerArrayStruct{
373+
Errs: mias,
374+
}
375+
376+
errs = validate.Struct(mia)
377+
NotEqual(t, errs, nil)
378+
Equal(t, len(errs.Errors), 1)
379+
380+
flatErrs, ok = errs.Flatten()["Errs[3][1]"]
381+
Equal(t, ok, true)
382+
Equal(t, flatErrs.Field, "Errs[3][1]")
383+
Equal(t, flatErrs.Tag, "required")
384+
}
385+
229386
func TestInterfaceErrValidation(t *testing.T) {
230387

231388
var v1 interface{}
@@ -578,6 +735,11 @@ func TestArrayDiveValidation(t *testing.T) {
578735
Equal(t, err.IsSliceOrArray, true)
579736
Equal(t, len(err.SliceOrArrayErrs), 1)
580737

738+
// flat := err.Flatten()
739+
// fe, ok := flat["[1]"]
740+
// Equal(t, ok, true)
741+
// Equal(t, fe.Tag, "required")
742+
581743
err = validate.Field(arr, "len=2,dive,required")
582744
NotEqual(t, err, nil)
583745
Equal(t, err.IsPlaceholderErr, false)
@@ -606,6 +768,12 @@ func TestArrayDiveValidation(t *testing.T) {
606768
NotEqual(t, errs, nil)
607769
Equal(t, len(errs.Errors), 1)
608770

771+
// flat = errs.Flatten()
772+
// me, ok := flat["Errs[1]"]
773+
// Equal(t, ok, true)
774+
// Equal(t, me.Field, "Errs[1]")
775+
// Equal(t, me.Tag, "required")
776+
609777
fieldErr, ok := errs.Errors["Errs"]
610778
Equal(t, ok, true)
611779
Equal(t, fieldErr.IsPlaceholderErr, true)
@@ -666,13 +834,15 @@ func TestArrayDiveValidation(t *testing.T) {
666834
Equal(t, sliceError1.IsPlaceholderErr, true)
667835
Equal(t, sliceError1.IsSliceOrArray, true)
668836
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
837+
Equal(t, sliceError1.Field, "Errs[0]")
669838

670839
innerSliceError1, ok := sliceError1.SliceOrArrayErrs[1].(*FieldError)
671840
Equal(t, ok, true)
672841
Equal(t, innerSliceError1.IsPlaceholderErr, false)
673842
Equal(t, innerSliceError1.Tag, required)
674843
Equal(t, innerSliceError1.IsSliceOrArray, false)
675844
Equal(t, len(innerSliceError1.SliceOrArrayErrs), 0)
845+
Equal(t, innerSliceError1.Field, "Errs[0][1]")
676846

677847
type Inner struct {
678848
Name string `validate:"required"`
@@ -736,12 +906,25 @@ func TestArrayDiveValidation(t *testing.T) {
736906
// for full test coverage
737907
fmt.Sprint(errs.Error())
738908

909+
// flat := errs.Flatten()
910+
// // fmt.Println(errs)
911+
// fmt.Println(flat)
912+
// expect Errs[0][1].Inner.Name
913+
// me, ok := flat["Errs[1]"]
914+
// Equal(t, ok, true)
915+
// Equal(t, me.Field, "Errs[1]")
916+
// Equal(t, me.Tag, "required")
917+
739918
fieldErr, ok = errs.Errors["Errs"]
740919
Equal(t, ok, true)
741920
Equal(t, fieldErr.IsPlaceholderErr, true)
742921
Equal(t, fieldErr.IsSliceOrArray, true)
743922
Equal(t, len(fieldErr.SliceOrArrayErrs), 3)
744923

924+
// flat := fieldErr.Flatten()
925+
// fmt.Println(errs)
926+
// fmt.Println(flat)
927+
745928
sliceError1, ok = fieldErr.SliceOrArrayErrs[0].(*FieldError)
746929
Equal(t, ok, true)
747930
Equal(t, sliceError1.IsPlaceholderErr, true)

0 commit comments

Comments
 (0)