From 17130c67bcb4709bb425e34daf8417d6fe57da65 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Mon, 21 Aug 2023 20:47:48 -0700 Subject: [PATCH] refactor struct level validations to support or's etc --- validator.go | 95 ++++++++++++++++------------------------------- validator_test.go | 4 +- 2 files changed, 35 insertions(+), 64 deletions(-) diff --git a/validator.go b/validator.go index 6f6d53ada..149491a0e 100644 --- a/validator.go +++ b/validator.go @@ -99,6 +99,8 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr current, kind, v.fldIsPointer = v.extractTypeInternal(current, false) + var isNestedStruct bool + switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: @@ -161,58 +163,43 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr } case reflect.Struct: + isNestedStruct = !current.Type().ConvertibleTo(timeType) + // For backward compatibility before struct level validation tags were supported + // as there were a number of projects relying on `required` not failing on non-pointer + // structs. Since it's basically nonsensical to use `required` with a non-pointer struct + // are explicitly skipping the required validation for it. This WILL be removed in the + // next major version. + if ct != nil && ct.tag == requiredTag { + ct = ct.next + } + } - typ = current.Type() - - if !typ.ConvertibleTo(timeType) { - - if ct != nil { - - if ct.typeof == typeStructOnly { - goto CONTINUE - } else if ct.typeof == typeIsDefault { - // set Field Level fields - v.slflParent = parent - v.flField = current - v.cf = cf - v.ct = ct - - if !ct.fn(ctx, v) { - v.str1 = string(append(ns, cf.altName...)) - - if v.v.hasTagNameFunc { - v.str2 = string(append(structNs, cf.name...)) - } else { - v.str2 = v.str1 - } + typ = current.Type() - v.errs = append(v.errs, - &fieldError{ - v: v.v, - tag: ct.aliasTag, - actualTag: ct.tag, - ns: v.str1, - structNs: v.str2, - fieldLen: uint8(len(cf.altName)), - structfieldLen: uint8(len(cf.name)), - value: current.Interface(), - param: ct.param, - kind: kind, - typ: typ, - }, - ) - return - } +OUTER: + for { + if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) { + // isNestedStruct check here + if isNestedStruct { + // if len == 0 then validating using 'Var' or 'VarWithValue' + // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... + // VarWithField - this allows for validating against each field within the struct against a specific value + // pretty handy in certain situations + if len(cf.name) > 0 { + ns = append(append(ns, cf.altName...), '.') + structNs = append(append(structNs, cf.name...), '.') } - ct = ct.next + v.validateStruct(ctx, parent, current, typ, ns, structNs, ct) } + return + } - if ct != nil && ct.typeof == typeNoStructLevel { - return - } + switch ct.typeof { + case typeNoStructLevel: + return - CONTINUE: + case typeStructOnly: // if len == 0 then validating using 'Var' or 'VarWithValue' // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... // VarWithField - this allows for validating against each field within the struct against a specific value @@ -224,22 +211,6 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr v.validateStruct(ctx, parent, current, typ, ns, structNs, ct) return - } - } - - if ct == nil || !ct.hasTag { - return - } - - typ = current.Type() - -OUTER: - for { - if ct == nil { - return - } - - switch ct.typeof { case typeOmitEmpty: @@ -366,7 +337,7 @@ OUTER: ct = ct.next if ct == nil { - return + continue OUTER } if ct.typeof != typeOr { diff --git a/validator_test.go b/validator_test.go index 4935c7782..d254bf70f 100644 --- a/validator_test.go +++ b/validator_test.go @@ -3150,7 +3150,7 @@ func TestInterfaceErrValidation(t *testing.T) { AssertError(t, errs, "ExternalCMD.Data.Name", "ExternalCMD.Data.Name", "Name", "Name", "required") type TestMapStructPtr struct { - Errs map[int]interface{} `validate:"gt=0,dive,len=2"` + Errs map[int]interface{} `validate:"gt=0,dive,required"` } mip := map[int]interface{}{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} @@ -3162,7 +3162,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) Equal(t, len(errs.(ValidationErrors)), 1) - AssertError(t, errs, "TestMapStructPtr.Errs[3]", "TestMapStructPtr.Errs[3]", "Errs[3]", "Errs[3]", "len") + AssertError(t, errs, "TestMapStructPtr.Errs[3]", "TestMapStructPtr.Errs[3]", "Errs[3]", "Errs[3]", "required") type TestMultiDimensionalStructs struct { Errs [][]interface{} `validate:"gt=0,dive,dive"`