diff --git a/README.md b/README.md index ec9e083..737b07e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The following table shows the validations and possible types, where "I" means "I | max | I | - | - | W | W | W | W | W | | min | I | - | - | W | W | W | W | W | | in | I | W | W | W | W | W | - | W | -| nin | W | W | W | W | W | W | - | W | +| nin | I | W | W | W | W | W | - | W | | required | I | W | W | W | W | W | W | W | | email | I | - | - | - | - | - | - | - | diff --git a/tests/endtoend/string.go b/tests/endtoend/string.go index 754b395..a87a9b3 100644 --- a/tests/endtoend/string.go +++ b/tests/endtoend/string.go @@ -11,6 +11,7 @@ type StringType struct { FieldNeq string `validate:"neq=cba"` FieldNeqIC string `validate:"neq_ignore_case=YeS"` FieldIn string `validate:"in=ab bc cd"` + FieldNotIn string `validate:"nin=xx yy zz"` EmailReq string `validate:"required,email"` EmailOpt string `validate:"email"` } @@ -28,8 +29,9 @@ func string_tests() { FieldNeq: "cba", FieldNeqIC: "yeS", FieldIn: "abc", - EmailReq: "invalid.email.format", // Invalid required email - EmailOpt: "invalid", // Invalid optional email + FieldNotIn: "zz", + EmailReq: "invalid.email.format", // Invalid required email + EmailOpt: "invalid", // Invalid optional email } expectedMsgErrors = []string{ "FieldReq is required", @@ -40,6 +42,7 @@ func string_tests() { "FieldNeq must not be equal to 'cba'", "FieldNeqIC must not be equal to 'yes'", "FieldIn must be one of 'ab' 'bc' 'cd'", + "FieldNotIn must not be one of 'xx' 'yy' 'zz'", "EmailReq must be a valid email", "EmailOpt must be a valid email", } @@ -58,8 +61,9 @@ func string_tests() { FieldNeq: "ops", FieldNeqIC: "No", FieldIn: "bc", - EmailReq: "user@example.com", // Valid required email - EmailOpt: "", // Empty optional email (valid) + FieldNotIn: "xy", + EmailReq: "user@example.com", // Valid required email + EmailOpt: "", // Empty optional email (valid) } expectedMsgErrors = nil errs = StringTypeValidate(v) diff --git a/tests/endtoend/stringtype_validator.go b/tests/endtoend/stringtype_validator.go index a98b570..4bbd4d4 100644 --- a/tests/endtoend/stringtype_validator.go +++ b/tests/endtoend/stringtype_validator.go @@ -45,6 +45,10 @@ func StringTypeValidate(obj *StringType) []error { errs = append(errs, types.NewValidationError("FieldIn must be one of 'ab' 'bc' 'cd'")) } + if !(obj.FieldNotIn != "xx" && obj.FieldNotIn != "yy" && obj.FieldNotIn != "zz") { + errs = append(errs, types.NewValidationError("FieldNotIn must not be one of 'xx' 'yy' 'zz'")) + } + if !(obj.EmailReq != "") { errs = append(errs, types.NewValidationError("EmailReq is required")) } diff --git a/validgen/get_test_elements_string_test.go b/validgen/get_test_elements_string_test.go index 372cd56..a3d734a 100644 --- a/validgen/get_test_elements_string_test.go +++ b/validgen/get_test_elements_string_test.go @@ -126,10 +126,11 @@ func TestGetTestElementsWithStringFields(t *testing.T) { fieldValidation: "in=a b c", }, want: TestElements{ - leftOperand: "obj.InField", - operator: "==", - rightOperands: []string{`"a"`, `"b"`, `"c"`}, - errorMessage: "InField must be one of 'a' 'b' 'c'", + leftOperand: "obj.InField", + operator: "==", + rightOperands: []string{`"a"`, `"b"`, `"c"`}, + concatOperator: "||", + errorMessage: "InField must be one of 'a' 'b' 'c'", }, }, { @@ -139,10 +140,25 @@ func TestGetTestElementsWithStringFields(t *testing.T) { fieldValidation: "in=' a ' ' b ' ' c '", }, want: TestElements{ - leftOperand: "obj.InField", - operator: "==", - rightOperands: []string{`" a "`, `" b "`, `" c "`}, - errorMessage: "InField must be one of ' a ' ' b ' ' c '", + leftOperand: "obj.InField", + operator: "==", + rightOperands: []string{`" a "`, `" b "`, `" c "`}, + concatOperator: "||", + errorMessage: "InField must be one of ' a ' ' b ' ' c '", + }, + }, + { + name: "NotIn string with spaces", + args: args{ + fieldName: "NotInField", + fieldValidation: "nin=a b c", + }, + want: TestElements{ + leftOperand: "obj.NotInField", + operator: "!=", + rightOperands: []string{`"a"`, `"b"`, `"c"`}, + concatOperator: "&&", + errorMessage: "NotInField must not be one of 'a' 'b' 'c'", }, }, { diff --git a/validgen/if_code.go b/validgen/if_code.go index 2d840cd..8a3983c 100644 --- a/validgen/if_code.go +++ b/validgen/if_code.go @@ -28,7 +28,7 @@ func IfCode(fieldName, fieldValidation, fieldType string) (string, error) { booleanCondition := "" for _, roperand := range testElements.rightOperands { if booleanCondition != "" { - booleanCondition += " || " + booleanCondition += " " + testElements.concatOperator + " " } booleanCondition += fmt.Sprintf("%s %s %s", testElements.leftOperand, testElements.operator, roperand) diff --git a/validgen/if_code_test.go b/validgen/if_code_test.go index 0bb9aa7..4c81c59 100644 --- a/validgen/if_code_test.go +++ b/validgen/if_code_test.go @@ -51,6 +51,19 @@ func TestIfCode(t *testing.T) { if !(obj.strField == "a" || obj.strField == "b" || obj.strField == "c") { errs = append(errs, types.NewValidationError("strField must be one of 'a' 'b' 'c'")) } +`, + }, + { + name: "if code with string and not in", + args: args{ + fieldName: "strField", + fieldType: "string", + fieldValidation: "nin=a b c", + }, + want: ` + if !(obj.strField != "a" && obj.strField != "b" && obj.strField != "c") { + errs = append(errs, types.NewValidationError("strField must not be one of 'a' 'b' 'c'")) + } `, }, } diff --git a/validgen/parser_validation.go b/validgen/parser_validation.go index 9b0deeb..9077b1a 100644 --- a/validgen/parser_validation.go +++ b/validgen/parser_validation.go @@ -34,6 +34,7 @@ func ParserValidation(fieldValidation string) (*Validation, error) { "neq": ONE_VALUE, "neq_ignore_case": ONE_VALUE, "in": MANY_VALUES, + "nin": MANY_VALUES, "email": ZERO_VALUE, } diff --git a/validgen/test_elements.go b/validgen/test_elements.go index 71ee71c..0f3cafe 100644 --- a/validgen/test_elements.go +++ b/validgen/test_elements.go @@ -8,15 +8,18 @@ import ( ) const ( - EqIgnoreCaseTag = "eq_ignore_case" - NeqIgnoreCaseTag = "neq_ignore_case" + EqIgnoreCaseOp = "eq_ignore_case" + NeqIgnoreCaseOp = "neq_ignore_case" + InOp = "in" + NotInOp = "nin" ) type TestElements struct { - leftOperand string - operator string - rightOperands []string - errorMessage string + leftOperand string + operator string + rightOperands []string + concatOperator string + errorMessage string } func GetTestElements(fieldName, fieldValidation, fieldType string) (TestElements, error) { @@ -41,6 +44,7 @@ func GetTestElements(fieldName, fieldValidation, fieldType string) (TestElements "neq,string": {"{{.Name}}", "!=", `"{{.Target}}"`, "{{.Name}} must not be equal to '{{.Target}}'"}, "neq_ignore_case,string": {"types.ToLower({{.Name}})", "!=", `"{{.Target}}"`, "{{.Name}} must not be equal to '{{.Target}}'"}, "in,string": {"{{.Name}}", "==", `"{{.Target}}"`, "{{.Name}} must be one of {{.Targets}}"}, + "nin,string": {"{{.Name}}", "!=", `"{{.Target}}"`, "{{.Name}} must not be one of {{.Targets}}"}, "email,string": {"types.IsValidEmail({{.Name}})", "==", `true`, "{{.Name}} must be a valid email"}, } @@ -54,7 +58,7 @@ func GetTestElements(fieldName, fieldValidation, fieldType string) (TestElements return TestElements{}, types.NewValidationError("unsupported validation %s type %s", fieldValidation, fieldType) } - if validation.Operation == EqIgnoreCaseTag || validation.Operation == NeqIgnoreCaseTag { + if validation.Operation == EqIgnoreCaseOp || validation.Operation == NeqIgnoreCaseOp { for i := range validation.Values { validation.Values[i] = strings.ToLower(validation.Values[i]) } @@ -77,16 +81,31 @@ func GetTestElements(fieldName, fieldValidation, fieldType string) (TestElements } } + var concatOperator string + switch validation.Operation { + case InOp: + concatOperator = "||" + case NotInOp: + concatOperator = "&&" + default: + concatOperator = "" + } + + if len(roperands) > 1 && concatOperator == "" { + return TestElements{}, types.NewValidationError("missed concat operator") + } + targetValues = strings.TrimSpace(targetValues) errorMsg := condition.errorMessage errorMsg = replaceNameAndTargetWithoutPrefix(errorMsg, fieldName, targetValue) errorMsg = replaceTargetInErrors(errorMsg, targetValue, targetValues) return TestElements{ - leftOperand: replaceNameAndTargetWithPrefix(condition.loperand, fieldName, targetValue), - operator: condition.operator, - rightOperands: roperands, - errorMessage: errorMsg, + leftOperand: replaceNameAndTargetWithPrefix(condition.loperand, fieldName, targetValue), + operator: condition.operator, + rightOperands: roperands, + concatOperator: concatOperator, + errorMessage: errorMsg, }, nil }