diff --git a/pkg/generators/models/models.go b/pkg/generators/models/models.go index db72bf9..11a5eec 100644 --- a/pkg/generators/models/models.go +++ b/pkg/generators/models/models.go @@ -10,9 +10,10 @@ import ( "sort" "text/template" - tpl "github.com/contiamo/openapi-generator-go/v2/pkg/generators/templates" "github.com/getkin/kin-openapi/openapi3" "github.com/pkg/errors" + + tpl "github.com/contiamo/openapi-generator-go/v2/pkg/generators/templates" ) // ExtensionPatternError is an extension property that, if set, allows the API author @@ -51,13 +52,16 @@ const ( // Model is a template model for rendering Go code for a given API schema type Model struct { + // Validation stuff + ValidationSpec + // Name is a name of the generated type that follows the `type` keyword in the definition // For example, `type Name GoType`. Name string // Description is a description that will become a comment on the generated type Description string - // Imports is a list of imports that will be present in the Go file - Imports []string + // Imports is a list of imports that will be present in the Go file. Key is the package and value is the alias + Imports map[string]string // Kind is a kind of generated model (e.g. `struct` or `enum`) Kind ModelKind // Properties is a list of type's property descriptors @@ -86,28 +90,9 @@ type ConvertSpec struct { TargetGoType string } -// PropSpec is a Go property descriptor -type PropSpec struct { - // Name is a property name in structs, variable name in enums, etc - Name string - // PropertyName is the original name of the property - PropertyName string - // Description used in the comment of the property - Description string - // GoType used for this property (e.g. `string`, `int`, etc) - GoType string - // JSONTags is a string of JSON tags used for marshaling (e.g. `json:omitempty`) - JSONTags string - // Value is a value used for a enum item - Value string - // IsRequired is true when the property is a required value and should not be omitted on marshaling - IsRequired bool - // IsEnum is true when the property is a enum item - IsEnum bool - // IsOneOf is true when the property is a `oneof` schema - IsOneOf bool - +type ValidationSpec struct { // Validation stuff + HasPattern bool Pattern string PatternErrorMsg string IsNullable bool @@ -132,6 +117,31 @@ type PropSpec struct { IsIPv6 bool } +// PropSpec is a Go property descriptor +type PropSpec struct { + // Validation stuff + ValidationSpec + + // Name is a property name in structs, variable name in enums, etc + Name string + // PropertyName is the original name of the property + PropertyName string + // Description used in the comment of the property + Description string + // GoType used for this property (e.g. `string`, `int`, etc) + GoType string + // JSONTags is a string of JSON tags used for marshaling (e.g. `json:omitempty`) + JSONTags string + // Value is a value used for a enum item + Value string + // IsRequired is true when the property is a required value and should not be omitted on marshaling + IsRequired bool + // IsEnum is true when the property is a enum item + IsEnum bool + // IsOneOf is true when the property is a `oneof` schema + IsOneOf bool +} + type Discriminator struct { // Field points to the property that specifies the data type name Field string @@ -170,6 +180,8 @@ func NewModelFromRef(ref *openapi3.SchemaRef) (model *Model, err error) { default: model.Kind = Value model.GoType = goTypeFromSpec(ref) + model.Imports = make(map[string]string) + err = fillValidationSpec(ref, &model.ValidationSpec, model.GoType, false, model.Imports) } if err != nil { return nil, err @@ -181,7 +193,8 @@ func NewModelFromRef(ref *openapi3.SchemaRef) (model *Model, err error) { // NewModelFromParameters returns a model built from operation parameters func NewModelFromParameters(params openapi3.Parameters) (model *Model, err error) { model = &Model{ - Kind: Struct, + Kind: Struct, + Imports: make(map[string]string), } for _, param := range params { @@ -193,13 +206,12 @@ func NewModelFromParameters(params openapi3.Parameters) (model *Model, err error } if spec.GoType == "time.Time" || spec.GoType == "*time.Time" { - model.Imports = append(model.Imports, "time") + model.Imports["time"] = "" } - extraImports, err := fillValidationRelatedProperties(param.Value.Schema, &spec) + err := fillValidationSpecForProperty(param.Value.Schema, &spec, model.Imports) if err != nil { return nil, fmt.Errorf("invalid parameter %q:%w", param.Value.Name, err) } - model.Imports = append(model.Imports, extraImports...) omit := "" if !spec.IsRequired { @@ -214,8 +226,6 @@ func NewModelFromParameters(params openapi3.Parameters) (model *Model, err error model.Properties = append(model.Properties, spec) } - model.Imports = uniqueStrings(model.Imports) - return model, nil } @@ -522,11 +532,12 @@ func dedup[T any](input []T) []T { } // structPropsFromRef creates property descriptors for a Go struct from a schema -func structPropsFromRef(ref *openapi3.SchemaRef) (specs []PropSpec, imports []string, err error) { +func structPropsFromRef(ref *openapi3.SchemaRef) (specs []PropSpec, imports map[string]string, err error) { if ref == nil || ref.Value == nil { return nil, nil, nil } + imports = make(map[string]string) for _, name := range sortedKeys(ref.Value.Properties) { prop := ref.Value.Properties[name] @@ -563,18 +574,18 @@ func structPropsFromRef(ref *openapi3.SchemaRef) (specs []PropSpec, imports []st } spec := PropSpec{ - Name: tpl.ToPascalCase(name), - PropertyName: name, - Description: prop.Value.Description, - GoType: goType, - IsRequired: isRequired, - IsEnum: len(prop.Value.Enum) > 0, - IsNullable: prop.Value.Nullable, - IsOneOf: prop.Value.OneOf != nil && len(prop.Value.OneOf) > 0, + Name: tpl.ToPascalCase(name), + PropertyName: name, + Description: prop.Value.Description, + GoType: goType, + IsRequired: isRequired, + IsEnum: len(prop.Value.Enum) > 0, + ValidationSpec: ValidationSpec{IsNullable: prop.Value.Nullable}, + IsOneOf: prop.Value.OneOf != nil && len(prop.Value.OneOf) > 0, } if spec.GoType == "time.Time" || spec.GoType == "*time.Time" { - imports = append(imports, "time") + imports["time"] = "" } omit := "" @@ -588,28 +599,41 @@ func structPropsFromRef(ref *openapi3.SchemaRef) (specs []PropSpec, imports []st spec.JSONTags = jsonTags - extraImports, err := fillValidationRelatedProperties(prop, &spec) + err := fillValidationSpecForProperty(prop, &spec, imports) if err != nil { return nil, nil, fmt.Errorf("invalid %s property %q: %w", ExtensionPatternError, name, err) } - imports = append(imports, extraImports...) - specs = append(specs, spec) } - return specs, uniqueStrings(imports), err + return specs, imports, err } -// fillValidationRelatedProperties sets validation rules for the property descriptor out of format and +// fillValidationSpecForProperty sets validation rules for the property descriptor out of format and // bounds in the spec. // Returns a list of unique imports in order to implement the validation. -func fillValidationRelatedProperties(ref *openapi3.SchemaRef, spec *PropSpec) (imports []string, err error) { - importsMap := make(map[string]something) +func fillValidationSpecForProperty(ref *openapi3.SchemaRef, spec *PropSpec, imports map[string]string) (err error) { + // Only add validation fields that are part of the property and not the referenced schema. + if ref.Ref != "" { + valSpec := ValidationSpec{} + err = fillValidationSpec(ref, &valSpec, spec.GoType, spec.IsRequired, make(map[string]string)) + if err != nil { + return err + } + // Enable to force validation on referenced types. + spec.NeedsValidation = valSpec.NeedsValidation + spec.IsRequiredInValidation = !valSpec.IsNullable && spec.IsRequired + return nil + } + + return fillValidationSpec(ref, &spec.ValidationSpec, spec.GoType, spec.IsRequired, imports) +} - if validatedTypesRegExp.MatchString(spec.GoType) { +func fillValidationSpec(ref *openapi3.SchemaRef, spec *ValidationSpec, goType string, isRequired bool, imports map[string]string) (err error) { + if validatedTypesRegExp.MatchString(goType) { // enable recursive validation spec.NeedsValidation = true - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired } if ref.Value.Min != nil { @@ -617,7 +641,7 @@ func fillValidationRelatedProperties(ref *openapi3.SchemaRef, spec *PropSpec) (i spec.HasMin = true spec.Min = *ref.Value.Min if spec.Min > 0 { - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired } } @@ -626,7 +650,7 @@ func fillValidationRelatedProperties(ref *openapi3.SchemaRef, spec *PropSpec) (i spec.HasMax = true spec.Max = *ref.Value.Max if spec.Max > 0 { - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired } } @@ -635,7 +659,7 @@ func fillValidationRelatedProperties(ref *openapi3.SchemaRef, spec *PropSpec) (i spec.HasMinLength = true spec.MinLength = ref.Value.MinLength if spec.MinLength > 0 { - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired } } @@ -644,7 +668,7 @@ func fillValidationRelatedProperties(ref *openapi3.SchemaRef, spec *PropSpec) (i spec.HasMaxLength = true spec.MaxLength = *ref.Value.MaxLength if spec.MaxLength > 0 { - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired } } @@ -662,103 +686,103 @@ func fillValidationRelatedProperties(ref *openapi3.SchemaRef, spec *PropSpec) (i if ref.Value.Pattern != "" { spec.NeedsValidation = true + spec.HasPattern = true spec.Pattern = ref.Value.Pattern - importsMap["regexp"] = something{} + imports["regexp"] = "" msg, ok := ref.Value.ExtensionProps.Extensions[ExtensionPatternError] if ok { rawMsg, ok := msg.(json.RawMessage) if !ok { - return nil, fmt.Errorf("invalid %s value, expected json msg, got %T", ExtensionPatternError, msg) + return fmt.Errorf("invalid %s value, expected json msg, got %T", ExtensionPatternError, msg) } err = json.Unmarshal(rawMsg, &spec.PatternErrorMsg) if err != nil { - return nil, fmt.Errorf("invalid %s value, must be a string", ExtensionPatternError) + return fmt.Errorf("invalid %s value, must be a string", ExtensionPatternError) } } } switch ref.Value.Format { case "date": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired - spec.IsRequired = true + spec.IsRequiredInValidation = !spec.IsNullable && isRequired + spec.NeedsValidation = true spec.IsDate = true case "date-time": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired - spec.IsRequired = true + spec.IsRequiredInValidation = !spec.IsNullable && isRequired + spec.NeedsValidation = true spec.IsDateTime = true - importsMap["time"] = something{} + imports["time"] = "" case "byte": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsBase64 = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "email": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsEmail = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "uuid": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsUUID = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "url": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsURL = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "uri": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsURI = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "request-uri": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsRequestURI = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "hostname": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsHostname = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "ipv4": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsIPv4 = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "ipv6": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsIPv6 = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "ip": - spec.IsRequiredInValidation = !spec.IsNullable && spec.IsRequired + spec.IsRequiredInValidation = !spec.IsNullable && isRequired spec.NeedsValidation = true spec.IsIP = true - importsMap["github.com/go-ozzo/ozzo-validation/v4/is"] = something{} + imports["github.com/go-ozzo/ozzo-validation/v4/is"] = "" case "", "int32", "int64", "float", "double": break // do nothing } - imports = make([]string, 0, len(importsMap)) - for importStr := range importsMap { - imports = append(imports, importStr) + if spec.NeedsValidation { + imports["github.com/go-ozzo/ozzo-validation/v4"] = "validation" } - return imports, nil + return nil } // enumPropsFromRef generates a list of enum property/item descriptors. @@ -799,16 +823,3 @@ func checkIfRequired(name string, list []string) bool { } return false } - -// uniqueStrings filters out duplicates from `input` and forms a list of unique values `output` -func uniqueStrings(input []string) (output []string) { - m := make(map[string]struct{}) - for _, item := range input { - if _, ok := m[item]; ok { - continue - } - output = append(output, item) - m[item] = struct{}{} - } - return output -} diff --git a/pkg/generators/models/models_test.go b/pkg/generators/models/models_test.go index 2535370..79304b1 100644 --- a/pkg/generators/models/models_test.go +++ b/pkg/generators/models/models_test.go @@ -118,20 +118,20 @@ var cases = []struct { directory: "testdata/cases/parameter_model", }, { - name: "model with recursive definition within an allof", + name: "model with recursive definition within an allOf", directory: "testdata/cases/allof_self_reference", }, { - name: "allof merges required list", + name: "allOf merges required list", directory: "testdata/cases/allof_merges_required_list", }, // 25 { - name: "allof merges enum list", + name: "allOf merges enum list", directory: "testdata/cases/allof_enum", }, { - name: "allof supports intermediate array types", + name: "allOf supports intermediate array types", directory: "testdata/cases/allof_arrays", }, { diff --git a/pkg/generators/models/templates/model.gotpl b/pkg/generators/models/templates/model.gotpl index 5c94ef7..c76708f 100644 --- a/pkg/generators/models/templates/model.gotpl +++ b/pkg/generators/models/templates/model.gotpl @@ -12,9 +12,10 @@ import ( {{- end}} validation "github.com/go-ozzo/ozzo-validation/v4" -{{- range .Imports}} - "{{.}}" -{{ end}} + +{{- range $package, $alias := .Imports}} + {{$alias}} "{{$package}}" +{{- end}} ) {{- $modelName := .Name }} diff --git a/pkg/generators/models/templates/value.gotpl b/pkg/generators/models/templates/value.gotpl index e02219a..2ee5d24 100644 --- a/pkg/generators/models/templates/value.gotpl +++ b/pkg/generators/models/templates/value.gotpl @@ -6,5 +6,50 @@ // Version: {{.SpecVersion}} package {{ .PackageName }} +{{- if .Imports}} +import ( + {{- range $package, $alias := .Imports}} + {{$alias}} "{{$package}}" + {{- end}} +) +{{- end}} + +{{- if .PatternErrorMsg}} + // {{.Name}}PatternError is the error message returned for pattern validation errors on {{.Name}} + var {{.Name}}PatternError = validation.NewError("validation_{{.Name }}_pattern_invalid", "{{.PatternErrorMsg}}") +{{- end }} +{{- if .Pattern }} +// {{.Name| firstLower}}Pattern is the validation regexp for {{.Name}} +var {{.Name| firstLower}}Pattern = regexp.MustCompile(`{{ .Pattern }}`) +{{ end }} + {{ (printf "%s is a value type. %s" .Name .Description) | commentBlock }} type {{.Name}} {{.GoType}} + + +// Validate implements basic validation for this model +func (v {{.Name}}) Validate() error { +{{- if .NeedsValidation }} + return validation.Validate( + {{.GoType}}(v), + {{- if .HasMin }}validation.Min({{ .GoType }}({{ .Min }})),{{ end }} + {{- if .HasMax }}validation.Max({{ .GoType }}({{ .Max }})),{{ end }} + {{- if or .HasMinLength .HasMaxLength }}validation.Length({{ .MinLength }},{{ .MaxLength }}),{{ end }} + {{- if .IsDate }}validation.Date("2006-01-02"),{{ end }} + {{- if .IsDateTime }}validation.Date(time.RFC3339),{{ end }} + {{- if .IsBase64 }}is.Base64,{{ end }} + {{- if .IsEmail }}is.EmailFormat,{{ end }} + {{- if .IsUUID }}is.UUID,{{ end }} + {{- if .IsURL }}is.URL.Error("must be a valid URL with HTTP or HTTPS scheme"),{{ end }} + {{- if .IsURI }}is.RequestURI,{{ end }} + {{- if .IsRequestURI }}is.RequestURL.Error("must be valid URI with scheme"),{{ end }} + {{- if .IsHostname }}is.Host,{{ end }} + {{- if .IsIPv4 }}is.IPv4,{{ end }} + {{- if .IsIPv6 }}is.IPv6,{{ end }} + {{- if .IsIP }}is.IP,{{ end }} + {{- if .Pattern }}validation.Match({{.Name| firstLower}}Pattern){{if .PatternErrorMsg}}.ErrorObject({{.Name}}PatternError){{end}},{{ end }} + ) +{{- else }} + return nil +{{- end }} +} diff --git a/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/expected/model_connection.go b/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/expected/model_connection.go index 2a2abc2..5376ccd 100644 --- a/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/expected/model_connection.go +++ b/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/expected/model_connection.go @@ -8,11 +8,9 @@ package generatortest import ( validation "github.com/go-ozzo/ozzo-validation/v4" - "time" - "github.com/go-ozzo/ozzo-validation/v4/is" - "regexp" + "time" ) // connectionNamePattern is the validation pattern for Connection.Name @@ -37,6 +35,9 @@ type Connection struct { // Validate implements basic validation for this model func (m Connection) Validate() error { return validation.Errors{ + "createdAt": validation.Validate( + m.CreatedAt, validation.Required, validation.Date(time.RFC3339), + ), "id": validation.Validate( m.Id, validation.Required, is.UUID, ), @@ -49,6 +50,9 @@ func (m Connection) Validate() error { "technology": validation.Validate( m.Technology, validation.Required, ), + "updatedAt": validation.Validate( + m.UpdatedAt, validation.Required, validation.Date(time.RFC3339), + ), }.Filter() } diff --git a/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/generated/model_connection.go b/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/generated/model_connection.go index 2a2abc2..5376ccd 100644 --- a/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/generated/model_connection.go +++ b/pkg/generators/models/testdata/cases/allOf_enum_merging_and_validation/generated/model_connection.go @@ -8,11 +8,9 @@ package generatortest import ( validation "github.com/go-ozzo/ozzo-validation/v4" - "time" - "github.com/go-ozzo/ozzo-validation/v4/is" - "regexp" + "time" ) // connectionNamePattern is the validation pattern for Connection.Name @@ -37,6 +35,9 @@ type Connection struct { // Validate implements basic validation for this model func (m Connection) Validate() error { return validation.Errors{ + "createdAt": validation.Validate( + m.CreatedAt, validation.Required, validation.Date(time.RFC3339), + ), "id": validation.Validate( m.Id, validation.Required, is.UUID, ), @@ -49,6 +50,9 @@ func (m Connection) Validate() error { "technology": validation.Validate( m.Technology, validation.Required, ), + "updatedAt": validation.Validate( + m.UpdatedAt, validation.Required, validation.Date(time.RFC3339), + ), }.Filter() } diff --git a/pkg/generators/models/testdata/cases/embedded_type/expected/model_sub3.go b/pkg/generators/models/testdata/cases/embedded_type/expected/model_sub3.go index f247bf7..f535e5a 100644 --- a/pkg/generators/models/testdata/cases/embedded_type/expected/model_sub3.go +++ b/pkg/generators/models/testdata/cases/embedded_type/expected/model_sub3.go @@ -8,3 +8,8 @@ package generatortest // Sub3 is a value type. Type alias for a value type type Sub3 bool + +// Validate implements basic validation for this model +func (v Sub3) Validate() error { + return nil +} diff --git a/pkg/generators/models/testdata/cases/embedded_type/generated/model_sub3.go b/pkg/generators/models/testdata/cases/embedded_type/generated/model_sub3.go index f247bf7..f535e5a 100644 --- a/pkg/generators/models/testdata/cases/embedded_type/generated/model_sub3.go +++ b/pkg/generators/models/testdata/cases/embedded_type/generated/model_sub3.go @@ -8,3 +8,8 @@ package generatortest // Sub3 is a value type. Type alias for a value type type Sub3 bool + +// Validate implements basic validation for this model +func (v Sub3) Validate() error { + return nil +} diff --git a/pkg/generators/models/testdata/cases/snake_casing_handling/expected/model_user.go b/pkg/generators/models/testdata/cases/snake_casing_handling/expected/model_user.go index 32da950..91ab71c 100644 --- a/pkg/generators/models/testdata/cases/snake_casing_handling/expected/model_user.go +++ b/pkg/generators/models/testdata/cases/snake_casing_handling/expected/model_user.go @@ -22,7 +22,11 @@ type User struct { // Validate implements basic validation for this model func (m User) Validate() error { - return validation.Errors{}.Filter() + return validation.Errors{ + "dateOfBirth": validation.Validate( + m.DateOfBirth, validation.Date("2006-01-02"), + ), + }.Filter() } // GetDateOfBirth returns the DateOfBirth property diff --git a/pkg/generators/models/testdata/cases/snake_casing_handling/generated/model_user.go b/pkg/generators/models/testdata/cases/snake_casing_handling/generated/model_user.go index 32da950..91ab71c 100644 --- a/pkg/generators/models/testdata/cases/snake_casing_handling/generated/model_user.go +++ b/pkg/generators/models/testdata/cases/snake_casing_handling/generated/model_user.go @@ -22,7 +22,11 @@ type User struct { // Validate implements basic validation for this model func (m User) Validate() error { - return validation.Errors{}.Filter() + return validation.Errors{ + "dateOfBirth": validation.Validate( + m.DateOfBirth, validation.Date("2006-01-02"), + ), + }.Filter() } // GetDateOfBirth returns the DateOfBirth property diff --git a/pkg/generators/models/testdata/cases/validation/api.yaml b/pkg/generators/models/testdata/cases/validation/api.yaml index b929eaa..953607b 100644 --- a/pkg/generators/models/testdata/cases/validation/api.yaml +++ b/pkg/generators/models/testdata/cases/validation/api.yaml @@ -14,9 +14,7 @@ components: - cron properties: cron: - type: string - pattern: (@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7}) - x-pattern-error: "must be a valid cron value" + $ref: "#/components/schemas/Cron" pomodoro: type: string pattern: "^\\d{1,2}m$" @@ -43,7 +41,7 @@ components: minLength: 1 email: type: string - format: email + format: "email" date: type: string format: "date" @@ -53,9 +51,6 @@ components: base64: type: string format: "byte" - email: - type: string - format: "email" uuid: type: string format: "uuid" @@ -80,6 +75,10 @@ components: ip: type: string format: "ip" + Cron: + type: string + pattern: (@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7}) + x-pattern-error: "must be a valid cron value" Gender: type: string enum: diff --git a/pkg/generators/models/testdata/cases/validation/expected/model_cron.go b/pkg/generators/models/testdata/cases/validation/expected/model_cron.go new file mode 100644 index 0000000..a7964f2 --- /dev/null +++ b/pkg/generators/models/testdata/cases/validation/expected/model_cron.go @@ -0,0 +1,28 @@ +// Code generated by openapi-generator-go DO NOT EDIT. +// +// Source: +// +// Title: Test +// Version: 0.1.0 +package generatortest + +import ( + validation "github.com/go-ozzo/ozzo-validation/v4" + "regexp" +) + +// CronPatternError is the error message returned for pattern validation errors on Cron +var CronPatternError = validation.NewError("validation_Cron_pattern_invalid", "must be a valid cron value") + +// cronPattern is the validation regexp for Cron +var cronPattern = regexp.MustCompile(`(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`) + +// Cron is a value type. +type Cron string + +// Validate implements basic validation for this model +func (v Cron) Validate() error { + return validation.Validate( + string(v), validation.Match(cronPattern).ErrorObject(CronPatternError), + ) +} diff --git a/pkg/generators/models/testdata/cases/validation/expected/model_person.go b/pkg/generators/models/testdata/cases/validation/expected/model_person.go index 40e9e63..4bb0bf6 100644 --- a/pkg/generators/models/testdata/cases/validation/expected/model_person.go +++ b/pkg/generators/models/testdata/cases/validation/expected/model_person.go @@ -9,18 +9,10 @@ package generatortest import ( validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" - "regexp" - "time" ) -// PersonCronPatternError is the error message returned for pattern validation errors on Person.Cron -var PersonCronPatternError = validation.NewError("validation_Cron_pattern_invalid", "must be a valid cron value") - -// personCronPattern is the validation pattern for Person.Cron -var personCronPattern = regexp.MustCompile(`(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`) - // personPomodoroPattern is the validation pattern for Person.Pomodoro var personPomodoroPattern = regexp.MustCompile(`^\d{1,2}m$`) @@ -33,7 +25,7 @@ type Person struct { // Base64: Base64 string `json:"base64,omitempty" mapstructure:"base64,omitempty"` // Cron: - Cron string `json:"cron" mapstructure:"cron"` + Cron Cron `json:"cron" mapstructure:"cron"` // Date: Date string `json:"date,omitempty" mapstructure:"date,omitempty"` // Datetime: @@ -81,7 +73,13 @@ func (m Person) Validate() error { m.Base64, is.Base64, ), "cron": validation.Validate( - m.Cron, validation.Match(personCronPattern).ErrorObject(PersonCronPatternError), + m.Cron, validation.NotNil, + ), + "date": validation.Validate( + m.Date, validation.Date("2006-01-02"), + ), + "datetime": validation.Validate( + m.Datetime, validation.Date(time.RFC3339), ), "email": validation.Validate( m.Email, is.EmailFormat, @@ -159,12 +157,12 @@ func (m *Person) SetBase64(val string) { } // GetCron returns the Cron property -func (m Person) GetCron() string { +func (m Person) GetCron() Cron { return m.Cron } // SetCron sets the Cron property -func (m *Person) SetCron(val string) { +func (m *Person) SetCron(val Cron) { m.Cron = val } diff --git a/pkg/generators/models/testdata/cases/validation/generated/model_cron.go b/pkg/generators/models/testdata/cases/validation/generated/model_cron.go new file mode 100644 index 0000000..a7964f2 --- /dev/null +++ b/pkg/generators/models/testdata/cases/validation/generated/model_cron.go @@ -0,0 +1,28 @@ +// Code generated by openapi-generator-go DO NOT EDIT. +// +// Source: +// +// Title: Test +// Version: 0.1.0 +package generatortest + +import ( + validation "github.com/go-ozzo/ozzo-validation/v4" + "regexp" +) + +// CronPatternError is the error message returned for pattern validation errors on Cron +var CronPatternError = validation.NewError("validation_Cron_pattern_invalid", "must be a valid cron value") + +// cronPattern is the validation regexp for Cron +var cronPattern = regexp.MustCompile(`(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`) + +// Cron is a value type. +type Cron string + +// Validate implements basic validation for this model +func (v Cron) Validate() error { + return validation.Validate( + string(v), validation.Match(cronPattern).ErrorObject(CronPatternError), + ) +} diff --git a/pkg/generators/models/testdata/cases/validation/generated/model_person.go b/pkg/generators/models/testdata/cases/validation/generated/model_person.go index 40e9e63..4bb0bf6 100644 --- a/pkg/generators/models/testdata/cases/validation/generated/model_person.go +++ b/pkg/generators/models/testdata/cases/validation/generated/model_person.go @@ -9,18 +9,10 @@ package generatortest import ( validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" - "regexp" - "time" ) -// PersonCronPatternError is the error message returned for pattern validation errors on Person.Cron -var PersonCronPatternError = validation.NewError("validation_Cron_pattern_invalid", "must be a valid cron value") - -// personCronPattern is the validation pattern for Person.Cron -var personCronPattern = regexp.MustCompile(`(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`) - // personPomodoroPattern is the validation pattern for Person.Pomodoro var personPomodoroPattern = regexp.MustCompile(`^\d{1,2}m$`) @@ -33,7 +25,7 @@ type Person struct { // Base64: Base64 string `json:"base64,omitempty" mapstructure:"base64,omitempty"` // Cron: - Cron string `json:"cron" mapstructure:"cron"` + Cron Cron `json:"cron" mapstructure:"cron"` // Date: Date string `json:"date,omitempty" mapstructure:"date,omitempty"` // Datetime: @@ -81,7 +73,13 @@ func (m Person) Validate() error { m.Base64, is.Base64, ), "cron": validation.Validate( - m.Cron, validation.Match(personCronPattern).ErrorObject(PersonCronPatternError), + m.Cron, validation.NotNil, + ), + "date": validation.Validate( + m.Date, validation.Date("2006-01-02"), + ), + "datetime": validation.Validate( + m.Datetime, validation.Date(time.RFC3339), ), "email": validation.Validate( m.Email, is.EmailFormat, @@ -159,12 +157,12 @@ func (m *Person) SetBase64(val string) { } // GetCron returns the Cron property -func (m Person) GetCron() string { +func (m Person) GetCron() Cron { return m.Cron } // SetCron sets the Cron property -func (m *Person) SetCron(val string) { +func (m *Person) SetCron(val Cron) { m.Cron = val }