From 1b9afd28f4398d326a6b2206b067332fb7d38fd2 Mon Sep 17 00:00:00 2001 From: Robert Weber Date: Thu, 8 Aug 2019 15:39:37 -0600 Subject: [PATCH] Add support for external references Enable external refs with -ri External references will be encapsulated in a package as described by the --extrefs argument. Get can be insecure (eg ignore Cert issues) using the -insecure option Does not generate the external code (as it would be in a different file anyway) but allows the code to be referenced. Automatically adds import paths/package name to appropriate generated files. --- cmd/oapi-codegen/oapi-codegen.go | 41 +++++- .../petstore-expanded/internal/petstore.go | 3 +- go.mod | 2 +- go.sum | 12 +- internal/test/client/doc.go | 1 - pkg/codegen/codegen.go | 138 ++++++++---------- pkg/codegen/operations.go | 26 ++-- pkg/codegen/schema.go | 32 ++-- pkg/codegen/template_helpers.go | 24 +-- .../templates/client-with-responses.tmpl | 2 +- pkg/codegen/templates/templates.gen.go | 2 +- pkg/codegen/utils.go | 39 +++-- pkg/codegen/utils_test.go | 99 +++++++++++-- pkg/util/loader.go | 39 ++++- 14 files changed, 302 insertions(+), 158 deletions(-) diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index 6ec7895f9a..1f8ed21ccd 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -30,16 +30,36 @@ func errExit(format string, args ...interface{}) { os.Exit(1) } +// TODO define a type to allow recording of Type -> Import Path as args +// TODO this is temporary until we sort the config question +type refImports []string + +func (ri *refImports) String() string { + return "Reference Import arg" +} + +func (ri *refImports) Set(r string) error { + *ri = append(*ri, r) + return nil +} + func main() { var ( packageName string generate string outputFile string + refImports refImports + allowRefs bool + insecure bool ) + flag.StringVar(&packageName, "package", "", "The package name for generated code") flag.StringVar(&generate, "generate", "types,client,server,spec", - `Comma-separated list of code to generate; valid options: "types", client", "server", "spec" (default types,client,server,"spec")`) + `Comma-separated list of code to generate; valid options: "types", "client", "server", "spec" (default types,client,server,"spec")`) flag.StringVar(&outputFile, "o", "", "Where to output generated code, stdout is default") + flag.Var(&refImports, "ri", "Repeated reference import statements of the form Type=") + flag.BoolVar(&allowRefs, "extrefs", false, "Allow resolving external references") + flag.BoolVar(&insecure, "insecure", false, "Allow resolving remote URL's that have bad SSL/TLS") flag.Parse() if flag.NArg() < 1 { @@ -75,9 +95,22 @@ func main() { } } - swagger, err := util.LoadSwagger(flag.Arg(0)) + // Add user defined type -> import mapping + typeImports := map[string]string{} + for _, ri := range refImports { + parts := strings.Split(ri, "=") + if len(parts) != 2 { + fmt.Printf("invalid ref import arg. %s\n", ri) + flag.PrintDefaults() + os.Exit(1) + } + typeImports[parts[0]] = parts[1] + } + opts.TypeImports = typeImports + + swagger, err := util.LoadSwagger(flag.Arg(0), allowRefs, insecure) if err != nil { - errExit("error loading swagger spec\n: %s", err) + errExit("error loading swagger spec:\n%s\n", err) } code, err := codegen.Generate(swagger, packageName, opts) @@ -88,7 +121,7 @@ func main() { if outputFile != "" { err = ioutil.WriteFile(outputFile, []byte(code), 0644) if err != nil { - errExit("error writing generated code to file: %s", err) + errExit("error writing generated code to file: %s\n", err) } } else { fmt.Println(code) diff --git a/examples/petstore-expanded/internal/petstore.go b/examples/petstore-expanded/internal/petstore.go index 9611dfbf3c..48e3b2962d 100644 --- a/examples/petstore-expanded/internal/petstore.go +++ b/examples/petstore-expanded/internal/petstore.go @@ -15,10 +15,11 @@ package internal import ( "fmt" - "github.com/labstack/echo/v4" "net/http" "sync" + "github.com/labstack/echo/v4" + "github.com/deepmap/oapi-codegen/examples/petstore-expanded/api" ) diff --git a/go.mod b/go.mod index d5a5e8ab3f..51c9024799 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c github.com/getkin/kin-openapi v0.2.0 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 - github.com/labstack/echo/v4 v4.1.6 + github.com/labstack/echo/v4 v4.1.8 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect diff --git a/go.sum b/go.sum index a8390c0721..172b08811a 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c h1:/ovYnF02fwL0kvspmy9AuyKg1JhdTRUgPw4nUxd9oZM= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -17,8 +16,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv4= -github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= +github.com/labstack/echo/v4 v4.1.8 h1:2IBbRrln806Ao53hR4dxU1SFgJEDWG/IUU81ryYlGdE= +github.com/labstack/echo/v4 v4.1.8/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU= github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= @@ -38,13 +37,14 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00= golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -53,18 +53,18 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190609082536-301114b31cce h1:CQakrGkKbydnUmt7cFIlmQ4lNQiqdTPt6xzXij4nYCc= golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3 h1:sU3tSV6wDhWsvf9NjL0FzRjgAmYnQL5NEhdmcN16UEg= golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1 h1:JwHzEZwWOyWUIR+OxPKGQGUfuOp/feyTesu6DEwqvsM= golang.org/x/tools v0.0.0-20190724185037-8aa4eac1a7c1/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/test/client/doc.go b/internal/test/client/doc.go index 4a3bcfc8fc..8244197df5 100644 --- a/internal/test/client/doc.go +++ b/internal/test/client/doc.go @@ -1,4 +1,3 @@ package client //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=client -o client.gen.go client.yaml - diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 49f039f240..2dd6cd9e2b 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -31,10 +31,11 @@ import ( // Options defines the optional code to generate. type Options struct { - GenerateServer bool // GenerateServer specifies whether to generate server boilerplate - GenerateClient bool // GenerateClient specifies whether to generate client boilerplate - GenerateTypes bool // GenerateTypes specifies whether to generate type definitions - EmbedSpec bool // Whether to embed the swagger spec in the generated code + GenerateServer bool // GenerateServer specifies whether to generate server boilerplate + GenerateClient bool // GenerateClient specifies whether to generate client boilerplate + GenerateTypes bool // GenerateTypes specifies whether to generate type definitions + EmbedSpec bool // Whether to embed the swagger spec in the generated code + TypeImports map[string]string // Additional imports added when using remote references } // Uses the Go templating engine to generate all of our server wrappers from @@ -50,11 +51,16 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri return "", errors.Wrap(err, "error parsing oapi-codegen templates") } - ops, err := OperationDefinitions(swagger) + ops, err := OperationDefinitions(swagger, opts.TypeImports) if err != nil { return "", errors.Wrap(err, "error creating operation definitions") } + //if opts.TypeImports != nil { + // TODO: we need to add package name to external resource type names. + // this affects all defines schemas here + //} + var typeDefinitions string if opts.GenerateTypes { typeDefinitions, err = GenerateTypeDefinitions(t, swagger, ops) @@ -103,63 +109,38 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri // Based on module prefixes, figure out which optional imports are required. // TODO: this is error prone, use tighter matches - for _, str := range []string{typeDefinitions, serverOut, clientOut, clientWithResponsesOut, inlinedSpec} { - if strings.Contains(str, "time.Time") { - imports = append(imports, "time") - } - if strings.Contains(str, "http.") { - imports = append(imports, "net/http") - } - if strings.Contains(str, "openapi3.") { - imports = append(imports, "github.com/getkin/kin-openapi/openapi3") - } - if strings.Contains(str, "json.") { - imports = append(imports, "encoding/json") - } - if strings.Contains(str, "echo.") { - imports = append(imports, "github.com/labstack/echo/v4") - } - if strings.Contains(str, "io.") { - imports = append(imports, "io") - } - if strings.Contains(str, "ioutil.") { - imports = append(imports, "io/ioutil") - } - if strings.Contains(str, "url.") { - imports = append(imports, "net/url") - } - if strings.Contains(str, "context.") { - imports = append(imports, "context") - } - if strings.Contains(str, "runtime.") { - imports = append(imports, "github.com/deepmap/oapi-codegen/pkg/runtime") - } - if strings.Contains(str, "bytes.") { - imports = append(imports, "bytes") - } - if strings.Contains(str, "gzip.") { - imports = append(imports, "compress/gzip") - } - if strings.Contains(str, "base64.") { - imports = append(imports, "encoding/base64") - } - if strings.Contains(str, "openapi3.") { - imports = append(imports, "github.com/getkin/kin-openapi/openapi3") - } - if strings.Contains(str, "strings.") { - imports = append(imports, "strings") - } - if strings.Contains(str, "fmt.") { - imports = append(imports, "fmt") - } - if strings.Contains(str, "yaml.") { - imports = append(imports, "gopkg.in/yaml.v2") - } - if strings.Contains(str, "xml.") { - imports = append(imports, "encoding/xml") + allCode := strings.Join([]string{typeDefinitions, serverOut, clientOut, clientWithResponsesOut, inlinedSpec}, "\n") + + knownTypeImports := map[string]string{ + "time.Time": "time", + "http.": "net/http", + "openapi3.": "github.com/getkin/kin-openapi/openapi3", + "json.": "encoding/json", + "echo.": "github.com/labstack/echo/v4", + "io.": "io", + "ioutil.": "io/ioutil", + "url.": "net/url", + "context.": "context", + "runtime.": "github.com/deepmap/oapi-codegen/pkg/runtime", + "bytes.": "bytes", + "gzip.": "compress/gzip", + "base64.": "encoding/base64", + "strings.": "strings", + "fmt.": "fmt", + "yaml.": "gopkg.in/yaml.v2", + "xml.": "encoding/xml", + "errors.": "github.com/pkg/errors", + } + + for t, i := range knownTypeImports { + if strings.Contains(allCode, t) { + imports = append(imports, i) } - if strings.Contains(str, "errors.") { - imports = append(imports, "github.com/pkg/errors") + } + + for t, i := range opts.TypeImports { + if strings.Contains(allCode, t) { + imports = append(imports, i) } } @@ -222,24 +203,29 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri } func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.Swagger, ops []OperationDefinition) (string, error) { - schemaTypes, err := GenerateTypesForSchemas(t, swagger.Components.Schemas) + var typeMap map[string]string + if len(ops) > 0 { + typeMap = ops[0].TypeMap // they are all the same + } + + schemaTypes, err := GenerateTypesForSchemas(t, swagger.Components.Schemas, typeMap) if err != nil { return "", errors.Wrap(err, "error generating Go types for component schemas") } - paramTypes, err := GenerateTypesForParameters(t, swagger.Components.Parameters) + paramTypes, err := GenerateTypesForParameters(t, swagger.Components.Parameters, typeMap) if err != nil { return "", errors.Wrap(err, "error generating Go types for component parameters") } allTypes := append(schemaTypes, paramTypes...) - responseTypes, err := GenerateTypesForResponses(t, swagger.Components.Responses) + responseTypes, err := GenerateTypesForResponses(t, swagger.Components.Responses, typeMap) if err != nil { return "", errors.Wrap(err, "error generating Go types for component responses") } allTypes = append(allTypes, responseTypes...) - bodyTypes, err := GenerateTypesForRequestBodies(t, swagger.Components.RequestBodies) + bodyTypes, err := GenerateTypesForRequestBodies(t, swagger.Components.RequestBodies, typeMap) if err != nil { return "", errors.Wrap(err, "error generating Go types for component request bodies") } @@ -266,13 +252,13 @@ func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.Swagger, op // Generates type definitions for any custom types defined in the // components/schemas section of the Swagger spec. -func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3.SchemaRef) ([]TypeDefinition, error) { +func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3.SchemaRef, typeMap map[string]string) ([]TypeDefinition, error) { types := make([]TypeDefinition, 0) // We're going to define Go types for every object under components/schemas for _, schemaName := range SortedSchemaKeys(schemas) { schemaRef := schemas[schemaName] - goSchema, err := GenerateGoSchema(schemaRef, []string{schemaName}) + goSchema, err := GenerateGoSchema(schemaRef, []string{schemaName}, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error converting Schema %s to Go type", schemaName)) } @@ -290,12 +276,12 @@ func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3. // Generates type definitions for any custom types defined in the // components/parameters section of the Swagger spec. -func GenerateTypesForParameters(t *template.Template, params map[string]*openapi3.ParameterRef) ([]TypeDefinition, error) { +func GenerateTypesForParameters(t *template.Template, params map[string]*openapi3.ParameterRef, typeMap map[string]string) ([]TypeDefinition, error) { var types []TypeDefinition for _, paramName := range SortedParameterKeys(params) { paramOrRef := params[paramName] - goType, err := paramToGoType(paramOrRef.Value, nil) + goType, err := paramToGoType(paramOrRef.Value, nil, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for schema in parameter %s", paramName)) } @@ -308,7 +294,7 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi if paramOrRef.Ref != "" { // Generate a reference type for referenced parameters - refType, err := RefPathToGoType(paramOrRef.Ref) + refType, err := RefPathToGoType(paramOrRef.Ref, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for (%s) in parameter %s", paramOrRef.Ref, paramName)) } @@ -322,7 +308,7 @@ func GenerateTypesForParameters(t *template.Template, params map[string]*openapi // Generates type definitions for any custom types defined in the // components/responses section of the Swagger spec. -func GenerateTypesForResponses(t *template.Template, responses openapi3.Responses) ([]TypeDefinition, error) { +func GenerateTypesForResponses(t *template.Template, responses openapi3.Responses, typeMap map[string]string) ([]TypeDefinition, error) { var types []TypeDefinition for _, responseName := range SortedResponsesKeys(responses) { @@ -334,7 +320,7 @@ func GenerateTypesForResponses(t *template.Template, responses openapi3.Response response := responseOrRef.Value jsonResponse, found := response.Content["application/json"] if found { - goType, err := GenerateGoSchema(jsonResponse.Schema, []string{responseName}) + goType, err := GenerateGoSchema(jsonResponse.Schema, []string{responseName}, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for schema in response %s", responseName)) } @@ -347,7 +333,7 @@ func GenerateTypesForResponses(t *template.Template, responses openapi3.Response if responseOrRef.Ref != "" { // Generate a reference type for referenced parameters - refType, err := RefPathToGoType(responseOrRef.Ref) + refType, err := RefPathToGoType(responseOrRef.Ref, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for (%s) in parameter %s", responseOrRef.Ref, responseName)) } @@ -361,7 +347,7 @@ func GenerateTypesForResponses(t *template.Template, responses openapi3.Response // Generates type definitions for any custom types defined in the // components/requestBodies section of the Swagger spec. -func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*openapi3.RequestBodyRef) ([]TypeDefinition, error) { +func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*openapi3.RequestBodyRef, typeMap map[string]string) ([]TypeDefinition, error) { var types []TypeDefinition for _, bodyName := range SortedRequestBodyKeys(bodies) { @@ -372,7 +358,7 @@ func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*open response := bodyOrRef.Value jsonBody, found := response.Content["application/json"] if found { - goType, err := GenerateGoSchema(jsonBody.Schema, []string{bodyName}) + goType, err := GenerateGoSchema(jsonBody.Schema, []string{bodyName}, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for schema in body %s", bodyName)) } @@ -385,7 +371,7 @@ func GenerateTypesForRequestBodies(t *template.Template, bodies map[string]*open if bodyOrRef.Ref != "" { // Generate a reference type for referenced bodies - refType, err := RefPathToGoType(bodyOrRef.Ref) + refType, err := RefPathToGoType(bodyOrRef.Ref, typeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for (%s) in body %s", bodyOrRef.Ref, bodyName)) } diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index e59869b4db..94ef337f40 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -141,12 +141,12 @@ func (p ParameterDefinitions) FindByName(name string) *ParameterDefinition { // This function walks the given parameters dictionary, and generates the above // descriptors into a flat list. This makes it a lot easier to traverse the // data in the template engine. -func DescribeParameters(params openapi3.Parameters, path []string) ([]ParameterDefinition, error) { +func DescribeParameters(params openapi3.Parameters, path []string, typeMap map[string]string) ([]ParameterDefinition, error) { outParams := make([]ParameterDefinition, 0) for _, paramOrRef := range params { param := paramOrRef.Value - goType, err := paramToGoType(param, append(path, param.Name)) + goType, err := paramToGoType(param, append(path, param.Name), typeMap) if err != nil { return nil, fmt.Errorf("error generating type for param (%s): %s", param.Name, err) @@ -164,7 +164,7 @@ func DescribeParameters(params openapi3.Parameters, path []string) ([]ParameterD // name as the type. $ref: "#/components/schemas/custom_type" becomes // "CustomType". if paramOrRef.Ref != "" { - goType, err := RefPathToGoType(paramOrRef.Ref) + goType, err := RefPathToGoType(paramOrRef.Ref, typeMap) if err != nil { return nil, fmt.Errorf("error dereferencing (%s) for param (%s): %s", paramOrRef.Ref, param.Name, err) @@ -190,6 +190,7 @@ type OperationDefinition struct { Summary string // Summary string from Swagger, used to generate a comment Method string // GET, POST, DELETE, etc. Path string // The Swagger path for the operation, like /resource/{id} + TypeMap map[string]string // map of typename to go path for import Spec *openapi3.Operation } @@ -255,7 +256,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]TypeDefinition, er contentType := responseRef.Value.Content[contentTypeName] // We can only generate a type if we have a schema: if contentType.Schema != nil { - responseSchema, err := GenerateGoSchema(contentType.Schema, []string{responseName}) + responseSchema, err := GenerateGoSchema(contentType.Schema, []string{responseName}, o.TypeMap) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("Unable to determine Go type for %s.%s", o.OperationId, contentTypeName)) } @@ -279,7 +280,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]TypeDefinition, er Schema: responseSchema, } if contentType.Schema.Ref != "" { - refType, err := RefPathToGoType(contentType.Schema.Ref) + refType, err := RefPathToGoType(contentType.Schema.Ref, o.TypeMap) if err != nil { return nil, errors.Wrap(err, "error dereferencing response Ref") } @@ -349,14 +350,14 @@ func FilterParameterDefinitionByType(params []ParameterDefinition, in string) [] } // OperationDefinitions returns all operations for a swagger definition. -func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, error) { +func OperationDefinitions(swagger *openapi3.Swagger, typeMap map[string]string) ([]OperationDefinition, error) { var operations []OperationDefinition for _, requestPath := range SortedPathsKeys(swagger.Paths) { pathItem := swagger.Paths[requestPath] // These are parameters defined for all methods on a given path. They // are shared by all methods. - globalParams, err := DescribeParameters(pathItem.Parameters, nil) + globalParams, err := DescribeParameters(pathItem.Parameters, nil, typeMap) if err != nil { return nil, fmt.Errorf("error describing global parameters for %s: %s", requestPath, err) @@ -374,7 +375,7 @@ func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, err // These are parameters defined for the specific path method that // we're iterating over. - localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"}) + localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"}, typeMap) if err != nil { return nil, fmt.Errorf("error describing global parameters for %s/%s: %s", opName, requestPath, err) @@ -392,7 +393,7 @@ func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, err return nil, err } - bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody) + bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody, typeMap) if err != nil { return nil, errors.Wrap(err, "error generating body definitions") } @@ -410,6 +411,7 @@ func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, err Spec: op, Bodies: bodyDefinitions, TypeDefinitions: typeDefinitions, + TypeMap: typeMap, } if op.RequestBody != nil { @@ -427,7 +429,7 @@ func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, err // This function turns the Swagger body definitions into a list of our body // definitions which will be used for code generation. -func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBodyRef) ([]RequestBodyDefinition, []TypeDefinition, error) { +func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBodyRef, typeMap map[string]string) ([]RequestBodyDefinition, []TypeDefinition, error) { if bodyOrRef == nil { return nil, nil, nil } @@ -449,7 +451,7 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody } bodyTypeName := operationID + tag + "Body" - bodySchema, err := GenerateGoSchema(content.Schema, []string{bodyTypeName}) + bodySchema, err := GenerateGoSchema(content.Schema, []string{bodyTypeName}, typeMap) if err != nil { return nil, nil, errors.Wrap(err, "error generating request body definition") } @@ -457,7 +459,7 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody // If the body is a pre-defined type if bodyOrRef.Ref != "" { // Convert the reference path to Go type - refType, err := RefPathToGoType(bodyOrRef.Ref) + refType, err := RefPathToGoType(bodyOrRef.Ref, typeMap) if err != nil { return nil, nil, errors.Wrap(err, fmt.Sprintf("error turning reference (%s) into a Go type", bodyOrRef.Ref)) } diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 58b644a9dd..4319e1260c 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -80,7 +80,7 @@ func PropertiesEqual(a, b Property) bool { return a.JsonFieldName == b.JsonFieldName && a.Schema.TypeDecl() == b.Schema.TypeDecl() && a.Required == b.Required } -func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { +func GenerateGoSchema(sref *openapi3.SchemaRef, path []string, typeMap map[string]string) (Schema, error) { schema := sref.Value // If Ref is set on the SchemaRef, it means that this type is actually a reference to @@ -89,7 +89,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { if sref.Ref != "" { var err error // Convert the reference path to Go type - refType, err = RefPathToGoType(sref.Ref) + refType, err = RefPathToGoType(sref.Ref, typeMap) if err != nil { return Schema{}, fmt.Errorf("error turning reference (%s) into a Go type: %s", sref.Ref, err) @@ -110,7 +110,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { // so that in a RESTful paradigm, the Create operation can return // (object, id), so that other operations can refer to (id) if schema.AllOf != nil { - mergedSchema, err := MergeSchemas(schema.AllOf, path) + mergedSchema, err := MergeSchemas(schema.AllOf, path, typeMap) if err != nil { return Schema{}, errors.Wrap(err, "error merging schemas") } @@ -146,7 +146,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { for _, pName := range SortedSchemaKeys(schema.Properties) { p := schema.Properties[pName] propertyPath := append(path, pName) - pSchema, err := GenerateGoSchema(p, propertyPath) + pSchema, err := GenerateGoSchema(p, propertyPath, typeMap) if err != nil { return Schema{}, errors.Wrap(err, fmt.Sprintf("error generating Go schema for property '%s'", pName)) } @@ -183,7 +183,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { GoType: "interface{}", } if schema.AdditionalProperties != nil { - additionalSchema, err := GenerateGoSchema(schema.AdditionalProperties, path) + additionalSchema, err := GenerateGoSchema(schema.AdditionalProperties, path, typeMap) if err != nil { return Schema{}, errors.Wrap(err, "error generating type for additional properties") } @@ -200,7 +200,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { case "array": // For arrays, we'll get the type of the Items and throw a // [] in front of it. - arrayType, err := GenerateGoSchema(schema.Items, path) + arrayType, err := GenerateGoSchema(schema.Items, path, typeMap) if err != nil { return Schema{}, errors.Wrap(err, "error generating type for array") } @@ -300,7 +300,7 @@ func GenStructFromSchema(schema Schema) string { } // Merge all the fields in the schemas supplied into one giant schema. -func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) { +func MergeSchemas(allOf []*openapi3.SchemaRef, path []string, typeMap map[string]string) (Schema, error) { var outSchema Schema for _, schemaOrRef := range allOf { ref := schemaOrRef.Ref @@ -308,13 +308,13 @@ func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) { var refType string var err error if ref != "" { - refType, err = RefPathToGoType(ref) + refType, err = RefPathToGoType(ref, typeMap) if err != nil { return Schema{}, errors.Wrap(err, "error converting reference path to a go type") } } - schema, err := GenerateGoSchema(schemaOrRef, path) + schema, err := GenerateGoSchema(schemaOrRef, path, typeMap) if err != nil { return Schema{}, errors.Wrap(err, "error generating Go schema in allOf") } @@ -345,7 +345,7 @@ func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) { // Now, we generate the struct which merges together all the fields. var err error - outSchema.GoType, err = GenStructFromAllOf(allOf, path) + outSchema.GoType, err = GenStructFromAllOf(allOf, path, typeMap) if err != nil { return Schema{}, errors.Wrap(err, "unable to generate aggregate type for AllOf") } @@ -355,7 +355,7 @@ func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) { // This function generates an object that is the union of the objects in the // input array. In the case of Ref objects, we use an embedded struct, otherwise, // we inline the fields. -func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, error) { +func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string, typeMap map[string]string) (string, error) { // Start out with struct { objectParts := []string{"struct {"} for _, schemaOrRef := range allOf { @@ -367,7 +367,7 @@ func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, err // InlinedMember // ... // } - goType, err := RefPathToGoType(ref) + goType, err := RefPathToGoType(ref, typeMap) if err != nil { return "", err } @@ -378,7 +378,7 @@ func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, err } else { // Inline all the fields from the schema into the output struct, // just like in the simple case of generating an object. - goSchema, err := GenerateGoSchema(schemaOrRef, path) + goSchema, err := GenerateGoSchema(schemaOrRef, path, typeMap) if err != nil { return "", err } @@ -393,14 +393,14 @@ func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, err // This constructs a Go type for a parameter, looking at either the schema or // the content, whichever is available -func paramToGoType(param *openapi3.Parameter, path []string) (Schema, error) { +func paramToGoType(param *openapi3.Parameter, path []string, typeMap map[string]string) (Schema, error) { if param.Content == nil && param.Schema == nil { return Schema{}, fmt.Errorf("parameter '%s' has no schema or content", param.Name) } // We can process the schema through the generic schema processor if param.Schema != nil { - return GenerateGoSchema(param.Schema, path) + return GenerateGoSchema(param.Schema, path, typeMap) } // At this point, we have a content type. We know how to deal with @@ -422,5 +422,5 @@ func paramToGoType(param *openapi3.Parameter, path []string) (Schema, error) { } // For json, we go through the standard schema mechanism - return GenerateGoSchema(mt.Schema, path) + return GenerateGoSchema(mt.Schema, path, typeMap) } diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index e828024ec6..59826e08f4 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -94,7 +94,7 @@ func genResponsePayload(operationID string) string { } // genResponseUnmarshal generates unmarshaling steps for structured response payloads -func genResponseUnmarshal(operationID string, responses openapi3.Responses) string { +func genResponseUnmarshal(operationID string, responses openapi3.Responses, typeMap map[string]string) string { var buffer = bytes.NewBufferString("") var mostSpecific = make(map[string]string) // content-type and status-code var lessSpecific = make(map[string]string) // status-code only @@ -142,7 +142,7 @@ func genResponseUnmarshal(operationID string, responses openapi3.Responses) stri } // Make sure that we actually have a go-type for this response: - goType, err := GenerateGoSchema(contentType.Schema, []string{contentTypeName}) + goType, err := GenerateGoSchema(contentType.Schema, []string{contentTypeName}, typeMap) if err != nil { fmt.Fprintf(os.Stderr, "Unable to determine Go type for %s.%s: %v\n", operationID, contentTypeName, err) continue @@ -239,15 +239,15 @@ func getResponseTypeDefinitions(op *OperationDefinition) []TypeDefinition { // This function map is passed to the template engine, and we can call each // function here by keyName from the template code. var TemplateFunctions = template.FuncMap{ - "genParamArgs": genParamArgs, - "genParamTypes": genParamTypes, - "genParamNames": genParamNames, - "genParamFmtString": genParamFmtString, - "swaggerUriToEchoUri": SwaggerUriToEchoUri, - "lcFirst": LowercaseFirstCharacter, - "camelCase": ToCamelCase, - "genResponsePayload": genResponsePayload, - "genResponseTypeName": genResponseTypeName, - "genResponseUnmarshal": genResponseUnmarshal, + "genParamArgs": genParamArgs, + "genParamTypes": genParamTypes, + "genParamNames": genParamNames, + "genParamFmtString": genParamFmtString, + "swaggerUriToEchoUri": SwaggerUriToEchoUri, + "lcFirst": LowercaseFirstCharacter, + "camelCase": ToCamelCase, + "genResponsePayload": genResponsePayload, + "genResponseTypeName": genResponseTypeName, + "genResponseUnmarshal": genResponseUnmarshal, "getResponseTypeDefinitions": getResponseTypeDefinitions, } diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index 038d637648..ef56612883 100644 --- a/pkg/codegen/templates/client-with-responses.tmpl +++ b/pkg/codegen/templates/client-with-responses.tmpl @@ -93,7 +93,7 @@ func Parse{{genResponseTypeName $opid}}(rsp *http.Response) (*{{genResponseTypeN response := {{genResponsePayload $opid}} - {{genResponseUnmarshal $opid .Spec.Responses}} + {{genResponseUnmarshal $opid .Spec.Responses .TypeMap}} return response, nil } diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index ece86dfb68..13b203d607 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -168,7 +168,7 @@ func Parse{{genResponseTypeName $opid}}(rsp *http.Response) (*{{genResponseTypeN response := {{genResponsePayload $opid}} - {{genResponseUnmarshal $opid .Spec.Responses}} + {{genResponseUnmarshal $opid .Spec.Responses .TypeMap}} return response, nil } diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go index 837a8301bb..ac54fce83b 100644 --- a/pkg/codegen/utils.go +++ b/pkg/codegen/utils.go @@ -15,6 +15,7 @@ package codegen import ( "fmt" + "github.com/getkin/kin-openapi/openapi3" "github.com/pkg/errors" @@ -188,20 +189,38 @@ 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. -func RefPathToGoType(refPath string) (string, error) { - pathParts := strings.Split(refPath, "/") - if pathParts[0] != "#" { - return "", errors.New("Only local document components are supported") +// +// To specify remote paths there must also be a typeImport map with the types +// mapped to import paths. +// https://foo.com/bar#/components/parameters/Bar -> packagename.Bar +// foo.json#/components/parameters/Bar -> packagename.Bar +func RefPathToGoType(refPath string, packages map[string]string) (string, error) { + s := strings.Split(refPath, "#") + if len(s) < 2 { + return "", errors.New("Missing fragment marker - this does not seem like a local or remote reference") + } else if len(s) > 2 { + return "", errors.New("Extra fragment marker") } + + pathParts := strings.Split(s[len(s)-1], "/") + if len(pathParts) != 4 { return "", errors.New("Parameter nesting is deeper than supported") } - return SchemaNameToTypeName(pathParts[3]), nil + schemaName := SchemaNameToTypeName(pathParts[len(pathParts)-1]) + + if s[0] != "" { + if packages == nil { + return "", errors.New("detected remote document component but no TypeImports specified") + } + packagePath, ok := packages[schemaName] + if !ok { + return "", errors.New("detected remote document component not specified in TypeImports") + } + p := strings.Split(packagePath, "/") + return p[len(p)-1] + "." + schemaName, nil + } + return schemaName, nil } // This function converts a swagger style path URI with parameters to a diff --git a/pkg/codegen/utils_test.go b/pkg/codegen/utils_test.go index 39f6d8d534..8f8cfc4470 100644 --- a/pkg/codegen/utils_test.go +++ b/pkg/codegen/utils_test.go @@ -134,22 +134,91 @@ func TestSortedRequestBodyKeys(t *testing.T) { } func TestRefPathToGoType(t *testing.T) { - goType, err := RefPathToGoType("#/components/schemas/Foo") - assert.Equal(t, "Foo", goType) - assert.NoError(t, err, "Expecting no error") - - goType, err = RefPathToGoType("#/components/parameters/foo_bar") - assert.Equal(t, "FooBar", goType) - assert.NoError(t, err, "Expecting no error") - - _, err = RefPathToGoType("http://deepmap.com/doc.json#/components/parameters/foo_bar") - assert.Errorf(t, err, "Expected an error on URL reference") - - _, err = RefPathToGoType("doc.json#/components/parameters/foo_bar") - assert.Errorf(t, err, "Expected an error on remote reference") + testTable := map[string]struct { + path string + typeMap map[string]string + goType string + isErr bool + }{ + "Pascal case": { + "#/components/schemas/Foo", + nil, + "Foo", + false, + }, + "Snake case": { + "#/components/parameters/foo_bar", + nil, + "FooBar", + false, + }, + "remote ref, no typemap": { + "http://deepmap.com/doc.json#/components/parameters/foo_bar", + nil, + "", + true, + }, + "path ref, no typemap": { + "doc.json#/components/parameters/foo_bar", + nil, + "", + true, + }, + "remote ref, matches typemap": { + "http://deepmap.com/doc.json#/components/parameters/foo_bar", + map[string]string{"FooBar": "github.com/me/mypkg"}, + "mypkg.FooBar", + false, + }, + "path ref, matches typemap": { + "doc.json#/components/parameters/foo_bar", + map[string]string{"FooBar": "github.com/me/mypkg"}, + "mypkg.FooBar", + false, + }, + "remote ref, no match in typemap": { + "http://deepmap.com/doc.json#/components/parameters/foo_bar", + map[string]string{"Foo": "github.com/me/mypkg"}, + "", + true, + }, + "path ref, no match in typemap": { + "doc.json#/components/parameters/foo_bar", + map[string]string{"Foo": "github.com/me/mypkg"}, + "", + true, + }, + "reference depth incorrect": { + "#/components/parameters/foo/components/bar", + nil, + "", + true, + }, + "invalid path, too many #": { + "#/components/parameters/foo#", + nil, + "", + true, + }, + "invalid path, no #": { + "/components/parameters/foo", + nil, + "", + true, + }, + } + for name, test := range testTable { + t.Run(name, func(t *testing.T) { + goType, err := RefPathToGoType(test.path, test.typeMap) + assert.Equal(t, test.goType, goType) + if test.isErr { + assert.Error(t, err, "Expected an error") + } else { + assert.NoError(t, err, "Expecting no error") + } + }) + } - _, err = RefPathToGoType("#/components/parameters/foo/components/bar") - assert.Errorf(t, err, "Expected an error on reference depth") } func TestSwaggerUriToEchoUri(t *testing.T) { diff --git a/pkg/util/loader.go b/pkg/util/loader.go index 318342a6d3..b7225708fb 100644 --- a/pkg/util/loader.go +++ b/pkg/util/loader.go @@ -1,16 +1,19 @@ package util import ( + "crypto/tls" "encoding/json" "fmt" "io/ioutil" + "net/http" + "net/url" "path/filepath" "strings" "github.com/getkin/kin-openapi/openapi3" ) -func LoadSwagger(filePath string) (*openapi3.Swagger, error) { +func LoadSwagger(filePath string, allowRefs bool, insecure bool) (*openapi3.Swagger, error) { data, err := ioutil.ReadFile(filePath) if err != nil { return nil, err @@ -21,7 +24,12 @@ func LoadSwagger(filePath string) (*openapi3.Swagger, error) { ext = strings.ToLower(ext) switch ext { case ".yaml", ".yml": - swagger, err = openapi3.NewSwaggerLoader().LoadSwaggerFromData(data) + sl := openapi3.NewSwaggerLoader() + sl.IsExternalRefsAllowed = allowRefs + if insecure { + sl.LoadSwaggerFromURIFunc = insecureReadUrl + } + swagger, err = sl.LoadSwaggerFromFile(filePath) case ".json": swagger = &openapi3.Swagger{} err = json.Unmarshal(data, swagger) @@ -33,3 +41,30 @@ func LoadSwagger(filePath string) (*openapi3.Swagger, error) { } return swagger, nil } + +func insecureReadUrl(sl *openapi3.SwaggerLoader, location *url.URL) (*openapi3.Swagger, error) { + if location.Scheme != "" && location.Host != "" { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + cl := &http.Client{Transport: tr} + resp, err := cl.Get(location.String()) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return nil, err + } + return sl.LoadSwaggerFromDataWithPath(data, location) + } + if location.Scheme != "" || location.Host != "" || location.RawQuery != "" { + return nil, fmt.Errorf("Unsupported URI: '%s'", location.String()) + } + data, err := ioutil.ReadFile(location.Path) + if err != nil { + return nil, err + } + return sl.LoadSwaggerFromDataWithPath(data, location) +}