Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions baked_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,21 +1316,24 @@ func hasValue(fl FieldLevel) bool {
// requireCheckField is a func for check field kind
func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool {
field := fl.Field()
var ok bool
kind := field.Kind()
var nullable, found bool
if len(param) > 0 {
field, kind, ok = fl.GetStructFieldOKAdvanced(fl.Parent(), param)
if !ok {
field, kind, nullable, found = fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
if !found {
return defaultNotFoundValue
}
}
switch kind {
case reflect.Invalid:
return defaultNotFoundValue
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil()
return field.IsNil()
default:
return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
if nullable && field.Interface() != nil {
return false
}
return field.IsValid() && field.Interface() == reflect.Zero(field.Type()).Interface()
}
}

Expand All @@ -1339,7 +1342,7 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
func requiredWith(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if requireCheckFieldKind(fl, param, false) {
if !requireCheckFieldKind(fl, param, true) {
return hasValue(fl)
}
}
Expand All @@ -1351,7 +1354,7 @@ func requiredWith(fl FieldLevel) bool {
func requiredWithAll(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if !requireCheckFieldKind(fl, param, false) {
if requireCheckFieldKind(fl, param, true) {
return true
}
}
Expand All @@ -1361,11 +1364,8 @@ func requiredWithAll(fl FieldLevel) bool {
// RequiredWithout is the validation function
// The field under validation must be present and not empty only when any of the other specified fields are not present.
func requiredWithout(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if !requireCheckFieldKind(fl, param, true) {
return hasValue(fl)
}
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
return hasValue(fl)
}
return true
}
Expand All @@ -1375,7 +1375,7 @@ func requiredWithout(fl FieldLevel) bool {
func requiredWithoutAll(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if requireCheckFieldKind(fl, param, true) {
if !requireCheckFieldKind(fl, param, true) {
return true
}
}
Expand Down
37 changes: 35 additions & 2 deletions field_level.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,27 @@ type FieldLevel interface {
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
//
// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable.
GetStructFieldOK() (reflect.Value, reflect.Kind, bool)

// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
//
// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable.
GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool)

// traverses the parent struct to retrieve a specific field denoted by the provided namespace
// in the param and returns the field, field kind, if it's a nullable type and whether is was successful in retrieving
// the field at all.
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool)

// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool)
}

var _ FieldLevel = new(validate)
Expand All @@ -52,7 +68,7 @@ func (v *validate) Field() reflect.Value {
}

// FieldName returns the field's name with the tag
// name takeing precedence over the fields actual name.
// name taking precedence over the fields actual name.
func (v *validate) FieldName() string {
return v.cf.altName
}
Expand All @@ -68,12 +84,29 @@ func (v *validate) Param() string {
}

// GetStructFieldOK returns Param returns param for validation against current field
//
// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable.
func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) {
return v.getStructFieldOKInternal(v.slflParent, v.ct.param)
current, kind, _, found := v.getStructFieldOKInternal(v.slflParent, v.ct.param)
return current, kind, found
}

// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
//
// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable.
func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
current, kind, _, found := v.GetStructFieldOKAdvanced2(val, namespace)
return current, kind, found
}

// GetStructFieldOK returns Param returns param for validation against current field
func (v *validate) GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) {
return v.getStructFieldOKInternal(v.slflParent, v.ct.param)
}

// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
func (v *validate) GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) {
return v.getStructFieldOKInternal(val, namespace)
}
7 changes: 3 additions & 4 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ BEGIN:
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, found bool) {
func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) {

BEGIN:
current, kind, _ = v.ExtractType(val)

current, kind, nullable = v.ExtractType(val)
if kind == reflect.Invalid {
return
}
Expand Down Expand Up @@ -112,7 +111,7 @@ BEGIN:
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])

if arrIdx >= current.Len() {
return current, kind, false
return
}

startIdx := idx2 + 1
Expand Down
2 changes: 1 addition & 1 deletion validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strconv"
)

// per validate contruct
// per validate construct
type validate struct {
v *Validate
top reflect.Value
Expand Down
104 changes: 77 additions & 27 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1640,125 +1640,125 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {
v: vd,
}

current, kind, ok := v.getStructFieldOKInternal(val, "Inner.CreatedAt")
current, kind, _, ok := v.getStructFieldOKInternal(val, "Inner.CreatedAt")
Equal(t, ok, true)
Equal(t, kind, reflect.Struct)
tm, ok := current.Interface().(time.Time)
Equal(t, ok, true)
Equal(t, tm, now)

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.Slice[1]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[1]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField")
Equal(t, ok, false)

current, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]")
Equal(t, ok, false)

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val3")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapMap[key2][key2-1]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapMap[key2][key2-1]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapStructs[key2].Name")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapStructs[key2].Name")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "name2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapMapStruct[key3][key3-1].Name")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapMapStruct[key3][key3-1].Name")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "name3")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceSlice[2][0]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceSlice[2][0]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "7")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceSliceStruct[2][1].Name")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceSliceStruct[2][1].Name")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "name8")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceMap[1][key5]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceMap[1][key5]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val5")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapSlice[key3][2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapSlice[key3][2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "9")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt8[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt8[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt16[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt16[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt32[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt32[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapInt64[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt64[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint8[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint8[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint16[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint16[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint32[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint32[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapUint64[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint64[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat32[3.03]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat32[3.03]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val3")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat64[2.02]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat64[2.02]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val2")

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.MapBool[true]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapBool[true]")
Equal(t, ok, true)
Equal(t, kind, reflect.String)
Equal(t, current.String(), "val1")
Expand All @@ -1784,13 +1784,13 @@ func TestCrossNamespaceFieldValidation(t *testing.T) {

val = reflect.ValueOf(test)

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2]")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2]")
Equal(t, ok, true)
Equal(t, kind, reflect.Ptr)
Equal(t, current.String(), "<*validator.SliceStruct Value>")
Equal(t, current.IsNil(), true)

current, kind, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2].Name")
current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2].Name")
Equal(t, ok, false)
Equal(t, kind, reflect.Ptr)
Equal(t, current.String(), "<*validator.SliceStruct Value>")
Expand Down Expand Up @@ -8864,3 +8864,53 @@ func TestAbilityToValidateNils(t *testing.T) {
errs = val.Struct(ts)
NotEqual(t, errs, nil)
}

func TestRequiredWithoutPointers(t *testing.T) {
type Lookup struct {
FieldA *bool `json:"fieldA,omitempty" validate:"required_without=FieldB"`
FieldB *bool `json:"fieldB,omitempty" validate:"required_without=FieldA"`
}

b := true
lookup := Lookup{
FieldA: &b,
FieldB: nil,
}

val := New()
errs := val.Struct(lookup)
Equal(t, errs, nil)

b = false
lookup = Lookup{
FieldA: &b,
FieldB: nil,
}
errs = val.Struct(lookup)
Equal(t, errs, nil)
}

func TestRequiredWithoutAllPointers(t *testing.T) {
type Lookup struct {
FieldA *bool `json:"fieldA,omitempty" validate:"required_without_all=FieldB"`
FieldB *bool `json:"fieldB,omitempty" validate:"required_without_all=FieldA"`
}

b := true
lookup := Lookup{
FieldA: &b,
FieldB: nil,
}

val := New()
errs := val.Struct(lookup)
Equal(t, errs, nil)

b = false
lookup = Lookup{
FieldA: &b,
FieldB: nil,
}
errs = val.Struct(lookup)
Equal(t, errs, nil)
}