From 0c0a786f60e63225a9495a45914c2b6dbdbc1b0d Mon Sep 17 00:00:00 2001 From: Jacob Hochstetler Date: Wed, 13 Oct 2021 19:05:07 -0500 Subject: [PATCH 1/3] added excluded_if/excluded_unless + tests --- README.md | 2 + baked_in.go | 34 +++++++++++++++ doc.go | 34 +++++++++++++++ validator_instance.go | 4 +- validator_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f56cff15..864b740a 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,8 @@ Baked-in Validations | required_with_all | Required With All | | required_without | Required Without | | required_without_all | Required Without All | +| excluded_if | Excluded If | +| excluded_unless | Excluded Unless | | excluded_with | Excluded With | | excluded_with_all | Excluded With All | | excluded_without | Excluded Without | diff --git a/baked_in.go b/baked_in.go index f5fd2391..a0dc7638 100644 --- a/baked_in.go +++ b/baked_in.go @@ -75,6 +75,8 @@ var ( "required_with_all": requiredWithAll, "required_without": requiredWithout, "required_without_all": requiredWithoutAll, + "excluded_if": excludedIf, + "excluded_unless": excludedUnless, "excluded_with": excludedWith, "excluded_with_all": excludedWithAll, "excluded_without": excludedWithout, @@ -1534,6 +1536,22 @@ func requiredIf(fl FieldLevel) bool { 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 // 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 { @@ -1550,6 +1568,22 @@ func requiredUnless(fl FieldLevel) bool { 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())) + } + fmt.Println(params[0], params[1], requireCheckFieldValue(fl, params[0], params[1], false), !hasValue(fl)) + 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 // 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 8c258479..390b4eec 100644 --- a/doc.go +++ b/doc.go @@ -349,6 +349,40 @@ Example: // require the field if the Field1 and Field2 is not present: 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 This validates that the value is the default value and is almost the diff --git a/validator_instance.go b/validator_instance.go index 973964fc..6d660600 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -33,6 +33,8 @@ const ( excludedWithoutTag = "excluded_without" excludedWithTag = "excluded_with" excludedWithAllTag = "excluded_with_all" + excludedIfTag = "excluded_if" + excludedUnlessTag = "excluded_unless" skipValidationTag = "-" diveTag = "dive" keysTag = "keys" @@ -120,7 +122,7 @@ func New() *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, - excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: + excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: _ = 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 f6942037..1ea6f88b 100644 --- a/validator_test.go +++ b/validator_test.go @@ -10635,6 +10635,105 @@ func TestRequiredWithoutAll(t *testing.T) { AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all") } +func TestExcludedIf(t *testing.T) { + 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", + } + + validate := New() + + 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") + + // Checks number of params in struct tag is correct + defer func() { + if r := recover(); r == nil { + t.Errorf("test3 should have panicked!") + } + }() + + fieldVal := "test" + test3 := struct { + Inner *Inner + Field1 string `validate:"excluded_if=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(test3) +} + +func TestExcludedUnless(t *testing.T) { + 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", + } + + validate := New() + + 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") + + // Checks number of params in struct tag is correct + defer func() { + if r := recover(); r == nil { + t.Errorf("test3 should have panicked!") + } + }() + + test3 := struct { + Inner *Inner + Field1 string `validate:"excluded_unless=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(test3) +} + func TestLookup(t *testing.T) { type Lookup struct { FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"` From bd4e58b133317dd32284831fda6434dddc1d0667 Mon Sep 17 00:00:00 2001 From: Jacob Hochstetler Date: Tue, 19 Apr 2022 09:26:49 -0500 Subject: [PATCH 2/3] Removed debug line --- baked_in.go | 1 - 1 file changed, 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index ee89c8ac..36a410b6 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1583,7 +1583,6 @@ func excludedUnless(fl FieldLevel) bool { if len(params)%2 != 0 { panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName())) } - fmt.Println(params[0], params[1], requireCheckFieldValue(fl, params[0], params[1], false), !hasValue(fl)) for i := 0; i < len(params); i += 2 { if !requireCheckFieldValue(fl, params[i], params[i+1], false) { return true From b64924ca89f87abf88cc1602baf6de9b85a41985 Mon Sep 17 00:00:00 2001 From: Jacob Hochstetler Date: Tue, 19 Apr 2022 09:59:34 -0500 Subject: [PATCH 3/3] added tests for nested structs --- validator_test.go | 90 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/validator_test.go b/validator_test.go index d2bcc5c5..4da0de6a 100644 --- a/validator_test.go +++ b/validator_test.go @@ -10676,20 +10676,17 @@ func TestRequiredWithoutAll(t *testing.T) { } func TestExcludedIf(t *testing.T) { - type ( - Inner struct { - Field *string - } - ) + 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", } - - validate := New() - errs := validate.Struct(test1) Equal(t, errs, nil) @@ -10699,32 +10696,55 @@ func TestExcludedIf(t *testing.T) { }{ 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("test3 should have panicked!") + t.Errorf("panicTest should have panicked!") } }() - - fieldVal := "test" - test3 := struct { + fieldVal := "panicTest" + panicTest := struct { Inner *Inner Field1 string `validate:"excluded_if=Inner.Field" json:"field_1"` }{ Inner: &Inner{Field: &fieldVal}, } - _ = validate.Struct(test3) + _ = validate.Struct(panicTest) } func TestExcludedUnless(t *testing.T) { + validate := New() type Inner struct { Field *string } @@ -10737,9 +10757,6 @@ func TestExcludedUnless(t *testing.T) { FieldE: "notest", FieldER: "filled", } - - validate := New() - errs := validate.Struct(test) Equal(t, errs, nil) @@ -10750,28 +10767,51 @@ func TestExcludedUnless(t *testing.T) { 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("test3 should have panicked!") + t.Errorf("panicTest should have panicked!") } }() - - test3 := struct { + panicTest := struct { Inner *Inner Field1 string `validate:"excluded_unless=Inner.Field" json:"field_1"` }{ Inner: &Inner{Field: &fieldVal}, } - _ = validate.Struct(test3) + _ = validate.Struct(panicTest) } func TestLookup(t *testing.T) { @@ -11559,7 +11599,7 @@ func TestSemverFormatValidation(t *testing.T) { } } } - + func TestRFC1035LabelFormatValidation(t *testing.T) { tests := []struct { value string `validate:"dns_rfc1035_label"`