diff --git a/baked_in.go b/baked_in.go index 1c2da529..a2279609 100644 --- a/baked_in.go +++ b/baked_in.go @@ -84,6 +84,7 @@ var ( "excluded_if": excludedIf, "excluded_if_contains": excludedIfContains, "excluded_unless": excludedUnless, + "excluded_unless_contains": excludedUnlessContains, "excluded_with": excludedWith, "excluded_with_all": excludedWithAll, "excluded_without": excludedWithout, @@ -1964,6 +1965,21 @@ func excludedUnless(fl FieldLevel) bool { return true } +// excludedUnless is the validation function +// The field under validation must not be present or is empty unless all the other specified fields are equal to the value following with the specified field. +func excludedUnlessContains(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for excluded_unless_contains %s", fl.FieldName())) + } + for i := 0; i < len(params); i += 2 { + if !requireCheckFieldValues(fl, params[i], params[i+1], false, true) { + return !hasValue(fl) + } + } + return true +} + // excludedWith is the validation function // The field under validation must not be present or is empty if any of the other specified fields are present. func excludedWith(fl FieldLevel) bool { diff --git a/doc.go b/doc.go index afe81266..27aa8808 100644 --- a/doc.go +++ b/doc.go @@ -426,6 +426,25 @@ Examples: // exclude the field unless the Field1 and Field2 is equal to the value respectively: Usage: excluded_unless=Field1 foo Field2 bar +# Excluded Unless Contains + +The field under validation must not be present or empty unless all +the other specified fields are equal to the value following the specified +field. For strings ensures value is not "". For slices, maps, pointers, +interfaces, channels and functions ensures the value is not nil. For structs ensures value is not the zero value. + +Diferent from excluded_unless, this tag will dive into slices to check if the value is present instead of checking the slice size. + + Usage: excluded_unless_contains + +Examples: + + // exclude the field unless the Field1 contains the parameter given: + Usage: excluded_unless_contains=Field1 foobar + + // exclude the field unless the Field1 and Field2 contains the value respectively: + Usage: excluded_unless_contains=Field1 foo Field2 bar + # Is Default This validates that the value is the default value and is almost the diff --git a/validator_instance.go b/validator_instance.go index db686a9b..b071fa7f 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -13,43 +13,44 @@ import ( ) const ( - defaultTagName = "validate" - utf8HexComma = "0x2C" - utf8Pipe = "0x7C" - tagSeparator = "," - orSeparator = "|" - tagKeySeparator = "=" - structOnlyTag = "structonly" - noStructLevelTag = "nostructlevel" - omitempty = "omitempty" - omitnil = "omitnil" - isdefault = "isdefault" - requiredWithoutAllTag = "required_without_all" - requiredWithoutTag = "required_without" - requiredWithTag = "required_with" - requiredWithAllTag = "required_with_all" - requiredIfTag = "required_if" - requiredIfContainsTag = "required_if_contains" - requiredUnlessTag = "required_unless" - skipUnlessTag = "skip_unless" - excludedWithoutAllTag = "excluded_without_all" - excludedWithoutTag = "excluded_without" - excludedWithTag = "excluded_with" - excludedWithAllTag = "excluded_with_all" - excludedIfTag = "excluded_if" - excludedIfContainsTag = "excluded_if_contains" - excludedUnlessTag = "excluded_unless" - skipValidationTag = "-" - diveTag = "dive" - keysTag = "keys" - endKeysTag = "endkeys" - requiredTag = "required" - namespaceSeparator = "." - leftBracket = "[" - rightBracket = "]" - restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" - restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" - restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" + defaultTagName = "validate" + utf8HexComma = "0x2C" + utf8Pipe = "0x7C" + tagSeparator = "," + orSeparator = "|" + tagKeySeparator = "=" + structOnlyTag = "structonly" + noStructLevelTag = "nostructlevel" + omitempty = "omitempty" + omitnil = "omitnil" + isdefault = "isdefault" + requiredWithoutAllTag = "required_without_all" + requiredWithoutTag = "required_without" + requiredWithTag = "required_with" + requiredWithAllTag = "required_with_all" + requiredIfTag = "required_if" + requiredIfContainsTag = "required_if_contains" + requiredUnlessTag = "required_unless" + skipUnlessTag = "skip_unless" + excludedWithoutAllTag = "excluded_without_all" + excludedWithoutTag = "excluded_without" + excludedWithTag = "excluded_with" + excludedWithAllTag = "excluded_with_all" + excludedIfTag = "excluded_if" + excludedIfContainsTag = "excluded_if_contains" + excludedUnlessTag = "excluded_unless" + excludedUnlessContainsTag = "excluded_unless_contains" + skipValidationTag = "-" + diveTag = "dive" + keysTag = "keys" + endKeysTag = "endkeys" + requiredTag = "required" + namespaceSeparator = "." + leftBracket = "[" + rightBracket = "]" + restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" + restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" + restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" ) var ( @@ -131,8 +132,8 @@ func New(options ...Option) *Validate { switch k { // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour case requiredIfContainsTag, requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, - excludedIfContainsTag, excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag, - skipUnlessTag: + excludedIfContainsTag, excludedIfTag, excludedUnlessContainsTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, + excludedWithoutAllTag, skipUnlessTag: _ = v.registerValidation(k, wrapFunc(val), true, true) default: // no need to error check here, baked in will always be valid diff --git a/validator_test.go b/validator_test.go index 85e4c438..f39592f5 100644 --- a/validator_test.go +++ b/validator_test.go @@ -13910,3 +13910,60 @@ func TestExcludeIfContains(t *testing.T) { AssertError(t, errs, "test.FieldTInnerP", "test.FieldTInnerP", "FieldTInnerP", "FieldTInnerP", "excluded_if_contains") AssertError(t, errs, "test.FieldTInner", "test.FieldTInner", "FieldTInner", "FieldTInner", "excluded_if_contains") } + +func TestExcludeUnlessContains(t *testing.T) { + type Inner struct { + Field []string + } + + fieldVal := "test" + type test struct { + InnerP *Inner + Inner Inner + FieldS []string `validate:"omitempty" json:"field_e"` + FieldI []int `validate:"omitempty" json:"field_i"` + FieldIP []*int `validate:"omitempty" json:"field_ip"` + FieldTS string `validate:"excluded_unless_contains=FieldS test" json:"field_ts"` + FieldTI string `validate:"excluded_unless_contains=FieldI 1" json:"field_ti"` + FieldTIP string `validate:"excluded_unless_contains=FieldIP 1" json:"field_tip"` + FieldTInnerP string `validate:"excluded_unless_contains=InnerP.Field test" json:"field_t_innerp"` + FieldTInner string `validate:"excluded_unless_contains=Inner.Field test" json:"field_t_inner"` + } + + validationOk := test{ + InnerP: &Inner{Field: []string{}}, + Inner: Inner{Field: []string{}}, + FieldS: []string{}, + FieldI: []int{}, + } + + validate := New() + + errs := validate.Struct(validationOk) + Equal(t, errs, nil) + + validationNotOk := test{ + InnerP: &Inner{Field: []string{}}, + Inner: Inner{Field: []string{}}, + FieldS: []string{}, + FieldI: []int{}, + FieldIP: []*int{}, + FieldTS: fieldVal, + FieldTI: fieldVal, + FieldTIP: fieldVal, + FieldTInnerP: fieldVal, + FieldTInner: fieldVal, + } + + errs = validate.Struct(validationNotOk) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 5) + + AssertError(t, errs, "test.FieldTS", "test.FieldTS", "FieldTS", "FieldTS", "excluded_unless_contains") + AssertError(t, errs, "test.FieldTI", "test.FieldTI", "FieldTI", "FieldTI", "excluded_unless_contains") + AssertError(t, errs, "test.FieldTIP", "test.FieldTIP", "FieldTIP", "FieldTIP", "excluded_unless_contains") + AssertError(t, errs, "test.FieldTInnerP", "test.FieldTInnerP", "FieldTInnerP", "FieldTInnerP", "excluded_unless_contains") + AssertError(t, errs, "test.FieldTInner", "test.FieldTInner", "FieldTInner", "FieldTInner", "excluded_unless_contains") +}