diff --git a/README.md b/README.md index 815f15c..47420e8 100644 --- a/README.md +++ b/README.md @@ -121,20 +121,20 @@ The following table shows the validations and possible types, where: | Validation/Type | String | Numeric types (integers and floats) | Complex | Boolean | Slice | Array | Map | Time | Duration | | - | - | - | - | - | - | - | - | - | - | -| eq | I | I | W | I | - | - | - | W | W | +| eq | I | I | I | I | - | - | - | W | W | | eq_ignore_case | I | - | - | - | - | - | - | - | - | | gt | - | I | - | - | - | - | - | W | W | | gte | - | I | - | - | - | - | - | W | W | | lt | - | I | - | - | - | - | - | W | W | | lte | - | I | - | - | - | - | - | W | W | -| neq | I | I | W | I | - | - | - | W | W | +| neq | I | I | I | I | - | - | - | W | W | | neq_ignore_case | I | - | - | - | - | - | - | - | - | | len | I | - | - | - | I | - | W | - | - | | max | I | - | - | - | I | - | W | W | W | | min | I | - | - | - | I | - | W | W | W | -| in | I | I | W | - | I | I | W | - | W | -| nin | I | I | W | - | I | I | W | - | W | -| required | I | I | W | - | I | - | W | W | W | +| in | I | I | I | - | I | I | W | - | W | +| nin | I | I | I | - | I | I | W | - | W | +| required | I | I | I | - | I | - | W | W | W | | email | I | - | - | - | - | - | - | - | - | | eqfield | I | I | I | I | - | - | - | W | W | | neqfield | I | I | I | I | - | - | - | W | W | @@ -143,7 +143,7 @@ The following table shows the validations and possible types, where: | ltefield | - | I | - | - | - | - | - | W | W | | ltfield | - | I | - | - | - | - | - | W | W | -Complex (`complex64`, `complex128`) supports `eqfield` and `neqfield` via Go `==` / `!=`. Ordering field tags (`gtefield`, `gtfield`, `ltefield`, `ltfield`) are rejected: Go has no `<` / `>` / `<=` / `>=` for complex values, and go-playground/validator does not define concrete ordering semantics for them. +Complex (`complex64`, `complex128`) supports `eq`, `neq`, `in`, `nin`, `eqfield`, and `neqfield` via Go `==` / `!=`, and `required` via `!= 0` (the zero value `0+0i`). Tag values are Go imaginary literals without spaces, for example `eq=1+2i` and `in=1+2i 5+6i`. Ordering tags (`gt`, `gte`, `lt`, `lte`, `gtfield`, `gtefield`, `ltfield`, `ltefield`) are rejected: Go has no `<` / `>` / `<=` / `>=` for complex values. ## Steps to run the unit tests diff --git a/docs/internals.md b/docs/internals.md index bee4a3f..3435549 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -88,10 +88,12 @@ An unknown operation has `CountValues` zero, which is `UndefinedValue`, and `Par If the field's `BaseType` is `package.Struct` for a struct parsed in this run, the type check stops there. Otherwise the field must be a Go type, and `IsValidByType` must accept the operation for `ToNormalizedString`. -`IsValidByType` strips one leading `*`. For `required` on a pointer it returns true without reading the type list. Every other operation must list the remaining normalized type. `GetConditionTable` still has to find a row for that normalized type when code is generated. +`IsValidByType` strips one leading `*`. A stripped `` pointer is rejected, because the condition table has no `*` row. For `required` on any other pointer it returns true without reading the type list. Every other operation must list the remaining normalized type. `GetConditionTable` still has to find a row for that normalized type when code is generated. ### Field comparisons +Scalar `` accepts `eq`, `neq`, `in`, `nin`, and `required`. The equality rows emit `==` or `!=` against the tag literal. `required` emits `!= 0`, which is the complex zero value. `gt`, `gte`, `lt`, and `lte` stay limited to `` and ``, so the generator does not emit `<`, `>`, `<=`, or `>=` for complex values. Slice, array, and map complex values are rejected. Scalar complex pointers are rejected in `IsValidByType` and omitted from generation. + `eqfield`, `neqfield`, `gtfield`, `gtefield`, `ltfield`, and `ltefield` set `IsFieldOperation`. `eqfield` and `neqfield` allow ``, ``, ``, ``, and ``. Their condition-table rows compare with `==` and `!=`. `gtfield`, `gtefield`, `ltfield`, and `ltefield` allow `` and `` only. `analyzeFieldOperations` checks field operations after the catalog checks. The target is the single value from the tag. `Field2` refers to a field of the same struct. `Nested.Field2` refers to a field of the struct stored on `Nested`. The lookup key is `common.KeyPath`, which joins names with `.`. Both fields must already be in the parsed set, and their `FieldType` values must be equal. That comparison includes `ComposedType`, `BaseType`, and `Size`, so `int` and `int32` do not match. @@ -140,7 +142,7 @@ errs = append(errs, types.NewValidationError("message")) `GetConditionTable` selects the row whose `AcceptedTypes` contain `ToNormalizedString`. `DefineTestElements` substitutes placeholders in that row's `operation` string. `{{.Name}}` becomes the field name. `{{.Target}}` becomes one tag value. -Scalar `in` rows set `concatOperator` to `||`, and the per-value copies are joined. That includes `*`, `*`, `*`, and `*`. Scalar `nin` rows set `concatOperator` to `&&`. Slice, array, and map rows leave `concatOperator` empty, so `DefineTestElements` keeps one copy. That copy lists every target through `{{.TargetsAsStringSlice}}` or `{{.TargetsAsNumericSlice}}`. +Scalar `in` rows set `concatOperator` to `||`, and the per-value copies are joined. That includes ``, `*`, `*`, `*`, and `*`. Scalar `nin` rows set `concatOperator` to `&&`. Complex scalar rows use the same `==` / `!=` form as numeric scalars, with the tag literal left unquoted. Slice, array, and map rows leave `concatOperator` empty, so `DefineTestElements` keeps one copy. That copy lists every target through `{{.TargetsAsStringSlice}}` or `{{.TargetsAsNumericSlice}}`. Those slice and map copies call `types.SliceOnlyContains`, `types.SliceNotContains`, `types.MapOnlyContains`, or `types.MapNotContains`. Non-pointer array rows pass `obj.Field[:]` into the slice helpers. Literal string comparisons quote the target. `email` calls `types.IsValidEmail`. `eq_ignore_case` and `neq_ignore_case` call `strings.EqualFold`. Field comparisons compile to `obj.Field` compared with `obj.Other` or `obj.Nested.Field`. diff --git a/internal/analyzer/analyzer_test.go b/internal/analyzer/analyzer_test.go index 3940172..8f92e8d 100644 --- a/internal/analyzer/analyzer_test.go +++ b/internal/analyzer/analyzer_test.go @@ -731,3 +731,83 @@ func TestAnalyzeStructsWithInvalidNestedFieldOperations(t *testing.T) { }) } } + +func TestAnalyzeComplexScalarOperations(t *testing.T) { + validTags := []string{ + `valid:"required"`, + `valid:"eq=1+2i"`, + `valid:"neq=3-4i"`, + `valid:"in=1+2i 5+6i"`, + `valid:"nin=7+8i 9+0i"`, + } + invalidTags := []struct { + tag string + op string + }{ + {tag: `valid:"gt=1+2i"`, op: "gt"}, + {tag: `valid:"gte=1+2i"`, op: "gte"}, + {tag: `valid:"lt=1+2i"`, op: "lt"}, + {tag: `valid:"lte=1+2i"`, op: "lte"}, + } + + for _, baseType := range []string{"complex64", "complex128"} { + for _, tag := range validTags { + t.Run(baseType+" "+tag, func(t *testing.T) { + _, err := AnalyzeStructs([]*parser.Struct{{ + Fields: []parser.Field{{ + FieldName: "Value", + Type: common.FieldType{BaseType: baseType}, + Tag: tag, + }}, + }}) + if err != nil { + t.Errorf("AnalyzeStructs() error = %v", err) + } + }) + } + + for _, tt := range invalidTags { + t.Run(baseType+" "+tt.tag, func(t *testing.T) { + _, err := AnalyzeStructs([]*parser.Struct{{ + Fields: []parser.Field{{ + FieldName: "Value", + Type: common.FieldType{BaseType: baseType}, + Tag: tt.tag, + }}, + }}) + wantErr := types.NewValidationError("operation %s: invalid %s() type", tt.op, baseType) + if err != wantErr { + t.Errorf("AnalyzeStructs() error = %v, wantErr %v", err, wantErr) + } + }) + } + } + + composed := []struct { + name string + composed string + norm string + tag string + op string + }{ + {name: "slice", composed: "[]", norm: "[]", tag: `valid:"eq=1+2i"`, op: "eq"}, + {name: "map", composed: "map", norm: "map[]", tag: `valid:"eq=1+2i"`, op: "eq"}, + {name: "pointer", composed: "*", norm: "*", tag: `valid:"eq=1+2i"`, op: "eq"}, + {name: "pointer required", composed: "*", norm: "*", tag: `valid:"required"`, op: "required"}, + } + for _, shape := range composed { + t.Run(shape.name, func(t *testing.T) { + _, err := AnalyzeStructs([]*parser.Struct{{ + Fields: []parser.Field{{ + FieldName: "Value", + Type: common.FieldType{BaseType: "complex128", ComposedType: shape.composed}, + Tag: shape.tag, + }}, + }}) + wantErr := types.NewValidationError("operation %s: invalid complex128(%s) type", shape.op, shape.norm) + if err != wantErr { + t.Errorf("AnalyzeStructs() error = %v, wantErr %v", err, wantErr) + } + }) + } +} diff --git a/internal/analyzer/operations/operations.go b/internal/analyzer/operations/operations.go index d93952a..ebc73be 100644 --- a/internal/analyzer/operations/operations.go +++ b/internal/analyzer/operations/operations.go @@ -31,7 +31,13 @@ func (o *Operations) IsValid(op string) bool { func (o *Operations) IsValidByType(op, fieldType string) bool { // * is a modifier and can be ignored for type validation. - fieldType, pointer := strings.CutPrefix(fieldType, "*") + normalized, pointer := strings.CutPrefix(fieldType, "*") + + // Scalar complex pointers have no condition-table row. Reject them here + // so analysis does not accept a tag that generation cannot emit. + if pointer && normalized == "" { + return false + } // Required can be used with all pointer types. if pointer && op == "required" { @@ -39,7 +45,7 @@ func (o *Operations) IsValidByType(op, fieldType string) bool { return true } - return slices.Contains(o.operations[op].ValidTypes, fieldType) + return slices.Contains(o.operations[op].ValidTypes, normalized) } func (o *Operations) IsFieldOperation(op string) bool { diff --git a/internal/analyzer/operations/operations_list.go b/internal/analyzer/operations/operations_list.go index 55f08a3..c565813 100644 --- a/internal/analyzer/operations/operations_list.go +++ b/internal/analyzer/operations/operations_list.go @@ -6,13 +6,13 @@ var operationsList = map[string]Operation{ "eq": { CountValues: common.OneValue, IsFieldOperation: false, - ValidTypes: []string{"", "", "", ""}, + ValidTypes: []string{"", "", "", "", ""}, }, "required": { CountValues: common.ZeroValue, IsFieldOperation: false, ValidTypes: []string{ - "", "", "", "", + "", "", "", "", "", "[]", "[]", "[]", "[]", "map[]", "map[]", "map[]", "map[]"}, }, @@ -68,7 +68,7 @@ var operationsList = map[string]Operation{ "neq": { CountValues: common.OneValue, IsFieldOperation: false, - ValidTypes: []string{"", "", "", ""}, + ValidTypes: []string{"", "", "", "", ""}, }, "neq_ignore_case": { CountValues: common.OneValue, @@ -79,7 +79,7 @@ var operationsList = map[string]Operation{ CountValues: common.ManyValues, IsFieldOperation: false, ValidTypes: []string{ - "", "", "", "", + "", "", "", "", "", "[]", "[]", "[]", "[]", "[N]", "[N]", "[N]", "[N]", "map[]", "map[]", "map[]", "map[]", @@ -89,7 +89,7 @@ var operationsList = map[string]Operation{ CountValues: common.ManyValues, IsFieldOperation: false, ValidTypes: []string{ - "", "", "", "", + "", "", "", "", "", "[]", "[]", "[]", "[]", "[N]", "[N]", "[N]", "[N]", "map[]", "map[]", "map[]", "map[]", diff --git a/internal/analyzer/operations/operations_test.go b/internal/analyzer/operations/operations_test.go index a10e742..c9b8606 100644 --- a/internal/analyzer/operations/operations_test.go +++ b/internal/analyzer/operations/operations_test.go @@ -59,7 +59,7 @@ func TestOperationsIsValidByType(t *testing.T) { { op: "eq", fieldTypes: []string{ - "", "", "", "", + "", "", "", "", "", "*", "*", "*", "*", }, valid: true, @@ -69,7 +69,7 @@ func TestOperationsIsValidByType(t *testing.T) { { op: "required", fieldTypes: []string{ - "", "", "", + "", "", "", "", "[]", "[]", "[]", "[]", "map[]", "map[]", "map[]", "map[]", "*", "*", "*", "*", @@ -172,7 +172,7 @@ func TestOperationsIsValidByType(t *testing.T) { { op: "neq", fieldTypes: []string{ - "", "", "", "", + "", "", "", "", "", "*", "*", "*", "*", }, valid: true, @@ -192,7 +192,7 @@ func TestOperationsIsValidByType(t *testing.T) { { op: "in", fieldTypes: []string{ - "", "", "", "", + "", "", "", "", "", "[]", "[]", "[]", "[]", "map[]", "map[]", "map[]", "map[]", "[N]", "[N]", "[N]", "[N]", @@ -208,7 +208,7 @@ func TestOperationsIsValidByType(t *testing.T) { { op: "nin", fieldTypes: []string{ - "", "", "", "", + "", "", "", "", "", "[]", "[]", "[]", "[]", "map[]", "map[]", "map[]", "map[]", "[N]", "[N]", "[N]", "[N]", @@ -235,7 +235,7 @@ func TestOperationsIsValidByType(t *testing.T) { op: "eqfield", fieldTypes: []string{ "", "", "", "", "", - "*", "*", "*", "*", "*", + "*", "*", "*", "*", }, valid: true, }, @@ -245,7 +245,7 @@ func TestOperationsIsValidByType(t *testing.T) { op: "neqfield", fieldTypes: []string{ "", "", "", "", "", - "*", "*", "*", "*", "*", + "*", "*", "*", "*", }, valid: true, }, @@ -306,15 +306,88 @@ func TestOperationsIsValidByType(t *testing.T) { { op: "gt", fieldTypes: []string{ - "", - "[]", "[]", "[]", "[]", + "", "", + "[]", "[]", "[]", "[]", "[]", "map[]", "map[]", "map[]", "map[]", - "*", + "*", "*", "*[]", "*[]", "*[]", "*[]", "*map[]", "*map[]", "*map[]", "*map[]", }, valid: false, }, + { + op: "gte", + fieldTypes: []string{ + "", "", "", + "*", "[]", + }, + valid: false, + }, + { + op: "lt", + fieldTypes: []string{ + "", "", "", + "*", "[]", + }, + valid: false, + }, + { + op: "lte", + fieldTypes: []string{ + "", "", "", + "*", "[]", + }, + valid: false, + }, + { + op: "eq", + fieldTypes: []string{ + "*", "[]", "[N]", "map[]", + }, + valid: false, + }, + { + op: "required", + fieldTypes: []string{ + "*", + }, + valid: false, + }, + { + op: "neq", + fieldTypes: []string{ + "*", "[]", "[N]", "map[]", + }, + valid: false, + }, + { + op: "in", + fieldTypes: []string{ + "*", "[]", "[N]", "map[]", + }, + valid: false, + }, + { + op: "nin", + fieldTypes: []string{ + "*", "[]", "[N]", "map[]", + }, + valid: false, + }, + { + op: "eqfield", + fieldTypes: []string{ + "*", + }, + valid: false, + }, + { + op: "neqfield", + fieldTypes: []string{ + "*", + }, + valid: false, + }, { op: "neq_ignore_case", fieldTypes: []string{"", "", "", "[]", "map[]", "", "*"}, diff --git a/internal/codegenerator/condition_table.go b/internal/codegenerator/condition_table.go index 0c5fed5..cfe5a38 100644 --- a/internal/codegenerator/condition_table.go +++ b/internal/codegenerator/condition_table.go @@ -38,7 +38,7 @@ var conditionTable = map[string]Operation{ }, }, { - AcceptedTypes: []string{"", "", ""}, + AcceptedTypes: []string{"", "", "", ""}, ConditionTable: ConditionTable{ operation: `obj.{{.Name}} == {{.Target}}`, concatOperator: "", @@ -74,7 +74,7 @@ var conditionTable = map[string]Operation{ }, }, { - AcceptedTypes: []string{"", ""}, + AcceptedTypes: []string{"", "", ""}, ConditionTable: ConditionTable{ operation: `obj.{{.Name}} != 0`, concatOperator: "", @@ -358,7 +358,7 @@ var conditionTable = map[string]Operation{ }, }, { - AcceptedTypes: []string{"", "", ""}, + AcceptedTypes: []string{"", "", "", ""}, ConditionTable: ConditionTable{ operation: `obj.{{.Name}} != {{.Target}}`, concatOperator: "", @@ -414,7 +414,7 @@ var conditionTable = map[string]Operation{ }, }, { - AcceptedTypes: []string{"", "", ""}, + AcceptedTypes: []string{"", "", "", ""}, ConditionTable: ConditionTable{ operation: `obj.{{.Name}} == {{.Target}}`, concatOperator: "||", @@ -546,7 +546,7 @@ var conditionTable = map[string]Operation{ }, }, { - AcceptedTypes: []string{"", "", ""}, + AcceptedTypes: []string{"", "", "", ""}, ConditionTable: ConditionTable{ operation: `obj.{{.Name}} != {{.Target}}`, concatOperator: "&&", diff --git a/internal/codegenerator/generated_function_code_no_pointer_test.go b/internal/codegenerator/generated_function_code_no_pointer_test.go index 60daf53..6a67768 100644 --- a/internal/codegenerator/generated_function_code_no_pointer_test.go +++ b/internal/codegenerator/generated_function_code_no_pointer_test.go @@ -140,6 +140,18 @@ return errs Tag: `validate:"required"`, }, + { + FieldName: "FieldRequiredComplex64", + Type: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + Tag: `validate:"required"`, + }, + + { + FieldName: "FieldRequiredComplex128", + Type: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + Tag: `validate:"required"`, + }, + { FieldName: "FieldRequiredStringSlice", Type: common.FieldType{ComposedType: "[]", BaseType: "string", Size: ""}, @@ -478,6 +490,14 @@ return errs { Validations: []*analyzer.Validation{AssertParserValidation(t, `required`)}, }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `required`)}, + }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `required`)}, + }, }, }, want: `func requiredStructValidate(obj *requiredStruct) []error { @@ -524,6 +544,12 @@ errs = append(errs, types.NewValidationError("FieldRequiredFloat64 is required") if !(obj.FieldRequiredBool != false) { errs = append(errs, types.NewValidationError("FieldRequiredBool is required")) } +if !(obj.FieldRequiredComplex64 != 0) { +errs = append(errs, types.NewValidationError("FieldRequiredComplex64 is required")) +} +if !(obj.FieldRequiredComplex128 != 0) { +errs = append(errs, types.NewValidationError("FieldRequiredComplex128 is required")) +} if !(len(obj.FieldRequiredStringSlice) != 0) { errs = append(errs, types.NewValidationError("FieldRequiredStringSlice must not be empty")) } @@ -703,6 +729,18 @@ return errs Type: common.FieldType{ComposedType: "", BaseType: "bool", Size: ""}, Tag: `validate:"eq=true"`, }, + + { + FieldName: "FieldEqComplex64", + Type: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + Tag: `validate:"eq=1+2i"`, + }, + + { + FieldName: "FieldEqComplex128", + Type: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + Tag: `validate:"eq=1+2i"`, + }, }, }, FieldsValidations: []analyzer.FieldValidations{ @@ -762,6 +800,14 @@ return errs { Validations: []*analyzer.Validation{AssertParserValidation(t, `eq=true`)}, }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `eq=1+2i`)}, + }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `eq=1+2i`)}, + }, }, }, want: `func eqStructValidate(obj *eqStruct) []error { @@ -808,6 +854,12 @@ errs = append(errs, types.NewValidationError("FieldEqFloat64 must be equal to 12 if !(obj.FieldEqBool == true) { errs = append(errs, types.NewValidationError("FieldEqBool must be equal to true")) } +if !(obj.FieldEqComplex64 == 1+2i) { +errs = append(errs, types.NewValidationError("FieldEqComplex64 must be equal to 1+2i")) +} +if !(obj.FieldEqComplex128 == 1+2i) { +errs = append(errs, types.NewValidationError("FieldEqComplex128 must be equal to 1+2i")) +} return errs } `, @@ -903,6 +955,18 @@ return errs Type: common.FieldType{ComposedType: "", BaseType: "bool", Size: ""}, Tag: `validate:"neq=true"`, }, + + { + FieldName: "FieldNeqComplex64", + Type: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + Tag: `validate:"neq=3+4i"`, + }, + + { + FieldName: "FieldNeqComplex128", + Type: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + Tag: `validate:"neq=3+4i"`, + }, }, }, FieldsValidations: []analyzer.FieldValidations{ @@ -962,6 +1026,14 @@ return errs { Validations: []*analyzer.Validation{AssertParserValidation(t, `neq=true`)}, }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `neq=3+4i`)}, + }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `neq=3+4i`)}, + }, }, }, want: `func neqStructValidate(obj *neqStruct) []error { @@ -1008,6 +1080,12 @@ errs = append(errs, types.NewValidationError("FieldNeqFloat64 must not be equal if !(obj.FieldNeqBool != true) { errs = append(errs, types.NewValidationError("FieldNeqBool must not be equal to true")) } +if !(obj.FieldNeqComplex64 != 3+4i) { +errs = append(errs, types.NewValidationError("FieldNeqComplex64 must not be equal to 3+4i")) +} +if !(obj.FieldNeqComplex128 != 3+4i) { +errs = append(errs, types.NewValidationError("FieldNeqComplex128 must not be equal to 3+4i")) +} return errs } `, @@ -3047,6 +3125,18 @@ return errs Tag: `validate:"in=true"`, }, + { + FieldName: "FieldInComplex64", + Type: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + Tag: `validate:"in=1+2i 5+6i"`, + }, + + { + FieldName: "FieldInComplex128", + Type: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + Tag: `validate:"in=1+2i 5+6i"`, + }, + { FieldName: "FieldInStringSlice", Type: common.FieldType{ComposedType: "[]", BaseType: "string", Size: ""}, @@ -3358,6 +3448,14 @@ return errs Validations: []*analyzer.Validation{AssertParserValidation(t, `in=true`)}, }, + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `in=1+2i 5+6i`)}, + }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `in=1+2i 5+6i`)}, + }, + { Validations: []*analyzer.Validation{AssertParserValidation(t, `in=ab cd ef`)}, }, @@ -3571,6 +3669,12 @@ errs = append(errs, types.NewValidationError("FieldInFloat64 must be one of '11. if !(obj.FieldInBool == true) { errs = append(errs, types.NewValidationError("FieldInBool must be one of 'true'")) } +if !(obj.FieldInComplex64 == 1+2i || obj.FieldInComplex64 == 5+6i) { +errs = append(errs, types.NewValidationError("FieldInComplex64 must be one of '1+2i' '5+6i'")) +} +if !(obj.FieldInComplex128 == 1+2i || obj.FieldInComplex128 == 5+6i) { +errs = append(errs, types.NewValidationError("FieldInComplex128 must be one of '1+2i' '5+6i'")) +} if !(types.SliceOnlyContains(obj.FieldInStringSlice, []string{"ab", "cd", "ef"})) { errs = append(errs, types.NewValidationError("FieldInStringSlice elements must be one of 'ab' 'cd' 'ef'")) } @@ -3793,6 +3897,18 @@ return errs Tag: `validate:"nin=true"`, }, + { + FieldName: "FieldNinComplex64", + Type: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + Tag: `validate:"nin=7+8i 9+0i"`, + }, + + { + FieldName: "FieldNinComplex128", + Type: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + Tag: `validate:"nin=7+8i 9+0i"`, + }, + { FieldName: "FieldNinStringSlice", Type: common.FieldType{ComposedType: "[]", BaseType: "string", Size: ""}, @@ -4104,6 +4220,14 @@ return errs Validations: []*analyzer.Validation{AssertParserValidation(t, `nin=true`)}, }, + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `nin=7+8i 9+0i`)}, + }, + + { + Validations: []*analyzer.Validation{AssertParserValidation(t, `nin=7+8i 9+0i`)}, + }, + { Validations: []*analyzer.Validation{AssertParserValidation(t, `nin=ab cd ef`)}, }, @@ -4317,6 +4441,12 @@ errs = append(errs, types.NewValidationError("FieldNinFloat64 must not be one of if !(obj.FieldNinBool != true) { errs = append(errs, types.NewValidationError("FieldNinBool must not be one of 'true'")) } +if !(obj.FieldNinComplex64 != 7+8i && obj.FieldNinComplex64 != 9+0i) { +errs = append(errs, types.NewValidationError("FieldNinComplex64 must not be one of '7+8i' '9+0i'")) +} +if !(obj.FieldNinComplex128 != 7+8i && obj.FieldNinComplex128 != 9+0i) { +errs = append(errs, types.NewValidationError("FieldNinComplex128 must not be one of '7+8i' '9+0i'")) +} if !(types.SliceNotContains(obj.FieldNinStringSlice, []string{"ab", "cd", "ef"})) { errs = append(errs, types.NewValidationError("FieldNinStringSlice elements must not be one of 'ab' 'cd' 'ef'")) } diff --git a/internal/codegenerator/generated_validation_code_no_pointer_test.go b/internal/codegenerator/generated_validation_code_no_pointer_test.go index b4e1407..0caba89 100644 --- a/internal/codegenerator/generated_validation_code_no_pointer_test.go +++ b/internal/codegenerator/generated_validation_code_no_pointer_test.go @@ -198,6 +198,30 @@ errs = append(errs, types.NewValidationError("FieldRequiredFloat64 is required") want: `if !(obj.FieldRequiredBool != false) { errs = append(errs, types.NewValidationError("FieldRequiredBool is required")) } +`, + }, + { + name: "required_complex64_required", + args: args{ + fieldName: "FieldRequiredComplex64", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + fieldValidation: "required", + }, + want: `if !(obj.FieldRequiredComplex64 != 0) { +errs = append(errs, types.NewValidationError("FieldRequiredComplex64 is required")) +} +`, + }, + { + name: "required_complex128_required", + args: args{ + fieldName: "FieldRequiredComplex128", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + fieldValidation: "required", + }, + want: `if !(obj.FieldRequiredComplex128 != 0) { +errs = append(errs, types.NewValidationError("FieldRequiredComplex128 is required")) +} `, }, { @@ -702,6 +726,30 @@ errs = append(errs, types.NewValidationError("FieldEqFloat64 must be equal to 12 want: `if !(obj.FieldEqBool == true) { errs = append(errs, types.NewValidationError("FieldEqBool must be equal to true")) } +`, + }, + { + name: "eq_complex64_eq=1+2i", + args: args{ + fieldName: "FieldEqComplex64", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + fieldValidation: "eq=1+2i", + }, + want: `if !(obj.FieldEqComplex64 == 1+2i) { +errs = append(errs, types.NewValidationError("FieldEqComplex64 must be equal to 1+2i")) +} +`, + }, + { + name: "eq_complex128_eq=1+2i", + args: args{ + fieldName: "FieldEqComplex128", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + fieldValidation: "eq=1+2i", + }, + want: `if !(obj.FieldEqComplex128 == 1+2i) { +errs = append(errs, types.NewValidationError("FieldEqComplex128 must be equal to 1+2i")) +} `, }, { @@ -870,6 +918,30 @@ errs = append(errs, types.NewValidationError("FieldNeqFloat64 must not be equal want: `if !(obj.FieldNeqBool != true) { errs = append(errs, types.NewValidationError("FieldNeqBool must not be equal to true")) } +`, + }, + { + name: "neq_complex64_neq=3+4i", + args: args{ + fieldName: "FieldNeqComplex64", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + fieldValidation: "neq=3+4i", + }, + want: `if !(obj.FieldNeqComplex64 != 3+4i) { +errs = append(errs, types.NewValidationError("FieldNeqComplex64 must not be equal to 3+4i")) +} +`, + }, + { + name: "neq_complex128_neq=3+4i", + args: args{ + fieldName: "FieldNeqComplex128", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + fieldValidation: "neq=3+4i", + }, + want: `if !(obj.FieldNeqComplex128 != 3+4i) { +errs = append(errs, types.NewValidationError("FieldNeqComplex128 must not be equal to 3+4i")) +} `, }, { @@ -2682,6 +2754,30 @@ errs = append(errs, types.NewValidationError("FieldInFloat64 must be one of '11. want: `if !(obj.FieldInBool == true) { errs = append(errs, types.NewValidationError("FieldInBool must be one of 'true'")) } +`, + }, + { + name: "in_complex64_in=1+2i 5+6i", + args: args{ + fieldName: "FieldInComplex64", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + fieldValidation: "in=1+2i 5+6i", + }, + want: `if !(obj.FieldInComplex64 == 1+2i || obj.FieldInComplex64 == 5+6i) { +errs = append(errs, types.NewValidationError("FieldInComplex64 must be one of '1+2i' '5+6i'")) +} +`, + }, + { + name: "in_complex128_in=1+2i 5+6i", + args: args{ + fieldName: "FieldInComplex128", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + fieldValidation: "in=1+2i 5+6i", + }, + want: `if !(obj.FieldInComplex128 == 1+2i || obj.FieldInComplex128 == 5+6i) { +errs = append(errs, types.NewValidationError("FieldInComplex128 must be one of '1+2i' '5+6i'")) +} `, }, { @@ -3354,6 +3450,30 @@ errs = append(errs, types.NewValidationError("FieldNinFloat64 must not be one of want: `if !(obj.FieldNinBool != true) { errs = append(errs, types.NewValidationError("FieldNinBool must not be one of 'true'")) } +`, + }, + { + name: "nin_complex64_nin=7+8i 9+0i", + args: args{ + fieldName: "FieldNinComplex64", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex64", Size: ""}, + fieldValidation: "nin=7+8i 9+0i", + }, + want: `if !(obj.FieldNinComplex64 != 7+8i && obj.FieldNinComplex64 != 9+0i) { +errs = append(errs, types.NewValidationError("FieldNinComplex64 must not be one of '7+8i' '9+0i'")) +} +`, + }, + { + name: "nin_complex128_nin=7+8i 9+0i", + args: args{ + fieldName: "FieldNinComplex128", + fieldType: common.FieldType{ComposedType: "", BaseType: "complex128", Size: ""}, + fieldValidation: "nin=7+8i 9+0i", + }, + want: `if !(obj.FieldNinComplex128 != 7+8i && obj.FieldNinComplex128 != 9+0i) { +errs = append(errs, types.NewValidationError("FieldNinComplex128 must not be one of '7+8i' '9+0i'")) +} `, }, { diff --git a/internal/codegenerator/get_test_elements_all_types_test.go b/internal/codegenerator/get_test_elements_all_types_test.go index 80d7bee..48a4410 100644 --- a/internal/codegenerator/get_test_elements_all_types_test.go +++ b/internal/codegenerator/get_test_elements_all_types_test.go @@ -32,7 +32,7 @@ func TestDefineTestElementsWithAllTypes(t *testing.T) { }, }, { - types: []string{"", ""}, + types: []string{"", "", ""}, want: TestElements{ conditions: []string{`obj.field != 0`}, concatOperator: "", @@ -144,6 +144,15 @@ func TestDefineTestElementsWithAllTypes(t *testing.T) { errorMessage: "field must be equal to 123.45", }, }, + { + types: []string{""}, + value: "1+2i", + want: TestElements{ + conditions: []string{`obj.field == 1+2i`}, + concatOperator: "", + errorMessage: "field must be equal to 1+2i", + }, + }, { types: []string{""}, value: "true", @@ -223,6 +232,15 @@ func TestDefineTestElementsWithAllTypes(t *testing.T) { errorMessage: "field must not be equal to 123.45", }, }, + { + types: []string{""}, + value: "3-4i", + want: TestElements{ + conditions: []string{`obj.field != 3-4i`}, + concatOperator: "", + errorMessage: "field must not be equal to 3-4i", + }, + }, { types: []string{""}, value: "true", @@ -653,6 +671,15 @@ func TestDefineTestElementsWithAllTypes(t *testing.T) { errorMessage: "field must be one of '1.23' '4.56' '7.89'", }, }, + { + types: []string{""}, + value: "1+2i,3-4i", + want: TestElements{ + conditions: []string{`obj.field == 1+2i`, `obj.field == 3-4i`}, + concatOperator: "||", + errorMessage: "field must be one of '1+2i' '3-4i'", + }, + }, { types: []string{""}, value: "true,false", @@ -912,6 +939,15 @@ func TestDefineTestElementsWithAllTypes(t *testing.T) { errorMessage: "field must not be one of '1.23' '4.56' '7.89'", }, }, + { + types: []string{""}, + value: "7+8i,9+0i", + want: TestElements{ + conditions: []string{`obj.field != 7+8i`, `obj.field != 9+0i`}, + concatOperator: "&&", + errorMessage: "field must not be one of '7+8i' '9+0i'", + }, + }, { types: []string{""}, value: "true,false", diff --git a/internal/codegenerator/get_test_elements_errors_test.go b/internal/codegenerator/get_test_elements_errors_test.go index 5af40d4..74c2d84 100644 --- a/internal/codegenerator/get_test_elements_errors_test.go +++ b/internal/codegenerator/get_test_elements_errors_test.go @@ -46,6 +46,42 @@ func TestDefineTestElementsWithInvalidOperations(t *testing.T) { }, expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation gtfield type map[] (float64)"), }, + { + name: "unsupported gt on complex64", + args: args{ + fieldName: "value", + fieldType: common.FieldType{BaseType: "complex64"}, + fieldValidation: "gt=1+2i", + }, + expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation gt type (complex64)"), + }, + { + name: "unsupported gte on complex128", + args: args{ + fieldName: "value", + fieldType: common.FieldType{BaseType: "complex128"}, + fieldValidation: "gte=1+2i", + }, + expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation gte type (complex128)"), + }, + { + name: "unsupported lt on complex64", + args: args{ + fieldName: "value", + fieldType: common.FieldType{BaseType: "complex64"}, + fieldValidation: "lt=1+2i", + }, + expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation lt type (complex64)"), + }, + { + name: "unsupported lte on complex128", + args: args{ + fieldName: "value", + fieldType: common.FieldType{BaseType: "complex128"}, + fieldValidation: "lte=1+2i", + }, + expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation lte type (complex128)"), + }, { name: "unsupported gtfield on complex128", args: args{ @@ -55,6 +91,24 @@ func TestDefineTestElementsWithInvalidOperations(t *testing.T) { }, expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation gtfield type (complex128)"), }, + { + name: "unsupported eq on complex pointer", + args: args{ + fieldName: "value", + fieldType: common.FieldType{ComposedType: "*", BaseType: "complex64"}, + fieldValidation: "eq=1+2i", + }, + expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation eq type * (complex64)"), + }, + { + name: "unsupported eq on complex slice", + args: args{ + fieldName: "value", + fieldType: common.FieldType{ComposedType: "[]", BaseType: "complex128"}, + fieldValidation: "eq=1+2i", + }, + expectedErr: types.NewValidationError("INTERNAL ERROR: unsupported operation eq type [] (complex128)"), + }, { name: "unsupported eqfield on complex slice", args: args{ diff --git a/testgen/README.md b/testgen/README.md index dad1bf9..dfc798a 100644 --- a/testgen/README.md +++ b/testgen/README.md @@ -12,8 +12,8 @@ ValidGen currently supports 21 validations across multiple data types: | Validation | Basic types | Slice | Array | Map | | - | - | - | - | - | -| eq | STRING INT FLOAT BOOL | | | | -| required | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | | STRING INT FLOAT BOOL | +| eq | STRING INT FLOAT COMPLEX BOOL | | | | +| required | STRING INT FLOAT COMPLEX BOOL | STRING INT FLOAT BOOL | | STRING INT FLOAT BOOL | | gt | INT FLOAT | | | | | gte | INT FLOAT | | | | | lte | INT FLOAT | | | | @@ -22,10 +22,10 @@ ValidGen currently supports 21 validations across multiple data types: | max | STRING | STRING INT FLOAT BOOL | | STRING INT FLOAT BOOL | | eq_ignore_case | STRING | | | | | len | STRING | STRING INT FLOAT BOOL | | STRING INT FLOAT BOOL | -| neq | STRING INT FLOAT BOOL | | | | +| neq | STRING INT FLOAT COMPLEX BOOL | | | | | neq_ignore_case | STRING | | | | -| in | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | -| nin | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | +| in | STRING INT FLOAT COMPLEX BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | +| nin | STRING INT FLOAT COMPLEX BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | STRING INT FLOAT BOOL | | email | STRING | | | | | eqfield | STRING INT FLOAT COMPLEX BOOL | | | | | neqfield | STRING INT FLOAT COMPLEX BOOL | | | | @@ -39,7 +39,7 @@ In this table: - **BOOL** represents the `bool` Go type - **INT** represents all ten integer Go types: `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64` - **FLOAT** represents both float Go types: `float32`, `float64` -- **COMPLEX** represents both complex Go types: `complex64`, `complex128` (equality only; no ordering field tags) +- **COMPLEX** represents both complex Go types: `complex64`, `complex128`. Scalar `eq`, `neq`, `in`, `nin`, and `required` are generated for non-pointer values. Ordering tags are rejected. Field equality stays hand-written. For slices, arrays, and maps, the same type expansion applies. For example, slice STRING is `[]string`, while slice INT expands to all integer Go types. @@ -81,9 +81,9 @@ However, these generators lacked a common configuration, didn't implement all te - Unit tests for `BuildValidationCode`, in `internal/codegenerator/generated_validation_code_*`. - Unit tests for the generated validator function, in `internal/codegenerator/generated_function_code_*`. -A case with both inputs emits the valid input and the invalid input. Array `required` cases set `excludeIf` to `noPointer` because a non-pointer Go array cannot be empty. +A case with both inputs emits the valid input and the invalid input. Array `required` cases set `excludeIf` to `noPointer` because a non-pointer Go array cannot be empty. Scalar complex cases set `skipPointer` because pointer complex values are outside this generator. -`go test ./testgen` checks that list. `TestTypesValidationListsNonFieldOperations` requires one entry for each operation below, with `isFieldValidation` false, the same argument count as `operations.New()`, and a case for every type `IsValidByType` accepts. `TestTypesValidationCasesBuildValidationCode` calls `BuildValidationCode` for each concrete type, including the pointer form, and checks that the generated error text contains the catalog message. +`go test ./testgen` checks that list. `TestTypesValidationListsNonFieldOperations` requires one entry for each operation below, with `isFieldValidation` false, the same argument count as `operations.New()`, and a case for every type `IsValidByType` accepts. `TestTypesValidationCasesBuildValidationCode` calls `BuildValidationCode` for each concrete type, including the pointer form, and checks that the generated error text contains the catalog message. Cases marked `skipPointer` are checked only as non-pointers. | Operation | Generated | | - | - | @@ -103,7 +103,7 @@ These groups already have tests beside the code they check. A generator that rea - Parser checks in `internal/parser/parser_test.go`. They compare parsed structs with source text. - Examples under `_examples/`. -Field-operation rows in the four generated suites wait on integer field operations in issue #78. Complex ordering and `dive` in issue #7 are separate work. +Field-operation rows in the four generated suites wait on integer field operations in issue #78. Complex ordering tags stay rejected, and `dive` in issue #7 is separate work. ## Usage diff --git a/testgen/coverage_test.go b/testgen/coverage_test.go index 164b07c..f269f6c 100644 --- a/testgen/coverage_test.go +++ b/testgen/coverage_test.go @@ -123,6 +123,9 @@ func TestTypesValidationCasesBuildValidationCode(t *testing.T) { if tc.excludeIf&noPointer != 0 && !pointer { continue } + if pointer && tc.excludeIf&skipPointer != 0 { + continue + } normalizedType := tc.typeClass if pointer { diff --git a/testgen/generate_cmp_perf_tests.go b/testgen/generate_cmp_perf_tests.go index ee64ea5..4326ec3 100644 --- a/testgen/generate_cmp_perf_tests.go +++ b/testgen/generate_cmp_perf_tests.go @@ -54,6 +54,10 @@ func generateComparativePerformanceTest(tplFile, outputFile string, pointer bool fmt.Printf("Skipping no pointer: tag %s type %s\n", typeVal.tag, testCase.typeClass) continue } + if pointer && testCase.excludeIf&skipPointer != 0 { + fmt.Printf("Skipping pointer: tag %s type %s\n", typeVal.tag, testCase.typeClass) + continue + } normalizedType := testCase.typeClass if pointer { diff --git a/testgen/generate_function_code_tests.go b/testgen/generate_function_code_tests.go index cf6b584..8212728 100644 --- a/testgen/generate_function_code_tests.go +++ b/testgen/generate_function_code_tests.go @@ -71,6 +71,10 @@ func generateFunctionCodeUnitTest(tplFile, outputFile string, pointer bool) erro fmt.Printf("Skipping no pointer: tag %s type %s\n", typeValidation.tag, toGenerate.typeClass) continue } + if pointer && toGenerate.excludeIf&skipPointer != 0 { + fmt.Printf("Skipping pointer: tag %s type %s\n", typeValidation.tag, toGenerate.typeClass) + continue + } normalizedType := toGenerate.typeClass if pointer { diff --git a/testgen/generate_validation_code_tests.go b/testgen/generate_validation_code_tests.go index 5b02e4a..aabe98a 100644 --- a/testgen/generate_validation_code_tests.go +++ b/testgen/generate_validation_code_tests.go @@ -53,6 +53,10 @@ func generateValidationCodeUnitTest(tplFile, outputFile string, pointer bool) er fmt.Printf("Skipping no pointer: tag %s type %s\n", typeValidation.tag, toGenerate.typeClass) continue } + if pointer && toGenerate.excludeIf&skipPointer != 0 { + fmt.Printf("Skipping pointer: tag %s type %s\n", typeValidation.tag, toGenerate.typeClass) + continue + } normalizedType := toGenerate.typeClass if pointer { diff --git a/testgen/generate_validation_types_tests.go b/testgen/generate_validation_types_tests.go index 0b3ebd6..cf2cfad 100644 --- a/testgen/generate_validation_types_tests.go +++ b/testgen/generate_validation_types_tests.go @@ -58,6 +58,10 @@ func generateValidationTypesEndToEndTest(tplFile, outputFile string, pointer boo fmt.Printf("Skipping no pointer: tag %s type %s\n", testCase.tag, toGenerate.typeClass) continue } + if pointer && toGenerate.excludeIf&skipPointer != 0 { + fmt.Printf("Skipping pointer: tag %s type %s\n", testCase.tag, toGenerate.typeClass) + continue + } normalizedType := toGenerate.typeClass if pointer { diff --git a/testgen/validations.go b/testgen/validations.go index babf8c6..1efd8b3 100644 --- a/testgen/validations.go +++ b/testgen/validations.go @@ -7,6 +7,9 @@ type excludeIf uint32 const ( cmpBenchTests excludeIf = 1 << iota noPointer + // skipPointer keeps a scalar case out of pointer generation. + // Complex pointers are not in this issue's scope. + skipPointer ) type typeValidation struct { @@ -79,6 +82,14 @@ var typesValidation = []struct { invalidCase: `false`, errorMessage: `{{.FieldName}} is required`, }, + { + typeClass: ``, + validation: ``, + validCase: `1+2i`, + invalidCase: `0`, + errorMessage: `{{.FieldName}} is required`, + excludeIf: skipPointer | cmpBenchTests, + }, // required: "[]", "[]", "[]", "[]" { @@ -212,6 +223,14 @@ var typesValidation = []struct { invalidCase: `false`, errorMessage: `{{.FieldName}} must be equal to {{.Target}}`, }, + { + typeClass: ``, + validation: `1+2i`, + validCase: `1+2i`, + invalidCase: `3+4i`, + errorMessage: `{{.FieldName}} must be equal to {{.Target}}`, + excludeIf: skipPointer | cmpBenchTests, + }, }, }, @@ -251,6 +270,14 @@ var typesValidation = []struct { invalidCase: `true`, errorMessage: `{{.FieldName}} must not be equal to {{.Target}}`, }, + { + typeClass: ``, + validation: `3+4i`, + validCase: `1+2i`, + invalidCase: `3+4i`, + errorMessage: `{{.FieldName}} must not be equal to {{.Target}}`, + excludeIf: skipPointer | cmpBenchTests, + }, }, }, @@ -662,6 +689,14 @@ var typesValidation = []struct { errorMessage: `{{.FieldName}} must be one of {{.Targets}}`, excludeIf: cmpBenchTests, }, + { + typeClass: ``, + validation: `1+2i 5+6i`, + validCase: `5+6i`, + invalidCase: `7+8i`, + errorMessage: `{{.FieldName}} must be one of {{.Targets}}`, + excludeIf: skipPointer | cmpBenchTests, + }, // in: "[]", "[]", "[]", "[]" { @@ -803,6 +838,14 @@ var typesValidation = []struct { invalidCase: `true`, errorMessage: `{{.FieldName}} must not be one of {{.Targets}}`, }, + { + typeClass: ``, + validation: `7+8i 9+0i`, + validCase: `1+2i`, + invalidCase: `9+0i`, + errorMessage: `{{.FieldName}} must not be one of {{.Targets}}`, + excludeIf: skipPointer | cmpBenchTests, + }, // nin: "[]", "[]", "[]", "[]" { diff --git a/tests/endtoend/complex.go b/tests/endtoend/complex.go new file mode 100644 index 0000000..65055e6 --- /dev/null +++ b/tests/endtoend/complex.go @@ -0,0 +1,97 @@ +package main + +import "log" + +type Complex64Validation struct { + Required complex64 `valid:"required"` + Eq complex64 `valid:"eq=1+2i"` + Neq complex64 `valid:"neq=3+4i"` + In complex64 `valid:"in=1+2i 5+6i"` + Nin complex64 `valid:"nin=7+8i 9+0i"` +} + +type Complex128Validation struct { + Required complex128 `valid:"required"` + Eq complex128 `valid:"eq=1+2i"` + Neq complex128 `valid:"neq=3+4i"` + In complex128 `valid:"in=1+2i 5+6i"` + Nin complex128 `valid:"nin=7+8i 9+0i"` +} + +func complexTests() { + log.Println("starting complex64 tests") + complex64Tests() + log.Println("complex64 tests ok") + + log.Println("starting complex128 tests") + complex128Tests() + log.Println("complex128 tests ok") +} + +func complex64Tests() { + invalid := &Complex64Validation{ + Required: 0, + Eq: 1 + 3i, + Neq: 3 + 4i, + In: 1 + 6i, + Nin: 7 + 8i, + } + wantInvalid := []string{ + "Required is required", + "Eq must be equal to 1+2i", + "Neq must not be equal to 3+4i", + "In must be one of '1+2i' '5+6i'", + "Nin must not be one of '7+8i' '9+0i'", + } + errs := Complex64ValidationValidate(invalid) + if !expectedMsgErrorsOk(errs, wantInvalid) { + log.Fatalf("error = %v, wantErr %v", errs, wantInvalid) + } + + // Nonzero with a zero real part, so required is not a real-part check. + valid := &Complex64Validation{ + Required: 0 + 1i, + Eq: 1 + 2i, + Neq: 3 + 5i, + In: 5 + 6i, + Nin: 1 + 0i, + } + errs = Complex64ValidationValidate(valid) + if !expectedMsgErrorsOk(errs, nil) { + log.Fatalf("error = %v, wantErr %v", errs, nil) + } +} + +func complex128Tests() { + invalid := &Complex128Validation{ + Required: 0, + Eq: 9 + 2i, + Neq: 3 + 4i, + In: 0, + Nin: 9 + 0i, + } + wantInvalid := []string{ + "Required is required", + "Eq must be equal to 1+2i", + "Neq must not be equal to 3+4i", + "In must be one of '1+2i' '5+6i'", + "Nin must not be one of '7+8i' '9+0i'", + } + errs := Complex128ValidationValidate(invalid) + if !expectedMsgErrorsOk(errs, wantInvalid) { + log.Fatalf("error = %v, wantErr %v", errs, wantInvalid) + } + + // Nonzero with a zero imaginary part. + valid := &Complex128Validation{ + Required: 1 + 0i, + Eq: 1 + 2i, + Neq: 0, + In: 1 + 2i, + Nin: 2 + 2i, + } + errs = Complex128ValidationValidate(valid) + if !expectedMsgErrorsOk(errs, nil) { + log.Fatalf("error = %v, wantErr %v", errs, nil) + } +} diff --git a/tests/endtoend/generated_endtoend_no_pointer_tests.go b/tests/endtoend/generated_endtoend_no_pointer_tests.go index 30d6531..2310a31 100644 --- a/tests/endtoend/generated_endtoend_no_pointer_tests.go +++ b/tests/endtoend/generated_endtoend_no_pointer_tests.go @@ -72,6 +72,8 @@ type requiredStructFields struct { FieldRequiredFloat32 float32 `valid:"required"` FieldRequiredFloat64 float64 `valid:"required"` FieldRequiredBool bool `valid:"required"` + FieldRequiredComplex64 complex64 `valid:"required"` + FieldRequiredComplex128 complex128 `valid:"required"` FieldRequiredStringSlice []string `valid:"required"` FieldRequiredIntSlice []int `valid:"required"` FieldRequiredInt8Slice []int8 `valid:"required"` @@ -125,6 +127,8 @@ func requiredStructFieldsTests() { "FieldRequiredFloat32 is required", "FieldRequiredFloat64 is required", "FieldRequiredBool is required", + "FieldRequiredComplex64 is required", + "FieldRequiredComplex128 is required", "FieldRequiredStringSlice must not be empty", "FieldRequiredIntSlice must not be empty", "FieldRequiredInt8Slice must not be empty", @@ -169,6 +173,8 @@ func requiredStructFieldsTests() { v.FieldRequiredFloat32 = 0 v.FieldRequiredFloat64 = 0 v.FieldRequiredBool = false + v.FieldRequiredComplex64 = 0 + v.FieldRequiredComplex128 = 0 v.FieldRequiredStringSlice = []string{} v.FieldRequiredIntSlice = []int{} v.FieldRequiredInt8Slice = []int8{} @@ -217,6 +223,8 @@ func requiredStructFieldsTests() { v.FieldRequiredFloat32 = 12.34 v.FieldRequiredFloat64 = 12.34 v.FieldRequiredBool = true + v.FieldRequiredComplex64 = 1 + 2i + v.FieldRequiredComplex128 = 1 + 2i v.FieldRequiredStringSlice = []string{"abcde"} v.FieldRequiredIntSlice = []int{32} v.FieldRequiredInt8Slice = []int8{32} @@ -254,20 +262,22 @@ func requiredStructFieldsTests() { } type eqStructFields struct { - FieldEqString string `valid:"eq=abcde"` - FieldEqInt int `valid:"eq=32"` - FieldEqInt8 int8 `valid:"eq=32"` - FieldEqInt16 int16 `valid:"eq=32"` - FieldEqInt32 int32 `valid:"eq=32"` - FieldEqInt64 int64 `valid:"eq=32"` - FieldEqUint uint `valid:"eq=32"` - FieldEqUint8 uint8 `valid:"eq=32"` - FieldEqUint16 uint16 `valid:"eq=32"` - FieldEqUint32 uint32 `valid:"eq=32"` - FieldEqUint64 uint64 `valid:"eq=32"` - FieldEqFloat32 float32 `valid:"eq=12.34"` - FieldEqFloat64 float64 `valid:"eq=12.34"` - FieldEqBool bool `valid:"eq=true"` + FieldEqString string `valid:"eq=abcde"` + FieldEqInt int `valid:"eq=32"` + FieldEqInt8 int8 `valid:"eq=32"` + FieldEqInt16 int16 `valid:"eq=32"` + FieldEqInt32 int32 `valid:"eq=32"` + FieldEqInt64 int64 `valid:"eq=32"` + FieldEqUint uint `valid:"eq=32"` + FieldEqUint8 uint8 `valid:"eq=32"` + FieldEqUint16 uint16 `valid:"eq=32"` + FieldEqUint32 uint32 `valid:"eq=32"` + FieldEqUint64 uint64 `valid:"eq=32"` + FieldEqFloat32 float32 `valid:"eq=12.34"` + FieldEqFloat64 float64 `valid:"eq=12.34"` + FieldEqBool bool `valid:"eq=true"` + FieldEqComplex64 complex64 `valid:"eq=1+2i"` + FieldEqComplex128 complex128 `valid:"eq=1+2i"` } func eqStructFieldsTests() { @@ -293,6 +303,8 @@ func eqStructFieldsTests() { "FieldEqFloat32 must be equal to 12.34", "FieldEqFloat64 must be equal to 12.34", "FieldEqBool must be equal to true", + "FieldEqComplex64 must be equal to 1+2i", + "FieldEqComplex128 must be equal to 1+2i", } v.FieldEqString = "fghij" @@ -309,6 +321,8 @@ func eqStructFieldsTests() { v.FieldEqFloat32 = 34.56 v.FieldEqFloat64 = 34.56 v.FieldEqBool = false + v.FieldEqComplex64 = 3 + 4i + v.FieldEqComplex128 = 3 + 4i errs = eqStructFieldsValidate(v) assertExpectedErrorMsgs("testcase 1", errs, expectedMsgErrors) @@ -329,6 +343,8 @@ func eqStructFieldsTests() { v.FieldEqFloat32 = 12.34 v.FieldEqFloat64 = 12.34 v.FieldEqBool = true + v.FieldEqComplex64 = 1 + 2i + v.FieldEqComplex128 = 1 + 2i expectedMsgErrors = nil errs = eqStructFieldsValidate(v) @@ -338,20 +354,22 @@ func eqStructFieldsTests() { } type neqStructFields struct { - FieldNeqString string `valid:"neq=abcde"` - FieldNeqInt int `valid:"neq=32"` - FieldNeqInt8 int8 `valid:"neq=32"` - FieldNeqInt16 int16 `valid:"neq=32"` - FieldNeqInt32 int32 `valid:"neq=32"` - FieldNeqInt64 int64 `valid:"neq=32"` - FieldNeqUint uint `valid:"neq=32"` - FieldNeqUint8 uint8 `valid:"neq=32"` - FieldNeqUint16 uint16 `valid:"neq=32"` - FieldNeqUint32 uint32 `valid:"neq=32"` - FieldNeqUint64 uint64 `valid:"neq=32"` - FieldNeqFloat32 float32 `valid:"neq=12.34"` - FieldNeqFloat64 float64 `valid:"neq=12.34"` - FieldNeqBool bool `valid:"neq=true"` + FieldNeqString string `valid:"neq=abcde"` + FieldNeqInt int `valid:"neq=32"` + FieldNeqInt8 int8 `valid:"neq=32"` + FieldNeqInt16 int16 `valid:"neq=32"` + FieldNeqInt32 int32 `valid:"neq=32"` + FieldNeqInt64 int64 `valid:"neq=32"` + FieldNeqUint uint `valid:"neq=32"` + FieldNeqUint8 uint8 `valid:"neq=32"` + FieldNeqUint16 uint16 `valid:"neq=32"` + FieldNeqUint32 uint32 `valid:"neq=32"` + FieldNeqUint64 uint64 `valid:"neq=32"` + FieldNeqFloat32 float32 `valid:"neq=12.34"` + FieldNeqFloat64 float64 `valid:"neq=12.34"` + FieldNeqBool bool `valid:"neq=true"` + FieldNeqComplex64 complex64 `valid:"neq=3+4i"` + FieldNeqComplex128 complex128 `valid:"neq=3+4i"` } func neqStructFieldsTests() { @@ -377,6 +395,8 @@ func neqStructFieldsTests() { "FieldNeqFloat32 must not be equal to 12.34", "FieldNeqFloat64 must not be equal to 12.34", "FieldNeqBool must not be equal to true", + "FieldNeqComplex64 must not be equal to 3+4i", + "FieldNeqComplex128 must not be equal to 3+4i", } v.FieldNeqString = "abcde" @@ -393,6 +413,8 @@ func neqStructFieldsTests() { v.FieldNeqFloat32 = 12.34 v.FieldNeqFloat64 = 12.34 v.FieldNeqBool = true + v.FieldNeqComplex64 = 3 + 4i + v.FieldNeqComplex128 = 3 + 4i errs = neqStructFieldsValidate(v) assertExpectedErrorMsgs("testcase 1", errs, expectedMsgErrors) @@ -413,6 +435,8 @@ func neqStructFieldsTests() { v.FieldNeqFloat32 = 34.56 v.FieldNeqFloat64 = 34.56 v.FieldNeqBool = false + v.FieldNeqComplex64 = 1 + 2i + v.FieldNeqComplex128 = 1 + 2i expectedMsgErrors = nil errs = neqStructFieldsValidate(v) @@ -1236,6 +1260,8 @@ type inStructFields struct { FieldInFloat32 float32 `valid:"in=11.11 22.22 33.33"` FieldInFloat64 float64 `valid:"in=11.11 22.22 33.33"` FieldInBool bool `valid:"in=true"` + FieldInComplex64 complex64 `valid:"in=1+2i 5+6i"` + FieldInComplex128 complex128 `valid:"in=1+2i 5+6i"` FieldInStringSlice []string `valid:"in=ab cd ef"` FieldInIntSlice []int `valid:"in=12 34 56"` FieldInInt8Slice []int8 `valid:"in=12 34 56"` @@ -1303,6 +1329,8 @@ func inStructFieldsTests() { "FieldInFloat32 must be one of '11.11' '22.22' '33.33'", "FieldInFloat64 must be one of '11.11' '22.22' '33.33'", "FieldInBool must be one of 'true'", + "FieldInComplex64 must be one of '1+2i' '5+6i'", + "FieldInComplex128 must be one of '1+2i' '5+6i'", "FieldInStringSlice elements must be one of 'ab' 'cd' 'ef'", "FieldInIntSlice elements must be one of '12' '34' '56'", "FieldInInt8Slice elements must be one of '12' '34' '56'", @@ -1361,6 +1389,8 @@ func inStructFieldsTests() { v.FieldInFloat32 = 44.44 v.FieldInFloat64 = 44.44 v.FieldInBool = false + v.FieldInComplex64 = 7 + 8i + v.FieldInComplex128 = 7 + 8i v.FieldInStringSlice = []string{"ab", "gh", "ef"} v.FieldInIntSlice = []int{12, 78, 56} v.FieldInInt8Slice = []int8{12, 78, 56} @@ -1423,6 +1453,8 @@ func inStructFieldsTests() { v.FieldInFloat32 = 22.22 v.FieldInFloat64 = 22.22 v.FieldInBool = true + v.FieldInComplex64 = 5 + 6i + v.FieldInComplex128 = 5 + 6i v.FieldInStringSlice = []string{"ab", "ef"} v.FieldInIntSlice = []int{12, 56} v.FieldInInt8Slice = []int8{12, 56} @@ -1488,6 +1520,8 @@ type ninStructFields struct { FieldNinFloat32 float32 `valid:"nin=11.11 22.22 33.33"` FieldNinFloat64 float64 `valid:"nin=11.11 22.22 33.33"` FieldNinBool bool `valid:"nin=true"` + FieldNinComplex64 complex64 `valid:"nin=7+8i 9+0i"` + FieldNinComplex128 complex128 `valid:"nin=7+8i 9+0i"` FieldNinStringSlice []string `valid:"nin=ab cd ef"` FieldNinIntSlice []int `valid:"nin=12 34 56"` FieldNinInt8Slice []int8 `valid:"nin=12 34 56"` @@ -1555,6 +1589,8 @@ func ninStructFieldsTests() { "FieldNinFloat32 must not be one of '11.11' '22.22' '33.33'", "FieldNinFloat64 must not be one of '11.11' '22.22' '33.33'", "FieldNinBool must not be one of 'true'", + "FieldNinComplex64 must not be one of '7+8i' '9+0i'", + "FieldNinComplex128 must not be one of '7+8i' '9+0i'", "FieldNinStringSlice elements must not be one of 'ab' 'cd' 'ef'", "FieldNinIntSlice elements must not be one of '12' '34' '56'", "FieldNinInt8Slice elements must not be one of '12' '34' '56'", @@ -1613,6 +1649,8 @@ func ninStructFieldsTests() { v.FieldNinFloat32 = 22.22 v.FieldNinFloat64 = 22.22 v.FieldNinBool = true + v.FieldNinComplex64 = 9 + 0i + v.FieldNinComplex128 = 9 + 0i v.FieldNinStringSlice = []string{"ab", "ef"} v.FieldNinIntSlice = []int{12, 78, 56} v.FieldNinInt8Slice = []int8{12, 78, 56} @@ -1675,6 +1713,8 @@ func ninStructFieldsTests() { v.FieldNinFloat32 = 44.44 v.FieldNinFloat64 = 44.44 v.FieldNinBool = false + v.FieldNinComplex64 = 1 + 2i + v.FieldNinComplex128 = 1 + 2i v.FieldNinStringSlice = []string{"gh", "ij", "kl"} v.FieldNinIntSlice = []int{78, 91} v.FieldNinInt8Slice = []int8{78, 91} diff --git a/tests/endtoend/main.go b/tests/endtoend/main.go index 1316043..a11907e 100644 --- a/tests/endtoend/main.go +++ b/tests/endtoend/main.go @@ -39,6 +39,7 @@ func main() { cmpBetweenInnerFieldsTests() cmpBetweenNestedFieldsTests() boolTests() + complexTests() pointerTests() noPointerTests() diff --git a/tests/endtoend/validator__.go b/tests/endtoend/validator__.go index 8174ddd..e854a3d 100755 --- a/tests/endtoend/validator__.go +++ b/tests/endtoend/validator__.go @@ -273,6 +273,44 @@ func CmpNestedUint8FieldsValidate(obj *CmpNestedUint8Fields) []error { } return errs } +func Complex128ValidationValidate(obj *Complex128Validation) []error { + var errs []error + if !(obj.Required != 0) { + errs = append(errs, types.NewValidationError("Required is required")) + } + if !(obj.Eq == 1+2i) { + errs = append(errs, types.NewValidationError("Eq must be equal to 1+2i")) + } + if !(obj.Neq != 3+4i) { + errs = append(errs, types.NewValidationError("Neq must not be equal to 3+4i")) + } + if !(obj.In == 1+2i || obj.In == 5+6i) { + errs = append(errs, types.NewValidationError("In must be one of '1+2i' '5+6i'")) + } + if !(obj.Nin != 7+8i && obj.Nin != 9+0i) { + errs = append(errs, types.NewValidationError("Nin must not be one of '7+8i' '9+0i'")) + } + return errs +} +func Complex64ValidationValidate(obj *Complex64Validation) []error { + var errs []error + if !(obj.Required != 0) { + errs = append(errs, types.NewValidationError("Required is required")) + } + if !(obj.Eq == 1+2i) { + errs = append(errs, types.NewValidationError("Eq must be equal to 1+2i")) + } + if !(obj.Neq != 3+4i) { + errs = append(errs, types.NewValidationError("Neq must not be equal to 3+4i")) + } + if !(obj.In == 1+2i || obj.In == 5+6i) { + errs = append(errs, types.NewValidationError("In must be one of '1+2i' '5+6i'")) + } + if !(obj.Nin != 7+8i && obj.Nin != 9+0i) { + errs = append(errs, types.NewValidationError("Nin must not be one of '7+8i' '9+0i'")) + } + return errs +} func DiveUserValidate(obj *DiveUser) []error { var errs []error if !(len(obj.Addresses) != 0) { @@ -396,6 +434,12 @@ func eqStructFieldsValidate(obj *eqStructFields) []error { if !(obj.FieldEqBool == true) { errs = append(errs, types.NewValidationError("FieldEqBool must be equal to true")) } + if !(obj.FieldEqComplex64 == 1+2i) { + errs = append(errs, types.NewValidationError("FieldEqComplex64 must be equal to 1+2i")) + } + if !(obj.FieldEqComplex128 == 1+2i) { + errs = append(errs, types.NewValidationError("FieldEqComplex128 must be equal to 1+2i")) + } return errs } func eqStructFieldsPointerValidate(obj *eqStructFieldsPointer) []error { @@ -662,6 +706,12 @@ func inStructFieldsValidate(obj *inStructFields) []error { if !(obj.FieldInBool == true) { errs = append(errs, types.NewValidationError("FieldInBool must be one of 'true'")) } + if !(obj.FieldInComplex64 == 1+2i || obj.FieldInComplex64 == 5+6i) { + errs = append(errs, types.NewValidationError("FieldInComplex64 must be one of '1+2i' '5+6i'")) + } + if !(obj.FieldInComplex128 == 1+2i || obj.FieldInComplex128 == 5+6i) { + errs = append(errs, types.NewValidationError("FieldInComplex128 must be one of '1+2i' '5+6i'")) + } if !(types.SliceOnlyContains(obj.FieldInStringSlice, []string{"ab", "cd", "ef"})) { errs = append(errs, types.NewValidationError("FieldInStringSlice elements must be one of 'ab' 'cd' 'ef'")) } @@ -1712,6 +1762,12 @@ func neqStructFieldsValidate(obj *neqStructFields) []error { if !(obj.FieldNeqBool != true) { errs = append(errs, types.NewValidationError("FieldNeqBool must not be equal to true")) } + if !(obj.FieldNeqComplex64 != 3+4i) { + errs = append(errs, types.NewValidationError("FieldNeqComplex64 must not be equal to 3+4i")) + } + if !(obj.FieldNeqComplex128 != 3+4i) { + errs = append(errs, types.NewValidationError("FieldNeqComplex128 must not be equal to 3+4i")) + } return errs } func neqStructFieldsPointerValidate(obj *neqStructFieldsPointer) []error { @@ -1818,6 +1874,12 @@ func ninStructFieldsValidate(obj *ninStructFields) []error { if !(obj.FieldNinBool != true) { errs = append(errs, types.NewValidationError("FieldNinBool must not be one of 'true'")) } + if !(obj.FieldNinComplex64 != 7+8i && obj.FieldNinComplex64 != 9+0i) { + errs = append(errs, types.NewValidationError("FieldNinComplex64 must not be one of '7+8i' '9+0i'")) + } + if !(obj.FieldNinComplex128 != 7+8i && obj.FieldNinComplex128 != 9+0i) { + errs = append(errs, types.NewValidationError("FieldNinComplex128 must not be one of '7+8i' '9+0i'")) + } if !(types.SliceNotContains(obj.FieldNinStringSlice, []string{"ab", "cd", "ef"})) { errs = append(errs, types.NewValidationError("FieldNinStringSlice elements must not be one of 'ab' 'cd' 'ef'")) } @@ -2162,6 +2224,12 @@ func requiredStructFieldsValidate(obj *requiredStructFields) []error { if !(obj.FieldRequiredBool != false) { errs = append(errs, types.NewValidationError("FieldRequiredBool is required")) } + if !(obj.FieldRequiredComplex64 != 0) { + errs = append(errs, types.NewValidationError("FieldRequiredComplex64 is required")) + } + if !(obj.FieldRequiredComplex128 != 0) { + errs = append(errs, types.NewValidationError("FieldRequiredComplex128 is required")) + } if !(len(obj.FieldRequiredStringSlice) != 0) { errs = append(errs, types.NewValidationError("FieldRequiredStringSlice must not be empty")) }