From 160207c7900c3b9be3b3c7e120cb08a2f953464d Mon Sep 17 00:00:00 2001 From: Alexey Dudko Date: Tue, 23 Jun 2020 14:30:56 +0200 Subject: [PATCH 1/3] use kin-openapi only to read openapi file --- go.sum | 6 ------ pkg/util/loader.go | 30 +++++------------------------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/go.sum b/go.sum index d66a76fb8c..1fb5ee48c7 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/getkin/kin-openapi v0.3.0 h1:xsQ4mA20YJDMgIHdHqMKZ66QUe6/hi+x6yLsTTz8xyQ= -github.com/getkin/kin-openapi v0.3.0/go.mod h1:W8dhxZgpE84ciM+VIItFqkmZ4eHtuomrdIHtASQIqi0= github.com/getkin/kin-openapi v0.13.0 h1:03fqBEEgivp4MVK2ElB140B56hjO9ZFvFTHBsvFsSro= github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -43,8 +41,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= @@ -89,7 +85,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/util/loader.go b/pkg/util/loader.go index 6538e24bd9..54048739d7 100644 --- a/pkg/util/loader.go +++ b/pkg/util/loader.go @@ -1,32 +1,12 @@ package util import ( - "fmt" - "io/ioutil" - "path/filepath" - "strings" - "github.com/getkin/kin-openapi/openapi3" ) -func LoadSwagger(filePath string) (*openapi3.Swagger, error) { - data, err := ioutil.ReadFile(filePath) - if err != nil { - return nil, err - } - - var swagger *openapi3.Swagger - ext := filepath.Ext(filePath) - ext = strings.ToLower(ext) - switch ext { - // The YAML handler can parse both YAML and JSON - case ".yaml", ".yml", ".json": - swagger, err = openapi3.NewSwaggerLoader().LoadSwaggerFromData(data) - default: - return nil, fmt.Errorf("%s is not a supported extension, use .yaml, .yml or .json", ext) - } - if err != nil { - return nil, err - } - return swagger, nil +func LoadSwagger(filePath string) (swagger *openapi3.Swagger, err error) { + loader := openapi3.NewSwaggerLoader() + loader.IsExternalRefsAllowed = false + swagger, err = loader.LoadSwaggerFromFile(filePath) + return } From 2e6738e3a6f093844acdfd792eabfe9f3a4ed118 Mon Sep 17 00:00:00 2001 From: Alexey Dudko Date: Mon, 6 Jul 2020 14:26:14 +0200 Subject: [PATCH 2/3] added the support of external references throug the --import-mapping option --- cmd/oapi-codegen/oapi-codegen.go | 22 +++++++++++++------ pkg/codegen/codegen.go | 33 ++++++++++++++++++++++++++++ pkg/codegen/utils.go | 37 +++++++++++++++++++++----------- pkg/util/loader.go | 2 +- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index b578e9faf0..83c70fdbda 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -14,6 +14,7 @@ package main import ( + "encoding/json" "flag" "fmt" "io/ioutil" @@ -33,12 +34,13 @@ func errExit(format string, args ...interface{}) { func main() { var ( - packageName string - generate string - outputFile string - includeTags string - excludeTags string - templatesDir string + packageName string + generate string + outputFile string + includeTags string + excludeTags string + templatesDir string + importMapping string ) flag.StringVar(&packageName, "package", "", "The package name for generated code") flag.StringVar(&generate, "generate", "types,client,server,spec", @@ -47,6 +49,7 @@ func main() { flag.StringVar(&includeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.") flag.StringVar(&excludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.") flag.StringVar(&templatesDir, "templates", "", "Path to directory containing user templates") + flag.StringVar(&importMapping, "import-mapping", "", "A dict from the external reference to golang package path") flag.Parse() if flag.NArg() < 1 { @@ -106,6 +109,13 @@ func main() { } opts.UserTemplates = templates + opts.ImportMapping = map[string]string{} + if len(importMapping) > 0 { + if err = json.Unmarshal([]byte(importMapping), &opts.ImportMapping); err != nil { + errExit("error parsing import-mapping: %s\n", err) + } + } + code, err := codegen.Generate(swagger, packageName, opts) if err != nil { errExit("error generating code: %s\n", err) diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 9f12de5c42..a4b979e9b3 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -42,6 +42,7 @@ type Options struct { IncludeTags []string // Only include operations that have one of these tags. Ignored when empty. ExcludeTags []string // Exclude operations that have one of these tags. Ignored when empty. UserTemplates map[string]string // Override built-in templates from user-provided files + ImportMapping map[string]string // ImportMapping specifies the golang package path for each external reference } type goImport struct { @@ -84,12 +85,41 @@ var ( {lookFor: "xml\\.", packageName: "encoding/xml"}, {lookFor: "yaml\\.", packageName: "gopkg.in/yaml.v2"}, } + + importMapping = map[string]goImport{} ) +func constructImportMapping(input map[string]string) map[string]goImport { + var ( + nameToAlias = map[string]string{} + result = map[string]goImport{} + ) + + { + var packagePaths []string + for _, packageName := range input { + packagePaths = append(packagePaths, packageName) + } + sort.Strings(packagePaths) + + for _, packageName := range packagePaths { + if _, ok := nameToAlias[packageName]; !ok { + nameToAlias[packageName] = fmt.Sprintf("externalRef%d", len(nameToAlias)) + } + } + } + for urlOrPath, packageName := range input { + result[urlOrPath] = goImport{alias: nameToAlias[packageName], packageName: packageName} + } + return result +} + // Uses the Go templating engine to generate all of our server wrappers from // the descriptions we've built up above from the schema objects. // opts defines func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (string, error) { + importMapping = constructImportMapping(opts.ImportMapping) + filterOperationsByTag(swagger, opts) if !opts.SkipPrune { pruneUnusedComponents(swagger) @@ -169,6 +199,9 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri // Imports needed for the generated code to compile var imports []string + for _, importGo := range importMapping { + imports = append(imports, importGo.String()) + } var buf bytes.Buffer w := bufio.NewWriter(&buf) diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index 2a35b0341d..bcef998165 100644 --- a/pkg/codegen/utils.go +++ b/pkg/codegen/utils.go @@ -65,7 +65,7 @@ func ToCamelCase(str string) string { if unicode.IsUpper(v) { n += string(v) } - if unicode.IsDigit(v) { + if unicode.IsDigit(v) { n += string(v) } if unicode.IsLower(v) { @@ -76,7 +76,7 @@ func ToCamelCase(str string) string { } } - if strings.ContainsRune(separators, v) { + if strings.ContainsRune(separators, v) { capNext = true } else { capNext = false @@ -197,20 +197,31 @@ func StringInArray(str string, array []string) bool { // #/components/schemas/Foo -> Foo // #/components/parameters/Bar -> Bar // #/components/responses/Baz -> Baz -// Remote components (document.json#/Foo) are not yet supported -// URL components (http://deepmap.com/schemas/document.json#Foo) are not yet -// supported -// We only support flat components for now, so no components in a schema under -// components. +// Remote components (document.json#/Foo) are supported if they present in --import-mapping +// URL components (http://deepmap.com/schemas/document.json#Foo) are supported if they present in --import-mapping +// func RefPathToGoType(refPath string) (string, error) { - pathParts := strings.Split(refPath, "/") - if pathParts[0] != "#" { - return "", errors.New("Only local document components are supported") + if refPath[0] == '#' { + pathParts := strings.Split(refPath, "/") + if len(pathParts) != 4 { + return "", errors.New("Parameter nesting is deeper than supported") + } + return SchemaNameToTypeName(pathParts[3]), nil } - if len(pathParts) != 4 { - return "", errors.New("Parameter nesting is deeper than supported") + pathParts := strings.Split(refPath, "#") + if len(pathParts) != 2 { + return "", fmt.Errorf("unsupported reference: %s", refPath) + } + remoteComponent, flatComponent := pathParts[0], pathParts[1] + if goImport, ok := importMapping[remoteComponent]; !ok { + return "", fmt.Errorf("unrecognized external reference '%s'; please provide the known import for this reference using option --import-mapping", remoteComponent) + } else { + goType, err := RefPathToGoType("#" + flatComponent) + if err != nil { + return "", err + } + return fmt.Sprintf("%s.%s", goImport.alias, goType), nil } - return SchemaNameToTypeName(pathParts[3]), nil } // This function converts a swagger style path URI with parameters to a diff --git a/pkg/util/loader.go b/pkg/util/loader.go index 54048739d7..187dbe7a8f 100644 --- a/pkg/util/loader.go +++ b/pkg/util/loader.go @@ -6,7 +6,7 @@ import ( func LoadSwagger(filePath string) (swagger *openapi3.Swagger, err error) { loader := openapi3.NewSwaggerLoader() - loader.IsExternalRefsAllowed = false + loader.IsExternalRefsAllowed = true swagger, err = loader.LoadSwaggerFromFile(filePath) return } From bbff877e0461880a2f774f1e99f59aa6f9cb53a9 Mon Sep 17 00:00:00 2001 From: Alexey Dudko Date: Mon, 6 Jul 2020 14:30:41 +0200 Subject: [PATCH 3/3] added example for external reference --- internal/test/externalref/doc.go | 3 +++ internal/test/externalref/externalref.gen.go | 15 +++++++++++++++ internal/test/externalref/imports_test.go | 16 ++++++++++++++++ internal/test/externalref/packageA/doc.go | 3 +++ .../test/externalref/packageA/externalref.gen.go | 14 ++++++++++++++ internal/test/externalref/packageA/spec.yaml | 8 ++++++++ internal/test/externalref/packageB/doc.go | 3 +++ .../test/externalref/packageB/externalref.gen.go | 9 +++++++++ internal/test/externalref/packageB/spec.yaml | 6 ++++++ internal/test/externalref/spec.yaml | 11 +++++++++++ 10 files changed, 88 insertions(+) create mode 100644 internal/test/externalref/doc.go create mode 100644 internal/test/externalref/externalref.gen.go create mode 100644 internal/test/externalref/imports_test.go create mode 100644 internal/test/externalref/packageA/doc.go create mode 100644 internal/test/externalref/packageA/externalref.gen.go create mode 100644 internal/test/externalref/packageA/spec.yaml create mode 100644 internal/test/externalref/packageB/doc.go create mode 100644 internal/test/externalref/packageB/externalref.gen.go create mode 100644 internal/test/externalref/packageB/spec.yaml create mode 100644 internal/test/externalref/spec.yaml diff --git a/internal/test/externalref/doc.go b/internal/test/externalref/doc.go new file mode 100644 index 0000000000..fb261d109d --- /dev/null +++ b/internal/test/externalref/doc.go @@ -0,0 +1,3 @@ +package externalref + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -generate types,skip-prune --package=externalref -o externalref.gen.go --import-mapping={"./packageA/spec.yaml":"github.com/deepmap/oapi-codegen/internal/test/externalref/packageA","./packageB/spec.yaml":"github.com/deepmap/oapi-codegen/internal/test/externalref/packageB"} spec.yaml diff --git a/internal/test/externalref/externalref.gen.go b/internal/test/externalref/externalref.gen.go new file mode 100644 index 0000000000..5fc09bd60a --- /dev/null +++ b/internal/test/externalref/externalref.gen.go @@ -0,0 +1,15 @@ +// Package externalref provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package externalref + +import ( + externalRef0 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageA" + externalRef1 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB" +) + +// Container defines model for Container. +type Container struct { + ObjectA *externalRef0.ObjectA `json:"object_a,omitempty"` + ObjectB *externalRef1.ObjectB `json:"object_b,omitempty"` +} diff --git a/internal/test/externalref/imports_test.go b/internal/test/externalref/imports_test.go new file mode 100644 index 0000000000..284964ae1b --- /dev/null +++ b/internal/test/externalref/imports_test.go @@ -0,0 +1,16 @@ +package externalref + +import ( + "testing" + + "github.com/deepmap/oapi-codegen/internal/test/externalref/packageA" + "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB" +) + +func TestParameters(t *testing.T) { + b := &packageB.ObjectB{} + _ = Container{ + ObjectA: &packageA.ObjectA{ObjectB: b}, + ObjectB: b, + } +} diff --git a/internal/test/externalref/packageA/doc.go b/internal/test/externalref/packageA/doc.go new file mode 100644 index 0000000000..abc1e631d6 --- /dev/null +++ b/internal/test/externalref/packageA/doc.go @@ -0,0 +1,3 @@ +package packageA + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -generate types,skip-prune --package=packageA -o externalref.gen.go --import-mapping={"../packageB/spec.yaml":"github.com/deepmap/oapi-codegen/internal/test/externalref/packageB"} spec.yaml diff --git a/internal/test/externalref/packageA/externalref.gen.go b/internal/test/externalref/packageA/externalref.gen.go new file mode 100644 index 0000000000..066f2b71b0 --- /dev/null +++ b/internal/test/externalref/packageA/externalref.gen.go @@ -0,0 +1,14 @@ +// Package packageA provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package packageA + +import ( + externalRef0 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB" +) + +// ObjectA defines model for ObjectA. +type ObjectA struct { + Name *string `json:"name,omitempty"` + ObjectB *externalRef0.ObjectB `json:"object_b,omitempty"` +} diff --git a/internal/test/externalref/packageA/spec.yaml b/internal/test/externalref/packageA/spec.yaml new file mode 100644 index 0000000000..b2386a097e --- /dev/null +++ b/internal/test/externalref/packageA/spec.yaml @@ -0,0 +1,8 @@ +components: + schemas: + ObjectA: + properties: + name: + type: string + object_b: + $ref: ../packageB/spec.yaml#/components/schemas/ObjectB \ No newline at end of file diff --git a/internal/test/externalref/packageB/doc.go b/internal/test/externalref/packageB/doc.go new file mode 100644 index 0000000000..4d28c8c007 --- /dev/null +++ b/internal/test/externalref/packageB/doc.go @@ -0,0 +1,3 @@ +package packageB + +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -generate types,skip-prune --package=packageB -o externalref.gen.go spec.yaml diff --git a/internal/test/externalref/packageB/externalref.gen.go b/internal/test/externalref/packageB/externalref.gen.go new file mode 100644 index 0000000000..831b7e8d3f --- /dev/null +++ b/internal/test/externalref/packageB/externalref.gen.go @@ -0,0 +1,9 @@ +// Package packageB provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package packageB + +// ObjectB defines model for ObjectB. +type ObjectB struct { + Name *string `json:"name,omitempty"` +} diff --git a/internal/test/externalref/packageB/spec.yaml b/internal/test/externalref/packageB/spec.yaml new file mode 100644 index 0000000000..6f90634711 --- /dev/null +++ b/internal/test/externalref/packageB/spec.yaml @@ -0,0 +1,6 @@ +components: + schemas: + ObjectB: + properties: + name: + type: string diff --git a/internal/test/externalref/spec.yaml b/internal/test/externalref/spec.yaml new file mode 100644 index 0000000000..455ee66253 --- /dev/null +++ b/internal/test/externalref/spec.yaml @@ -0,0 +1,11 @@ +openapi: "3.0.0" +info: {} +paths: {} +components: + schemas: + Container: + properties: + object_a: + $ref: ./packageA/spec.yaml#/components/schemas/ObjectA + object_b: + $ref: ./packageB/spec.yaml#/components/schemas/ObjectB