From 742006d126b63deb951c05d1f25b4e61fc910a78 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 15 Sep 2020 18:13:38 +0200 Subject: [PATCH 1/7] Closes #660 added excluded with and without --- baked_in.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/baked_in.go b/baked_in.go index 36e8057..da0363f 100644 --- a/baked_in.go +++ b/baked_in.go @@ -68,6 +68,10 @@ var ( "required_with_all": requiredWithAll, "required_without": requiredWithout, "required_without_all": requiredWithoutAll, + "excluded_with": excludedWith, + "excluded_with_all": excludedWithAll, + "excluded_without": excludedWithout, + "excluded_without_all": excludedWithoutAll, "isdefault": isDefault, "len": hasLengthOf, "min": hasMinOf, @@ -1383,6 +1387,18 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo } } +// 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 { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if !requireCheckFieldKind(fl, param, true) { + return !hasValue(fl) + } + } + return true +} + // RequiredWith is the validation function // The field under validation must be present and not empty only if any of the other specified fields are present. func requiredWith(fl FieldLevel) bool { @@ -1395,6 +1411,18 @@ func requiredWith(fl FieldLevel) bool { return true } +// ExcludedWithAll is the validation function +// The field under validation must not be present or is empty if all of the other specified fields are present. +func excludedWithAll(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if requireCheckFieldKind(fl, param, true) { + return true + } + } + return !hasValue(fl) +} + // RequiredWithAll is the validation function // The field under validation must be present and not empty only if all of the other specified fields are present. func requiredWithAll(fl FieldLevel) bool { @@ -1407,6 +1435,15 @@ func requiredWithAll(fl FieldLevel) bool { return hasValue(fl) } +// ExcludedWithout is the validation function +// The field under validation must not be present or is empty when any of the other specified fields are not present. +func excludedWithout(fl FieldLevel) bool { + if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) { + return !hasValue(fl) + } + return true +} + // RequiredWithout is the validation function // The field under validation must be present and not empty only when any of the other specified fields are not present. func requiredWithout(fl FieldLevel) bool { @@ -1416,6 +1453,18 @@ func requiredWithout(fl FieldLevel) bool { return true } +// RequiredWithoutAll is the validation function +// The field under validation must not be present or is empty when all of the other specified fields are not present. +func excludedWithoutAll(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if !requireCheckFieldKind(fl, param, true) { + return true + } + } + return !hasValue(fl) +} + // RequiredWithoutAll is the validation function // The field under validation must be present and not empty only when all of the other specified fields are not present. func requiredWithoutAll(fl FieldLevel) bool { From 490e2537923b67d8a5b6d96111aff7768a06a34b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 15 Sep 2020 19:40:20 +0200 Subject: [PATCH 2/7] Closes #660 added excluded with and without tests --- validator_test.go | 262 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) diff --git a/validator_test.go b/validator_test.go index e76a3cd..714b71b 100644 --- a/validator_test.go +++ b/validator_test.go @@ -8750,6 +8750,268 @@ func TestRequiredWith(t *testing.T) { AssertError(t, errs, "Field6", "Field6", "Field6", "Field6", "required_with") } +func TestExcludedWith(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with=FieldE" json:"field_1"` + Field2 *string `validate:"excluded_with=FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_with=FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_with=FieldE" json:"field_4"` + Field5 string `validate:"excluded_with=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_with=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with=Field" json:"field_1"` + Field2 *string `validate:"excluded_with=Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_with=Field" json:"field_3"` + Field4 interface{} `validate:"excluded_with=Field" json:"field_4"` + Field5 string `validate:"excluded_with=Inner.Field" json:"field_5"` + Field6 string `validate:"excluded_with=Inner2.Field" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 5) + for i := 1; i <= 5; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_with") + } +} + +func TestExcludedWithout(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without=Field" json:"field_1"` + Field2 *string `validate:"excluded_without=Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_without=Field" json:"field_3"` + Field4 interface{} `validate:"excluded_without=Field" json:"field_4"` + Field5 string `validate:"excluded_without=Inner.Field" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without=FieldE" json:"field_1"` + Field2 *string `validate:"excluded_without=FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_without=FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_without=FieldE" json:"field_4"` + Field5 string `validate:"excluded_without=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_without=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 6) + for i := 1; i <= 6; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_without") + } +} + +func TestExcludedWithAll(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with_all=FieldE Field" json:"field_1"` + Field2 *string `validate:"excluded_with_all=FieldE Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_with_all=FieldE Field" json:"field_3"` + Field4 interface{} `validate:"excluded_with_all=FieldE Field" json:"field_4"` + Field5 string `validate:"excluded_with_all=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_with_all=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: fieldVal, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with_all=Field FieldE" json:"field_1"` + Field2 *string `validate:"excluded_with_all=Field FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_with_all=Field FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_with_all=Field FieldE" json:"field_4"` + Field5 string `validate:"excluded_with_all=Inner.Field" json:"field_5"` + Field6 string `validate:"excluded_with_all=Inner2.Field" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + FieldE: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 5) + for i := 1; i <= 5; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_with_all") + } +} + +func TestExcludedWithoutAll(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without_all=Field FieldE" json:"field_1"` + Field2 *string `validate:"excluded_without_all=Field FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_without_all=Field FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_without_all=Field FieldE" json:"field_4"` + Field5 string `validate:"excluded_without_all=Inner.Field Inner.Field2" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without_all=FieldE Field" json:"field_1"` + Field2 *string `validate:"excluded_without_all=FieldE Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_without_all=FieldE Field" json:"field_3"` + Field4 interface{} `validate:"excluded_without_all=FieldE Field" json:"field_4"` + Field5 string `validate:"excluded_without_all=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_without_all=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 6) + for i := 1; i <= 6; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_without_all") + } +} + func TestRequiredWithAll(t *testing.T) { type Inner struct { Field *string From ca0533bb970bed5f048a23479ea9881fb70ca285 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 15 Sep 2020 19:45:48 +0200 Subject: [PATCH 3/7] Closes #660 readme updates --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f552826..eec60ea 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,10 @@ Baked-in Validations | required_with_all | Required With All | | required_without | Required Without | | required_without_all | Required Without All | +| excluded_with | Excluded With | +| excluded_with_all | Excluded With All | +| excluded_without | Excluded Without | +| excluded_without_all | Excluded Without All | | unique | Unique | Benchmarks From 1bf11c92993341053af72a7a99d6d9a76425dc1e Mon Sep 17 00:00:00 2001 From: David Ketch Date: Wed, 16 Sep 2020 15:14:29 -0500 Subject: [PATCH 4/7] Includes Error in FieldError's interface --- errors.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/errors.go b/errors.go index 86420b9..9682052 100644 --- a/errors.go +++ b/errors.go @@ -155,6 +155,9 @@ type FieldError interface { // NOTE: if no registered translator can be found it returns the same as // calling fe.Error() Translate(ut ut.Translator) string + + // Error returns the FieldError's message + Error() string } // compile time interface checks From f2ce6a4f9288fb6dd8cc205b6e5c7694b13623f6 Mon Sep 17 00:00:00 2001 From: Takeshi Kaneko Date: Thu, 17 Sep 2020 21:04:54 +0900 Subject: [PATCH 5/7] Fix a category of endswith in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f552826..1f795dd 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Baked-in Validations | contains | Contains | | containsany | Contains Any | | containsrune | Contains Rune | +| endswith | Ends With | | lowercase | Lowercase | | multibyte | Multi-Byte Characters | | number | NOT DOCUMENTED IN doc.go | @@ -186,7 +187,6 @@ Baked-in Validations | - | - | | dir | Directory | | e164 | NOT DOCUMENTED IN doc.go | -| endswith | Ends With | | excludes | Excludes | | excludesall | Excludes All | | excludesrune | Excludes Rune | From 0d657b4731be9539ccb8e6fc1cce2452d34231e1 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 27 Sep 2020 11:04:00 -0700 Subject: [PATCH 6/7] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6484f3..641b331 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.13.7 + - 1.15.2 - tip matrix: allow_failures: @@ -26,4 +26,4 @@ script: after_success: | [ $TRAVIS_GO_VERSION = 1.13.7 ] && - goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN \ No newline at end of file + goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN From ace513eddd249390d67a8c04e219aaa0b4e82fc1 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 27 Sep 2020 11:04:12 -0700 Subject: [PATCH 7/7] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 641b331..85a7be3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,5 +25,5 @@ script: - go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./... after_success: | - [ $TRAVIS_GO_VERSION = 1.13.7 ] && + [ $TRAVIS_GO_VERSION = 1.15.2 ] && goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN