Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add required_if_contains, excluded_unless_contains and excluded_if_contains #1257

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -244,13 +244,16 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| oneof | One Of |
| required | Required |
| required_if | Required If |
| required_if_contains | Required If Contains |
| required_unless | Required Unless |
| required_with | Required With |
| required_with_all | Required With All |
| required_without | Required Without |
| required_without_all | Required Without All |
| excluded_if | Excluded If |
| excluded_if_contains | Excluded If Contains |
| excluded_unless | Excluded Unless |
| excluded_unless_contains | Excluded Unless Contains |
| excluded_with | Excluded With |
| excluded_with_all | Excluded With All |
| excluded_without | Excluded Without |
Expand Down
80 changes: 80 additions & 0 deletions baked_in.go
Expand Up @@ -74,14 +74,17 @@ var (
bakedInValidators = map[string]Func{
"required": hasValue,
"required_if": requiredIf,
"required_if_contains": requiredIfContains,
"required_unless": requiredUnless,
"skip_unless": skipUnless,
"required_with": requiredWith,
"required_with_all": requiredWithAll,
"required_without": requiredWithout,
"required_without_all": requiredWithoutAll,
"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 @@ -1792,12 +1795,23 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
// requireCheckFieldValue is a func for check field value
func requireCheckFieldValue(
fl FieldLevel, param string, value string, defaultNotFoundValue bool,
) bool {
return requireCheckFieldValues(fl, param, value, defaultNotFoundValue, false)
}

// requireCheckFieldValue is a func for check field value
func requireCheckFieldValues(
fl FieldLevel, param string, value string, defaultNotFoundValue bool, sliceContains bool,
) bool {
field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
if !found {
return defaultNotFoundValue
}

return compareValues(field, kind, value, sliceContains)
}

func compareValues(field reflect.Value, kind reflect.Kind, value string, sliceContains bool) bool {
switch kind {

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
Expand All @@ -1813,8 +1827,28 @@ func requireCheckFieldValue(
return field.Float() == asFloat64(value)

case reflect.Slice, reflect.Map, reflect.Array:
// If slice contains is true, should look for the value inside the slice
if sliceContains {
for i := 0; i < field.Len(); i++ {
item := field.Index(i)
if compareValues(item, item.Kind(), value, false) {
return true
}
}

return false
}

return int64(field.Len()) == asInt(value)

case reflect.Ptr:
if field.IsNil() {
return false
}

element := field.Elem()

return compareValues(element, element.Kind(), value, false)
case reflect.Bool:
return field.Bool() == asBool(value)
}
Expand All @@ -1838,6 +1872,21 @@ func requiredIf(fl FieldLevel) bool {
return hasValue(fl)
}

// requiredIfContains is the validation function
// The field under validation must be present and not empty only if all the other specified fields are equal to the value following with the specified field.
func requiredIfContains(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for required_if_contains %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValues(fl, params[i], params[i+1], false, true) {
return true
}
}
return hasValue(fl)
}

// excludedIf is the validation function
// The field under validation must not be present or is empty only if all the other specified fields are equal to the value following with the specified field.
func excludedIf(fl FieldLevel) bool {
Expand All @@ -1854,6 +1903,22 @@ func excludedIf(fl FieldLevel) bool {
return !hasValue(fl)
}

// excludedIfContains is the validation function
// The field under validation must not be present or is empty only if all the other specified fields are equal to the value following with the specified field.
func excludedIfContains(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for excluded_if_contains %s", fl.FieldName()))
}

for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValues(fl, params[i], params[i+1], false, true) {
return true
}
}
return !hasValue(fl)
}

// requiredUnless is the validation function
// The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field.
func requiredUnless(fl FieldLevel) bool {
Expand Down Expand Up @@ -1900,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
57 changes: 57 additions & 0 deletions doc.go
Expand Up @@ -275,6 +275,25 @@ Examples:
// require the field if the Field1 and Field2 is equal to the value respectively:
Usage: required_if=Field1 foo Field2 bar

# Required If Contains

The field under validation must be present and not empty only if 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 required_if, this tag will dive into slices to check if the value is present instead of checking the slice size.

Usage: required_if_contains

Examples:

// require the field if the Field1 contains the parameter given:
Usage: required_if_contains=Field1 foobar

// require the field if the Field1 and Field2 contains the value respectively:
Usage: required_if_contains=Field1 foo Field2 bar

# Required Unless

The field under validation must be present and not empty unless all
Expand Down Expand Up @@ -371,6 +390,25 @@ Examples:
// exclude the field if the Field1 and Field2 is equal to the value respectively:
Usage: excluded_if=Field1 foo Field2 bar

# Excluded If Contains

The field under validation must not be present or not empty only if 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_if, this tag will dive into slices to check if the value is present instead of checking the slice size.

Usage: excluded_if_contains

Examples:

// exclude the field if the Field1 contains the parameter given:
Usage: excluded_if_contains=Field1 foobar

// exclude the field if the Field1 and Field2 contains the value respectively:
Usage: excluded_if_contains=Field1 foo Field2 bar

# Excluded Unless

The field under validation must not be present or empty unless all
Expand All @@ -388,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: 41 additions & 38 deletions validator_instance.go
Expand Up @@ -13,41 +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"
requiredUnlessTag = "required_unless"
skipUnlessTag = "skip_unless"
excludedWithoutAllTag = "excluded_without_all"
excludedWithoutTag = "excluded_without"
excludedWithTag = "excluded_with"
excludedWithAllTag = "excluded_with_all"
excludedIfTag = "excluded_if"
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 @@ -128,9 +131,9 @@ 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 requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag,
excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag,
skipUnlessTag:
case requiredIfContainsTag, requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag,
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