diff --git a/internal/test/issues/issue-832/config.yaml b/internal/test/issues/issue-832/config.yaml new file mode 100644 index 000000000..e1eb4e5f6 --- /dev/null +++ b/internal/test/issues/issue-832/config.yaml @@ -0,0 +1,5 @@ +package: issue_832 +generate: + models: true + embedded-spec: true +output: issue.gen.go diff --git a/internal/test/issues/issue-832/generate.go b/internal/test/issues/issue-832/generate.go new file mode 100644 index 000000000..877007acb --- /dev/null +++ b/internal/test/issues/issue-832/generate.go @@ -0,0 +1,3 @@ +package issue_832 + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml diff --git a/internal/test/issues/issue-832/issue.gen.go b/internal/test/issues/issue-832/issue.gen.go new file mode 100644 index 000000000..be10dedd6 --- /dev/null +++ b/internal/test/issues/issue-832/issue.gen.go @@ -0,0 +1,121 @@ +// Package issue_832 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT. +package issue_832 + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/url" + "path" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Defines values for Document_Status. +const ( + Four Document_Status = "four" + One Document_Status = "one" + Three Document_Status = "three" + Two Document_Status = "two" +) + +// Document defines model for Document. +type Document struct { + Name *string `json:"name,omitempty"` + Document_Status *Document_Status `json:"status,omitempty"` +} + +// Document_Status defines model for Document.status. +type Document_Status string + +// DocumentStatus defines model for DocumentStatus. +type DocumentStatus struct { + Value *string `json:"value,omitempty"` +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/7ySz0/rMAzH/5XK7x27tq/vljMIIYQ47AgIhdRbM9o4Styxaer/jpxuReOHxIlLkyb2", + "9/tx7AMY6j05dBxBHSCaFnudthdkhh4dy94H8hjYYrpxukdZee8RFEQO1q1hzCGy5iGFoBt6UPdADiEH", + "fiX5tgHlb0VDgMf8Q3oOu8WaFpP2bP60nCTHcY6n5w0aFrtT0HK2Pefc6m74CvSzlhxZtyIJbjCaYD1b", + "cqDgVr9gFoeAGbeas4BmCNFuMROFmOmAWatd02GTTebd/sFJsZY7ccCd7n0nZW8xxEmzKqrinxRAHp32", + "FhT8L6qihhy85jaxl6dEdYA1piaIuhas6wYUXE73V8iQQ8DoycWp7LqqZDHk+Ng+7X1nTcotN1EYTp2W", + "3d+AK1Dwp3wfhfI4B+U8BOmJzp/m7kZOx3xmrX8AW/8G7Tw03zGP41sAAAD//zZo9q75AgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/internal/test/issues/issue-832/spec.yaml b/internal/test/issues/issue-832/spec.yaml new file mode 100644 index 000000000..e0ba58de4 --- /dev/null +++ b/internal/test/issues/issue-832/spec.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.2 +info: + version: '0.0.1' + title: example + description: | + Make sure that recursive types are handled properly +paths: + /example: + get: + operationId: exampleGet + responses: + '200': + description: "OK" + content: + 'application/json': + schema: + $ref: '#/components/schemas/Document' + /example2: + get: + operationId: exampleGet2 + responses: + '200': + description: "OK" + content: + 'application/json': + schema: + $ref: '#/components/schemas/DocumentStatus' +components: + schemas: + Document: + type: object + properties: + name: + type: string + status: + type: string + x-go-name: Document_Status + enum: [one, two, three, four] + DocumentStatus: + type: object + properties: + value: + type: string diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index ed1afb03d..0b47f8091 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -592,14 +592,17 @@ func GenerateTypes(t *template.Template, types []TypeDefinition) (string, error) m := map[string]bool{} ts := []TypeDefinition{} - for _, t := range types { - if found := m[t.TypeName]; found { - continue + for _, typ := range types { + if found := m[typ.TypeName]; found { + // We want to create an error when we try to define the same type + // twice. + return "", fmt.Errorf("duplicate typename '%s' detected, can't auto-rename, "+ + "please use x-go-name to specify your own name for one of them", typ.TypeName) } - m[t.TypeName] = true + m[typ.TypeName] = true - ts = append(ts, t) + ts = append(ts, typ) } context := struct { diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 9c343e30e..fe9307e9e 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -220,7 +220,7 @@ func PropertiesEqual(a, b Property) bool { func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { // Add a fallback value in case the sref is nil. // i.e. the parent schema defines a type:array, but the array has - // no items defined. Therefore we have at least valid Go-Code. + // no items defined. Therefore, we have at least valid Go-Code. if sref == nil { return Schema{GoType: "interface{}"}, nil } @@ -438,7 +438,18 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { } } if len(path) > 1 { // handle additional type only on non-toplevel types - typeName := SchemaNameToTypeName(PathToTypeName(path)) + // Allow overriding autogenerated enum type names, since these may + // cause conflicts - see https://github.com/deepmap/oapi-codegen/issues/832 + var typeName string + if extension, ok := schema.Extensions[extGoName]; ok { + typeName, err = extTypeName(extension) + if err != nil { + return outSchema, fmt.Errorf("invalid value for %q: %w", extGoName, err) + } + } else { + typeName = SchemaNameToTypeName(PathToTypeName(path)) + } + typeDef := TypeDefinition{ TypeName: typeName, JsonName: strings.Join(path, "."), @@ -447,7 +458,6 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, typeDef) outSchema.RefType = typeName } - // outSchema.RefType = typeName } else { err := oapiSchemaToGoType(schema, path, &outSchema) if err != nil {