Skip to content

Commit

Permalink
feat: add excluded_unless_contains
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasoares committed Apr 25, 2024
1 parent 58330a7 commit fd2425e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 39 deletions.
16 changes: 16 additions & 0 deletions baked_in.go
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
19 changes: 19 additions & 0 deletions doc.go
Expand Up @@ -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
Expand Down
79 changes: 40 additions & 39 deletions validator_instance.go
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions validator_test.go
Expand Up @@ -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")
}

0 comments on commit fd2425e

Please sign in to comment.