added excluded_if/excluded_unless tags + tests (#847)

pull/936/head
Jacob Hochstetler 3 years ago committed by GitHub
parent 58d5778b18
commit c0195b2b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 33
      baked_in.go
  3. 34
      doc.go
  4. 4
      validator_instance.go
  5. 141
      validator_test.go

@ -219,6 +219,8 @@ Baked-in Validations
| required_with_all | Required With All | | required_with_all | Required With All |
| required_without | Required Without | | required_without | Required Without |
| required_without_all | Required Without All | | required_without_all | Required Without All |
| excluded_if | Excluded If |
| excluded_unless | Excluded Unless |
| excluded_with | Excluded With | | excluded_with | Excluded With |
| excluded_with_all | Excluded With All | | excluded_with_all | Excluded With All |
| excluded_without | Excluded Without | | excluded_without | Excluded Without |

@ -75,6 +75,8 @@ var (
"required_with_all": requiredWithAll, "required_with_all": requiredWithAll,
"required_without": requiredWithout, "required_without": requiredWithout,
"required_without_all": requiredWithoutAll, "required_without_all": requiredWithoutAll,
"excluded_if": excludedIf,
"excluded_unless": excludedUnless,
"excluded_with": excludedWith, "excluded_with": excludedWith,
"excluded_with_all": excludedWithAll, "excluded_with_all": excludedWithAll,
"excluded_without": excludedWithout, "excluded_without": excludedWithout,
@ -1542,6 +1544,22 @@ func requiredIf(fl FieldLevel) bool {
return hasValue(fl) 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 {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for excluded_if %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return false
}
}
return true
}
// requiredUnless is the validation function // 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. // 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 { func requiredUnless(fl FieldLevel) bool {
@ -1558,6 +1576,21 @@ func requiredUnless(fl FieldLevel) bool {
return hasValue(fl) return hasValue(fl)
} }
// 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 excludedUnless(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return true
}
}
return !hasValue(fl)
}
// excludedWith is the validation function // 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. // 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 { func excludedWith(fl FieldLevel) bool {

@ -349,6 +349,40 @@ Example:
// require the field if the Field1 and Field2 is not present: // require the field if the Field1 and Field2 is not present:
Usage: required_without_all=Field1 Field2 Usage: required_without_all=Field1 Field2
Excluded If
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.
Usage: excluded_if
Examples:
// exclude the field if the Field1 is equal to the parameter given:
Usage: excluded_if=Field1 foobar
// exclude the field if the Field1 and Field2 is equal to the value respectively:
Usage: excluded_if=Field1 foo Field2 bar
Excluded Unless
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.
Usage: excluded_unless
Examples:
// exclude the field unless the Field1 is equal to the parameter given:
Usage: excluded_unless=Field1 foobar
// exclude the field unless the Field1 and Field2 is equal to the value respectively:
Usage: excluded_unless=Field1 foo Field2 bar
Is Default Is Default
This validates that the value is the default value and is almost the This validates that the value is the default value and is almost the

@ -33,6 +33,8 @@ const (
excludedWithoutTag = "excluded_without" excludedWithoutTag = "excluded_without"
excludedWithTag = "excluded_with" excludedWithTag = "excluded_with"
excludedWithAllTag = "excluded_with_all" excludedWithAllTag = "excluded_with_all"
excludedIfTag = "excluded_if"
excludedUnlessTag = "excluded_unless"
skipValidationTag = "-" skipValidationTag = "-"
diveTag = "dive" diveTag = "dive"
keysTag = "keys" keysTag = "keys"
@ -120,7 +122,7 @@ func New() *Validate {
switch k { switch k {
// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour // 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, case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag,
excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag:
_ = v.registerValidation(k, wrapFunc(val), true, true) _ = v.registerValidation(k, wrapFunc(val), true, true)
default: default:
// no need to error check here, baked in will always be valid // no need to error check here, baked in will always be valid

@ -10675,6 +10675,145 @@ func TestRequiredWithoutAll(t *testing.T) {
AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all") AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all")
} }
func TestExcludedIf(t *testing.T) {
validate := New()
type Inner struct {
Field *string
}
test1 := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER *string `validate:"excluded_if=FieldE test" json:"field_er"`
}{
FieldE: "test",
}
errs := validate.Struct(test1)
Equal(t, errs, nil)
test2 := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_if=FieldE test" json:"field_er"`
}{
FieldE: "notest",
}
errs = validate.Struct(test2)
NotEqual(t, errs, nil)
ve := errs.(ValidationErrors)
Equal(t, len(ve), 1)
AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_if")
shouldError := "shouldError"
test3 := struct {
Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"`
Field1 int `validate:"excluded_if=Inner.Field test" json:"field_1"`
}{
Inner: &Inner{Field: &shouldError},
}
errs = validate.Struct(test3)
NotEqual(t, errs, nil)
ve = errs.(ValidationErrors)
Equal(t, len(ve), 1)
AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_if")
shouldPass := "test"
test4 := struct {
Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"`
Field1 int `validate:"excluded_if=Inner.Field test" json:"field_1"`
}{
Inner: &Inner{Field: &shouldPass},
}
errs = validate.Struct(test4)
Equal(t, errs, nil)
// Checks number of params in struct tag is correct
defer func() {
if r := recover(); r == nil {
t.Errorf("panicTest should have panicked!")
}
}()
fieldVal := "panicTest"
panicTest := struct {
Inner *Inner
Field1 string `validate:"excluded_if=Inner.Field" json:"field_1"`
}{
Inner: &Inner{Field: &fieldVal},
}
_ = validate.Struct(panicTest)
}
func TestExcludedUnless(t *testing.T) {
validate := New()
type Inner struct {
Field *string
}
fieldVal := "test"
test := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
}{
FieldE: "notest",
FieldER: "filled",
}
errs := validate.Struct(test)
Equal(t, errs, nil)
test2 := struct {
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"`
}{
FieldE: "test",
FieldER: "filled",
}
errs = validate.Struct(test2)
NotEqual(t, errs, nil)
ve := errs.(ValidationErrors)
Equal(t, len(ve), 1)
AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_unless")
shouldError := "test"
test3 := struct {
Inner *Inner
Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"`
}{
Inner: &Inner{Field: &shouldError},
Field1: "filled",
}
errs = validate.Struct(test3)
NotEqual(t, errs, nil)
ve = errs.(ValidationErrors)
Equal(t, len(ve), 1)
AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_unless")
shouldPass := "shouldPass"
test4 := struct {
Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"`
Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"`
}{
Inner: &Inner{Field: &shouldPass},
Field1: "filled",
}
errs = validate.Struct(test4)
Equal(t, errs, nil)
// Checks number of params in struct tag is correct
defer func() {
if r := recover(); r == nil {
t.Errorf("panicTest should have panicked!")
}
}()
panicTest := struct {
Inner *Inner
Field1 string `validate:"excluded_unless=Inner.Field" json:"field_1"`
}{
Inner: &Inner{Field: &fieldVal},
}
_ = validate.Struct(panicTest)
}
func TestLookup(t *testing.T) { func TestLookup(t *testing.T) {
type Lookup struct { type Lookup struct {
FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"` FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"`
@ -11460,7 +11599,7 @@ func TestSemverFormatValidation(t *testing.T) {
} }
} }
} }
func TestRFC1035LabelFormatValidation(t *testing.T) { func TestRFC1035LabelFormatValidation(t *testing.T) {
tests := []struct { tests := []struct {
value string `validate:"dns_rfc1035_label"` value string `validate:"dns_rfc1035_label"`

Loading…
Cancel
Save