From 7bbf3c8875fe398ef9553fcc0eb0eb24824baffb Mon Sep 17 00:00:00 2001 From: Alexey Dudko Date: Thu, 18 Mar 2021 15:29:29 +0100 Subject: [PATCH 1/2] implemented openapi specification embedding and specification loading in runtime with external reference resolution --- cmd/oapi-codegen/oapi-codegen.go | 5 +- go.mod | 4 +- go.sum | 18 ++++++- pkg/codegen/codegen.go | 3 +- pkg/codegen/inline.go | 46 +++-------------- pkg/codegen/templates/imports.tmpl | 2 + pkg/codegen/templates/inline.tmpl | 67 +++++++++++++++++-------- pkg/codegen/templates/templates.gen.go | 69 ++++++++++++++++++-------- 8 files changed, 126 insertions(+), 88 deletions(-) diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go index f1c509deaa..9a784627d2 100644 --- a/cmd/oapi-codegen/oapi-codegen.go +++ b/cmd/oapi-codegen/oapi-codegen.go @@ -123,11 +123,14 @@ func main() { errExit("can not specify both server and chi-server targets simultaneously") } - swagger, err := util.LoadSwagger(flag.Arg(0)) + var pathToSpec = flag.Arg(0) + swagger, err := util.LoadSwagger(pathToSpec) if err != nil { errExit("error loading swagger spec\n: %s", err) } + opts.SpecFileName = path.Base(pathToSpec) + templates, err := loadTemplateOverrides(cfg.TemplatesDir) if err != nil { errExit("error loading template overrides: %s\n", err) diff --git a/go.mod b/go.mod index 6a180fefe4..79ef59031e 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/deepmap/oapi-codegen require ( github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c - github.com/getkin/kin-openapi v0.37.0 + github.com/getkin/kin-openapi v0.49.0 github.com/go-chi/chi/v5 v5.0.0 github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 github.com/labstack/echo/v4 v4.2.1 @@ -19,4 +19,4 @@ require ( gopkg.in/yaml.v2 v2.3.0 ) -go 1.13 +go 1.16 diff --git a/go.sum b/go.sum index 797fd58d92..d993710d0e 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,13 @@ 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= 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.37.0 h1:nXIzVH5slhozZeKsmyPqM1fnTjHXKxilhaSN2TcdN/Q= -github.com/getkin/kin-openapi v0.37.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk= +github.com/getkin/kin-openapi v0.49.0 h1:nKSq662fS0kZ11+Wu3FLg3GQGL0UuH1VxF8wV1QuDEU= +github.com/getkin/kin-openapi v0.49.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.0 h1:DBPx88FjZJH3FsICfDAfIfnb7XxKIYVGG6lOPlhENAg= @@ -19,6 +20,7 @@ github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= 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= @@ -31,10 +33,12 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd h1:HvFwW+cm9bCbZ/+vuGNq7CRWXql8c0y8nGeYpqmpvmk= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= @@ -43,26 +47,32 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -85,13 +95,17 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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= +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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 9b9b70014d..6b2730b721 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -44,6 +44,7 @@ type Options struct { 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 ExcludeSchemas []string // Exclude from generation schemas with given names. Ignored when empty. + SpecFileName string } // goImport represents a go package to be imported in the generated code @@ -183,7 +184,7 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri var inlinedSpec string if opts.EmbedSpec { - inlinedSpec, err = GenerateInlinedSpec(t, swagger) + inlinedSpec, err = GenerateInlinedSpec(t, importMapping, opts) if err != nil { return "", errors.Wrap(err, "error generating Go handlers for Paths") } diff --git a/pkg/codegen/inline.go b/pkg/codegen/inline.go index 76251c9a94..82c90e9568 100644 --- a/pkg/codegen/inline.go +++ b/pkg/codegen/inline.go @@ -16,56 +16,22 @@ package codegen import ( "bufio" "bytes" - "compress/gzip" - "encoding/base64" "fmt" "text/template" - - "github.com/getkin/kin-openapi/openapi3" ) // This generates a gzipped, base64 encoded JSON representation of the // swagger definition, which we embed inside the generated code. -func GenerateInlinedSpec(t *template.Template, swagger *openapi3.Swagger) (string, error) { +func GenerateInlinedSpec(t *template.Template, importMapping importMap, opts Options) (string, error) { // Marshal to json - encoded, err := swagger.MarshalJSON() - if err != nil { - return "", fmt.Errorf("error marshaling swagger: %s", err) - } - // gzip + var err error var buf bytes.Buffer - zw, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) - if err != nil { - return "", fmt.Errorf("error creating gzip compressor: %s", err) - } - _, err = zw.Write(encoded) - if err != nil { - return "", fmt.Errorf("error gzipping swagger file: %s", err) - } - err = zw.Close() - if err != nil { - return "", fmt.Errorf("error gzipping swagger file: %s", err) - } - str := base64.StdEncoding.EncodeToString(buf.Bytes()) - - var parts []string - const width = 80 - - // Chop up the string into an array of strings. - for len(str) > width { - part := str[0:width] - parts = append(parts, part) - str = str[width:] - } - if len(str) > 0 { - parts = append(parts, str) - } - - // Generate inline code. - buf.Reset() w := bufio.NewWriter(&buf) - err = t.ExecuteTemplate(w, "inline.tmpl", parts) + err = t.ExecuteTemplate(w, "inline.tmpl", struct { + FileBase string + ImportMapping importMap + }{FileBase: opts.SpecFileName, ImportMapping: importMapping}) if err != nil { return "", fmt.Errorf("error generating inlined spec: %s", err) } diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl index 55505fed4d..943bcc170d 100644 --- a/pkg/codegen/templates/imports.tmpl +++ b/pkg/codegen/templates/imports.tmpl @@ -19,6 +19,8 @@ import ( "path" "strings" "time" + _ "embed" + "github.com/deepmap/oapi-codegen/pkg/runtime" openapi_types "github.com/deepmap/oapi-codegen/pkg/types" diff --git a/pkg/codegen/templates/inline.tmpl b/pkg/codegen/templates/inline.tmpl index 0ab3116ea4..0e8a1f4d43 100644 --- a/pkg/codegen/templates/inline.tmpl +++ b/pkg/codegen/templates/inline.tmpl @@ -1,29 +1,54 @@ -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ -{{range .}} - "{{.}}",{{end}} +//go:embed {{ .FileBase }} +var spec []byte + +// returns a raw spec +func RawSpec() []byte { + return spec } -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. -func GetSwagger() (*openapi3.Swagger, 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) +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathPrefix string) map[string]func() []byte { + // todo: fix spec validator so that external references are correct; + // now they can point to api.yaml files whereas the real file name is different + var res = map[string]func() []byte{ + path.Join(pathPrefix, "{{ .FileBase }}"): RawSpec, + {{- if (ne .FileBase "api.yaml") }} + path.Join(pathPrefix, "api.yaml"): RawSpec, + {{- end }} } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + {{ range $key, $value := .ImportMapping }} + for rawPath, rawFunc := range {{ $value.Name }}.PathToRawSpec(path.Join(pathPrefix, "{{ $value.Path }}")) { + if _, ok := res[rawPath]; ok { + panic(fmt.Sprintf("path already exists: %s", rawPath)) + } + res[rawPath] = rawFunc } + {{ end }} + return res +} - swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes()) +// 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.Swagger, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.SwaggerLoader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + if spec, ok := resolvePath[pathToFile]; !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } else { + return spec(), nil + } + } + swagger, err = loader.LoadSwaggerFromData(spec) if err != nil { - return nil, fmt.Errorf("error loading Swagger: %s", err) + return } - return swagger, nil + return } diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go index 4a6af4a5fd..693d94d42b 100644 --- a/pkg/codegen/templates/templates.gen.go +++ b/pkg/codegen/templates/templates.gen.go @@ -734,6 +734,8 @@ import ( "path" "strings" "time" + _ "embed" + "github.com/deepmap/oapi-codegen/pkg/runtime" openapi_types "github.com/deepmap/oapi-codegen/pkg/types" @@ -746,34 +748,59 @@ import ( {{- end}} ) `, - "inline.tmpl": `// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ -{{range .}} - "{{.}}",{{end}} + "inline.tmpl": `//go:embed {{ .FileBase }} +var spec []byte + +// returns a raw spec +func RawSpec() []byte { + return spec } -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. -func GetSwagger() (*openapi3.Swagger, 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) +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathPrefix string) map[string]func() []byte { + // todo: fix spec validator so that external references are correct; + // now they can point to api.yaml files whereas the real file name is different + var res = map[string]func() []byte{ + path.Join(pathPrefix, "{{ .FileBase }}"): RawSpec, + {{- if (ne .FileBase "api.yaml") }} + path.Join(pathPrefix, "api.yaml"): RawSpec, + {{- end }} } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) + {{ range $key, $value := .ImportMapping }} + for rawPath, rawFunc := range {{ $value.Name }}.PathToRawSpec(path.Join(pathPrefix, "{{ $value.Path }}")) { + if _, ok := res[rawPath]; ok { + panic(fmt.Sprintf("path already exists: %s", rawPath)) + } + res[rawPath] = rawFunc } + {{ end }} + return res +} - swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes()) +// 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.Swagger, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.SwaggerLoader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + if spec, ok := resolvePath[pathToFile]; !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } else { + return spec(), nil + } + } + swagger, err = loader.LoadSwaggerFromData(spec) if err != nil { - return nil, fmt.Errorf("error loading Swagger: %s", err) + return } - return swagger, nil + return } `, "param-types.tmpl": `{{range .}}{{$opid := .OperationId}} From f12e135feaff0ab3e4b192d40a765a80903d4568 Mon Sep 17 00:00:00 2001 From: Alexey Dudko Date: Fri, 19 Mar 2021 00:06:09 +0100 Subject: [PATCH 2/2] added merge helper --- pkg/util/merge.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 pkg/util/merge.go diff --git a/pkg/util/merge.go b/pkg/util/merge.go new file mode 100644 index 0000000000..5631df723a --- /dev/null +++ b/pkg/util/merge.go @@ -0,0 +1,59 @@ +package util + +import ( + "fmt" + "context" + + "github.com/getkin/kin-openapi/openapi3" +) + +func Merge(ctx context.Context, target *openapi3.Swagger, toMerge *openapi3.Swagger) (err error) { + for pathName, pathItem := range toMerge.Paths { + if _, ok := target.Paths[pathName]; ok { + return fmt.Errorf("path already exists: %s", pathName) + } + target.Paths[pathName] = pathItem + } + if err = target.Validate(ctx); err != nil { + return err + } + return +} + +func MergeFunc(ctx context.Context, target *openapi3.Swagger, toMergeFunc func() (*openapi3.Swagger, error)) (err error) { + var toMerge *openapi3.Swagger + if toMerge, err = toMergeFunc(); err != nil { + return err + } + if err = Merge(ctx, target, toMerge); err != nil { + return err + } + return +} + +// Merge all openapi spec into a single openapi3.Swagger container. +// This is needed for validating request/response based on openapi specification. +func MergeAll(ctx context.Context, toMergeFuncList ...func() (*openapi3.Swagger, error)) (result *openapi3.Swagger, err error) { + for _, toMergeFunc := range toMergeFuncList { + var toMerge *openapi3.Swagger + if toMerge, err = toMergeFunc(); err != nil { + return + } + if err = toMerge.Validate(ctx); err != nil { + return + } + + if result == nil { + result = toMerge + continue + } + + if err = Merge(ctx, result, toMerge); err != nil { + return + } + } + if result == nil { + result = &openapi3.Swagger{} + } + return +}