Skip to content

Commit

Permalink
refactor struct level validations to support or's etc
Browse files Browse the repository at this point in the history
  • Loading branch information
deankarn committed Aug 22, 2023
1 parent a2ffbbb commit 17130c6
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 64 deletions.
95 changes: 33 additions & 62 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -366,7 +337,7 @@ OUTER:
ct = ct.next

if ct == nil {
return
continue OUTER
}

if ct.typeof != typeOr {
Expand Down
4 changes: 2 additions & 2 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}
Expand All @@ -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"`
Expand Down

0 comments on commit 17130c6

Please sign in to comment.