Skip to content

Commit 76bb54e

Browse files
committed
DeepValidate returns slice of errors, added tests
1 parent 4aeb6cc commit 76bb54e

File tree

2 files changed

+529
-17
lines changed

2 files changed

+529
-17
lines changed

validator.go

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -167,46 +167,61 @@ func TryValidate(v any) (err error, isValidatable bool) {
167167
}
168168
}
169169

170-
// DeepValidate validates all fields of a struct, all elements of a slice or array,
170+
// DeepValidate recursively validates all fields of a struct, all elements of a slice or array,
171171
// and all values of a map by recursively calling Validate or Valid methods.
172-
// It provides detailed error information including the path to invalid values.
173-
func DeepValidate(v any) error {
174-
return deepValidate(reflect.ValueOf(v))
172+
// It returns all validation errors as a slice.
173+
// Use errors.Join(DeepValidate(v)...) to join the errors into a single error.
174+
func DeepValidate(v any) []error {
175+
var errs []error
176+
deepValidate(reflect.ValueOf(v), func(err error) {
177+
errs = append(errs, err)
178+
})
179+
return errs
175180
}
176181

177182
// deepValidate is the internal implementation of DeepValidate.
178183
// It recursively validates nested structures and provides path information for errors.
179-
func deepValidate(v reflect.Value, path ...string) error {
180-
err := Validate(v.Interface())
181-
if err != nil && len(path) > 0 {
182-
err = fmt.Errorf("%s: %w", strings.Join(path, " -> "), err)
184+
func deepValidate(v reflect.Value, onError func(error), path ...string) {
185+
// Handle invalid/zero reflect.Values (e.g., from nil interface{})
186+
if !v.IsValid() {
187+
return
183188
}
184-
if v.Kind() == reflect.Ptr {
185-
if v.IsNil() {
186-
return err
189+
190+
// Handle nil pointers before calling v.Interface()
191+
if v.Kind() == reflect.Pointer && v.IsNil() {
192+
return
193+
}
194+
195+
err := Validate(v.Interface())
196+
if err != nil {
197+
if len(path) > 0 {
198+
err = fmt.Errorf("%s: %w", strings.Join(path, " -> "), err)
187199
}
200+
onError(err)
201+
}
202+
if v.Kind() == reflect.Pointer {
188203
v = v.Elem()
189204
}
190205
switch v.Kind() {
191206
case reflect.Struct:
192-
for i := 0; i < v.NumField(); i++ {
193-
name := fmt.Sprintf("struct field %s", v.Type().Field(i).Name)
194-
err = errors.Join(err, deepValidate(v.Field(i), append(path, name)...))
207+
t := v.Type()
208+
for i := range v.NumField() {
209+
name := fmt.Sprintf("struct field %s", t.Field(i).Name)
210+
deepValidate(v.Field(i), onError, append(path, name)...)
195211
}
196212
case reflect.Map:
197213
keys := v.MapKeys()
198214
slices.SortFunc(keys, ReflectCompare)
199215
for _, key := range keys {
200216
name := fmt.Sprintf("map value [%#v]", key.Interface())
201-
err = errors.Join(err, deepValidate(v.MapIndex(key), append(path, name)...))
217+
deepValidate(v.MapIndex(key), onError, append(path, name)...)
202218
}
203219
case reflect.Slice, reflect.Array:
204220
for i := 0; i < v.Len(); i++ {
205221
name := fmt.Sprintf("elememt [%d]", i)
206-
err = errors.Join(err, deepValidate(v.Index(i), append(path, name)...))
222+
deepValidate(v.Index(i), onError, append(path, name)...)
207223
}
208224
}
209-
return err
210225
}
211226

212227
// ReflectCompare compares two reflect.Values of the same type.

0 commit comments

Comments
 (0)