diff --git a/README.md b/README.md index f5528266..eec60ea4 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 diff --git a/baked_in.go b/baked_in.go index 36e80572..da0363fb 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 { diff --git a/validator_test.go b/validator_test.go index e76a3cd9..714b71bc 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