From 2241a8d9ed53cf08c2f387e50a7b551afdffdf41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Fri, 29 Jan 2021 09:56:22 +0100 Subject: [PATCH] go: vendor the oapi-codegen cmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See the comment in tools.go, I cannot fully explain what's happening here. Somehow, Go 1.14 wants to use the vendored version of oapi-codegen but without this file, oapi-codegen isn't vendored so the generation fails. Signed-off-by: Ondřej Budai --- go.sum | 2 + internal/tools.go | 15 + .../cmd/oapi-codegen/oapi-codegen.go | 173 +++ .../oapi-codegen/pkg/codegen/codegen.go | 572 ++++++++ .../oapi-codegen/pkg/codegen/extension.go | 25 + .../oapi-codegen/pkg/codegen/filter.go | 46 + .../oapi-codegen/pkg/codegen/inline.go | 77 + .../oapi-codegen/pkg/codegen/operations.go | 790 +++++++++++ .../deepmap/oapi-codegen/pkg/codegen/prune.go | 483 +++++++ .../oapi-codegen/pkg/codegen/schema.go | 489 +++++++ .../pkg/codegen/template_helpers.go | 280 ++++ .../templates/additional-properties.tmpl | 70 + .../pkg/codegen/templates/chi-handler.tmpl | 17 + .../pkg/codegen/templates/chi-interface.tmpl | 7 + .../pkg/codegen/templates/chi-middleware.tmpl | 167 +++ .../templates/client-with-responses.tmpl | 115 ++ .../pkg/codegen/templates/client.tmpl | 273 ++++ .../oapi-codegen/pkg/codegen/templates/doc.go | 3 + .../pkg/codegen/templates/imports.tmpl | 10 + .../pkg/codegen/templates/inline.tmpl | 29 + .../pkg/codegen/templates/param-types.tmpl | 6 + .../pkg/codegen/templates/register.tmpl | 27 + .../pkg/codegen/templates/request-bodies.tmpl | 6 + .../codegen/templates/server-interface.tmpl | 7 + .../pkg/codegen/templates/templates.gen.go | 914 ++++++++++++ .../pkg/codegen/templates/typedef.tmpl | 13 + .../pkg/codegen/templates/wrappers.tmpl | 128 ++ .../deepmap/oapi-codegen/pkg/codegen/utils.go | 534 +++++++ .../oapi-codegen/pkg/util/inputmapping.go | 60 + .../deepmap/oapi-codegen/pkg/util/loader.go | 20 + vendor/github.com/getkin/kin-openapi/LICENSE | 21 + .../getkin/kin-openapi/jsoninfo/doc.go | 2 + .../getkin/kin-openapi/jsoninfo/field_info.go | 122 ++ .../getkin/kin-openapi/jsoninfo/marshal.go | 162 +++ .../kin-openapi/jsoninfo/marshal_ref.go | 30 + .../kin-openapi/jsoninfo/strict_struct.go | 6 + .../getkin/kin-openapi/jsoninfo/type_info.go | 68 + .../getkin/kin-openapi/jsoninfo/unmarshal.go | 121 ++ .../jsoninfo/unsupported_properties_error.go | 45 + .../getkin/kin-openapi/openapi3/callback.go | 15 + .../getkin/kin-openapi/openapi3/components.go | 104 ++ .../getkin/kin-openapi/openapi3/content.go | 77 + .../kin-openapi/openapi3/discriminator.go | 26 + .../getkin/kin-openapi/openapi3/doc.go | 5 + .../getkin/kin-openapi/openapi3/encoding.go | 93 ++ .../getkin/kin-openapi/openapi3/examples.go | 29 + .../getkin/kin-openapi/openapi3/extension.go | 38 + .../kin-openapi/openapi3/external_docs.go | 21 + .../getkin/kin-openapi/openapi3/header.go | 33 + .../getkin/kin-openapi/openapi3/info.go | 93 ++ .../getkin/kin-openapi/openapi3/link.go | 38 + .../getkin/kin-openapi/openapi3/media_type.go | 79 ++ .../getkin/kin-openapi/openapi3/operation.go | 102 ++ .../getkin/kin-openapi/openapi3/parameter.go | 224 +++ .../getkin/kin-openapi/openapi3/path_item.go | 126 ++ .../getkin/kin-openapi/openapi3/paths.go | 122 ++ .../getkin/kin-openapi/openapi3/refs.go | 199 +++ .../kin-openapi/openapi3/request_body.go | 69 + .../getkin/kin-openapi/openapi3/response.go | 92 ++ .../getkin/kin-openapi/openapi3/schema.go | 1235 +++++++++++++++++ .../kin-openapi/openapi3/schema_formats.go | 38 + .../openapi3/security_requirements.go | 43 + .../kin-openapi/openapi3/security_scheme.go | 214 +++ .../openapi3/serialization_method.go | 17 + .../getkin/kin-openapi/openapi3/server.go | 148 ++ .../getkin/kin-openapi/openapi3/swagger.go | 104 ++ .../kin-openapi/openapi3/swagger_loader.go | 887 ++++++++++++ .../getkin/kin-openapi/openapi3/tag.go | 20 + vendor/github.com/ghodss/yaml/.gitignore | 20 + vendor/github.com/ghodss/yaml/.travis.yml | 7 + vendor/github.com/ghodss/yaml/LICENSE | 50 + vendor/github.com/ghodss/yaml/README.md | 121 ++ vendor/github.com/ghodss/yaml/fields.go | 501 +++++++ vendor/github.com/ghodss/yaml/yaml.go | 277 ++++ vendor/modules.txt | 9 + 75 files changed, 11211 insertions(+) create mode 100644 internal/tools.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/cmd/oapi-codegen/oapi-codegen.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/codegen.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/extension.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/filter.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/inline.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/operations.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/prune.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/schema.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/template_helpers.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/additional-properties.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-handler.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-interface.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-middleware.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client-with-responses.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/doc.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/imports.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/inline.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/param-types.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/register.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/request-bodies.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/server-interface.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/templates.gen.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/typedef.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/wrappers.tmpl create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/codegen/utils.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/util/inputmapping.go create mode 100644 vendor/github.com/deepmap/oapi-codegen/pkg/util/loader.go create mode 100644 vendor/github.com/getkin/kin-openapi/LICENSE create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/doc.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/strict_struct.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/type_info.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go create mode 100644 vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/callback.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/components.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/content.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/doc.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/encoding.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/examples.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/extension.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/header.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/info.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/link.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/media_type.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/operation.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/parameter.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/path_item.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/paths.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/refs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/request_body.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/response.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/schema.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/server.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/swagger.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/tag.go create mode 100644 vendor/github.com/ghodss/yaml/.gitignore create mode 100644 vendor/github.com/ghodss/yaml/.travis.yml create mode 100644 vendor/github.com/ghodss/yaml/LICENSE create mode 100644 vendor/github.com/ghodss/yaml/README.md create mode 100644 vendor/github.com/ghodss/yaml/fields.go create mode 100644 vendor/github.com/ghodss/yaml/yaml.go diff --git a/go.sum b/go.sum index 464c7de8c..9f1661d72 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,7 @@ github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAU github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -160,6 +161,7 @@ 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/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371 h1:Cjq6sG3gnKDchzWy7ouGQklhxMtWvh4AhSNJ0qGIeo4= golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/tools.go b/internal/tools.go new file mode 100644 index 000000000..5bab31a5b --- /dev/null +++ b/internal/tools.go @@ -0,0 +1,15 @@ +// +build tools + +// This file is here to just explicitly tell `go mod vendor` that we depend +// on oapi-codegen. Without this file, `go generate ./...` in Go >= 1.14 gets +// confused because oapi-codegen is not being vendored. +// +// This is apparently the conventional way, see: +// https://stackoverflow.com/questions/52428230/how-do-go-modules-work-with-installable-commands +// https://github.com/golang/go/issues/29516 + +package main + +import ( + _ "github.com/deepmap/oapi-codegen/cmd/oapi-codegen" +) diff --git a/vendor/github.com/deepmap/oapi-codegen/cmd/oapi-codegen/oapi-codegen.go b/vendor/github.com/deepmap/oapi-codegen/cmd/oapi-codegen/oapi-codegen.go new file mode 100644 index 000000000..50816de95 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/cmd/oapi-codegen/oapi-codegen.go @@ -0,0 +1,173 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/codegen" + "github.com/deepmap/oapi-codegen/pkg/util" +) + +func errExit(format string, args ...interface{}) { + _, _ = fmt.Fprintf(os.Stderr, format, args...) + os.Exit(1) +} + +func main() { + var ( + packageName string + generate string + outputFile string + includeTags string + excludeTags string + templatesDir string + importMapping string + excludeSchemas string + ) + 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", "chi-server", "server", "spec", "skip-fmt", "skip-prune"`) + flag.StringVar(&outputFile, "o", "", "Where to output generated code, stdout is default") + 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.StringVar(&excludeSchemas, "exclude-schemas", "", "A comma separated list of schemas which must be excluded from generation") + flag.Parse() + + if flag.NArg() < 1 { + fmt.Println("Please specify a path to a OpenAPI 3.0 spec file") + os.Exit(1) + } + + // If the package name has not been specified, we will use the name of the + // swagger file. + if packageName == "" { + path := flag.Arg(0) + baseName := filepath.Base(path) + // Split the base name on '.' to get the first part of the file. + nameParts := strings.Split(baseName, ".") + packageName = codegen.ToCamelCase(nameParts[0]) + } + + opts := codegen.Options{} + for _, g := range splitCSVArg(generate) { + switch g { + case "client": + opts.GenerateClient = true + case "chi-server": + opts.GenerateChiServer = true + case "server": + opts.GenerateEchoServer = true + case "types": + opts.GenerateTypes = true + case "spec": + opts.EmbedSpec = true + case "skip-fmt": + opts.SkipFmt = true + case "skip-prune": + opts.SkipPrune = true + default: + fmt.Printf("unknown generate option %s\n", g) + flag.PrintDefaults() + os.Exit(1) + } + } + + opts.IncludeTags = splitCSVArg(includeTags) + opts.ExcludeTags = splitCSVArg(excludeTags) + opts.ExcludeSchemas = splitCSVArg(excludeSchemas) + + if opts.GenerateEchoServer && opts.GenerateChiServer { + errExit("can not specify both server and chi-server targets simultaneously") + } + + swagger, err := util.LoadSwagger(flag.Arg(0)) + if err != nil { + errExit("error loading swagger spec\n: %s", err) + } + + templates, err := loadTemplateOverrides(templatesDir) + if err != nil { + errExit("error loading template overrides: %s\n", err) + } + opts.UserTemplates = templates + + if len(importMapping) > 0 { + opts.ImportMapping, err = util.ParseCommandlineMap(importMapping) + if 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) + } + + if outputFile != "" { + err = ioutil.WriteFile(outputFile, []byte(code), 0644) + if err != nil { + errExit("error writing generated code to file: %s", err) + } + } else { + fmt.Println(code) + } +} + +func splitCSVArg(input string) []string { + input = strings.TrimSpace(input) + if len(input) == 0 { + return nil + } + splitInput := strings.Split(input, ",") + args := make([]string, 0, len(splitInput)) + for _, s := range splitInput { + s = strings.TrimSpace(s) + if len(s) > 0 { + args = append(args, s) + } + } + return args +} + +func loadTemplateOverrides(templatesDir string) (map[string]string, error) { + var templates = make(map[string]string) + + if templatesDir == "" { + return templates, nil + } + + files, err := ioutil.ReadDir(templatesDir) + if err != nil { + return nil, err + } + + for _, f := range files { + data, err := ioutil.ReadFile(path.Join(templatesDir, f.Name())) + if err != nil { + return nil, err + } + templates[f.Name()] = string(data) + } + + return templates, nil +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/codegen.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/codegen.go new file mode 100644 index 000000000..7dc51012a --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/codegen.go @@ -0,0 +1,572 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package codegen + +import ( + "bufio" + "bytes" + "fmt" + "go/format" + "regexp" + "sort" + "strings" + "text/template" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/pkg/errors" + + "github.com/deepmap/oapi-codegen/pkg/codegen/templates" +) + +// Options defines the optional code to generate. +type Options struct { + GenerateChiServer bool // GenerateChiServer specifies whether to generate chi server boilerplate + GenerateEchoServer bool // GenerateEchoServer specifies whether to generate echo 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 + SkipFmt bool // Whether to skip go fmt on the generated code + SkipPrune bool // Whether to skip pruning unused components on the generated code + 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 + ExcludeSchemas []string // Exclude from generation schemas with given names. Ignored when empty. +} + +type goImport struct { + lookFor string + alias string + packageName string +} + +func (i goImport) String() string { + if i.alias != "" { + return fmt.Sprintf("%s %q", i.alias, i.packageName) + } + return fmt.Sprintf("%q", i.packageName) +} + +type goImports []goImport + +var ( + allGoImports = goImports{ + {lookFor: "base64\\.", packageName: "encoding/base64"}, + {lookFor: "bytes\\.", packageName: "bytes"}, + {lookFor: "chi\\.", packageName: "github.com/go-chi/chi"}, + {lookFor: "context\\.", packageName: "context"}, + {lookFor: "echo\\.", packageName: "github.com/labstack/echo/v4"}, + {lookFor: "errors\\.", packageName: "github.com/pkg/errors"}, + {lookFor: "fmt\\.", packageName: "fmt"}, + {lookFor: "gzip\\.", packageName: "compress/gzip"}, + {lookFor: "http\\.", packageName: "net/http"}, + {lookFor: "io\\.", packageName: "io"}, + {lookFor: "ioutil\\.", packageName: "io/ioutil"}, + {lookFor: "json\\.", packageName: "encoding/json"}, + {lookFor: "openapi3\\.", packageName: "github.com/getkin/kin-openapi/openapi3"}, + {lookFor: "openapi_types\\.", alias: "openapi_types", packageName: "github.com/deepmap/oapi-codegen/pkg/types"}, + {lookFor: "path\\.", packageName: "path"}, + {lookFor: "runtime\\.", packageName: "github.com/deepmap/oapi-codegen/pkg/runtime"}, + {lookFor: "strings\\.", packageName: "strings"}, + {lookFor: "time\\.Duration", packageName: "time"}, + {lookFor: "time\\.Time", packageName: "time"}, + {lookFor: "url\\.", packageName: "net/url"}, + {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) + } + + // This creates the golang templates text package + t := template.New("oapi-codegen").Funcs(TemplateFunctions) + // This parses all of our own template files into the template object + // above + t, err := templates.Parse(t) + if err != nil { + return "", errors.Wrap(err, "error parsing oapi-codegen templates") + } + + // Override built-in templates with user-provided versions + for _, tpl := range t.Templates() { + if _, ok := opts.UserTemplates[tpl.Name()]; ok { + utpl := t.New(tpl.Name()) + if _, err := utpl.Parse(opts.UserTemplates[tpl.Name()]); err != nil { + return "", errors.Wrapf(err, "error parsing user-provided template %q", tpl.Name()) + } + } + } + + ops, err := OperationDefinitions(swagger) + if err != nil { + return "", errors.Wrap(err, "error creating operation definitions") + } + + var typeDefinitions string + if opts.GenerateTypes { + typeDefinitions, err = GenerateTypeDefinitions(t, swagger, ops, opts.ExcludeSchemas) + if err != nil { + return "", errors.Wrap(err, "error generating type definitions") + } + } + + var echoServerOut string + if opts.GenerateEchoServer { + echoServerOut, err = GenerateEchoServer(t, ops) + if err != nil { + return "", errors.Wrap(err, "error generating Go handlers for Paths") + } + } + + var chiServerOut string + if opts.GenerateChiServer { + chiServerOut, err = GenerateChiServer(t, ops) + if err != nil { + return "", errors.Wrap(err, "error generating Go handlers for Paths") + } + } + + var clientOut string + if opts.GenerateClient { + clientOut, err = GenerateClient(t, ops) + if err != nil { + return "", errors.Wrap(err, "error generating client") + } + } + + var clientWithResponsesOut string + if opts.GenerateClient { + clientWithResponsesOut, err = GenerateClientWithResponses(t, ops) + if err != nil { + return "", errors.Wrap(err, "error generating client with responses") + } + } + + var inlinedSpec string + if opts.EmbedSpec { + inlinedSpec, err = GenerateInlinedSpec(t, swagger) + if err != nil { + return "", errors.Wrap(err, "error generating Go handlers for Paths") + } + } + + // 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) + + // Based on module prefixes, figure out which optional imports are required. + pkgs := make(map[string]int) + for _, str := range []string{typeDefinitions, chiServerOut, echoServerOut, clientOut, clientWithResponsesOut, inlinedSpec} { + for _, line := range strings.Split(strings.TrimSpace(str), "\n") { + line = strings.TrimSpace(line) + + if line == "" || strings.HasPrefix(line, "//") { + continue + } + + for _, goImport := range allGoImports { + match, err := regexp.MatchString(fmt.Sprintf("[^a-zA-Z0-9_]%s", goImport.lookFor), line) + if err != nil { + return "", errors.Wrap(err, "error figuring out imports") + } + + if match { + pkgs[goImport.String()]++ + } + } + } + } + + for k := range pkgs { + imports = append(imports, k) + } + + importsOut, err := GenerateImports(t, imports, packageName) + if err != nil { + return "", errors.Wrap(err, "error generating imports") + } + + _, err = w.WriteString(importsOut) + if err != nil { + return "", errors.Wrap(err, "error writing imports") + } + + _, err = w.WriteString(typeDefinitions) + if err != nil { + return "", errors.Wrap(err, "error writing type definitions") + + } + + if opts.GenerateClient { + _, err = w.WriteString(clientOut) + if err != nil { + return "", errors.Wrap(err, "error writing client") + } + _, err = w.WriteString(clientWithResponsesOut) + if err != nil { + return "", errors.Wrap(err, "error writing client") + } + } + + if opts.GenerateEchoServer { + _, err = w.WriteString(echoServerOut) + if err != nil { + return "", errors.Wrap(err, "error writing server path handlers") + } + } + + if opts.GenerateChiServer { + _, err = w.WriteString(chiServerOut) + if err != nil { + return "", errors.Wrap(err, "error writing server path handlers") + } + } + + if opts.EmbedSpec { + _, err = w.WriteString(inlinedSpec) + if err != nil { + return "", errors.Wrap(err, "error writing inlined spec") + } + } + + err = w.Flush() + if err != nil { + return "", errors.Wrap(err, "error flushing output buffer") + } + + // remove any byte-order-marks which break Go-Code + goCode := SanitizeCode(buf.String()) + + // The generation code produces unindented horrors. Use the Go formatter + // to make it all pretty. + if opts.SkipFmt { + return goCode, nil + } + outBytes, err := format.Source([]byte(goCode)) + if err != nil { + fmt.Println(goCode) + return "", errors.Wrap(err, "error formatting Go code") + } + return string(outBytes), nil +} + +func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.Swagger, ops []OperationDefinition, excludeSchemas []string) (string, error) { + schemaTypes, err := GenerateTypesForSchemas(t, swagger.Components.Schemas, excludeSchemas) + if err != nil { + return "", errors.Wrap(err, "error generating Go types for component schemas") + } + + paramTypes, err := GenerateTypesForParameters(t, swagger.Components.Parameters) + 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) + 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) + if err != nil { + return "", errors.Wrap(err, "error generating Go types for component request bodies") + } + allTypes = append(allTypes, bodyTypes...) + + paramTypesOut, err := GenerateTypesForOperations(t, ops) + if err != nil { + return "", errors.Wrap(err, "error generating Go types for operation parameters") + } + + typesOut, err := GenerateTypes(t, allTypes) + if err != nil { + return "", errors.Wrap(err, "error generating code for type definitions") + } + + allOfBoilerplate, err := GenerateAdditionalPropertyBoilerplate(t, allTypes) + if err != nil { + return "", errors.Wrap(err, "error generating allOf boilerplate") + } + + typeDefinitions := strings.Join([]string{typesOut, paramTypesOut, allOfBoilerplate}, "") + return typeDefinitions, nil +} + +// 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, excludeSchemas []string) ([]TypeDefinition, error) { + var excludeSchemasMap = make(map[string]bool) + for _, schema := range excludeSchemas { + excludeSchemasMap[schema] = true + } + types := make([]TypeDefinition, 0) + // We're going to define Go types for every object under components/schemas + for _, schemaName := range SortedSchemaKeys(schemas) { + if _, ok := excludeSchemasMap[schemaName]; ok { + continue + } + schemaRef := schemas[schemaName] + + goSchema, err := GenerateGoSchema(schemaRef, []string{schemaName}) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error converting Schema %s to Go type", schemaName)) + } + + types = append(types, TypeDefinition{ + JsonName: schemaName, + TypeName: SchemaNameToTypeName(schemaName), + Schema: goSchema, + }) + + types = append(types, goSchema.GetAdditionalTypeDefs()...) + } + return types, nil +} + +// 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) { + var types []TypeDefinition + for _, paramName := range SortedParameterKeys(params) { + paramOrRef := params[paramName] + + goType, err := paramToGoType(paramOrRef.Value, nil) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for schema in parameter %s", paramName)) + } + + typeDef := TypeDefinition{ + JsonName: paramName, + Schema: goType, + TypeName: SchemaNameToTypeName(paramName), + } + + if paramOrRef.Ref != "" { + // Generate a reference type for referenced parameters + refType, err := RefPathToGoType(paramOrRef.Ref) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for (%s) in parameter %s", paramOrRef.Ref, paramName)) + } + typeDef.TypeName = SchemaNameToTypeName(refType) + } + + types = append(types, typeDef) + } + return types, nil +} + +// 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) { + var types []TypeDefinition + + for _, responseName := range SortedResponsesKeys(responses) { + responseOrRef := responses[responseName] + + // We have to generate the response object. We're only going to + // handle application/json media types here. Other responses should + // simply be specified as strings or byte arrays. + response := responseOrRef.Value + jsonResponse, found := response.Content["application/json"] + if found { + goType, err := GenerateGoSchema(jsonResponse.Schema, []string{responseName}) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for schema in response %s", responseName)) + } + + typeDef := TypeDefinition{ + JsonName: responseName, + Schema: goType, + TypeName: SchemaNameToTypeName(responseName), + } + + if responseOrRef.Ref != "" { + // Generate a reference type for referenced parameters + refType, err := RefPathToGoType(responseOrRef.Ref) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for (%s) in parameter %s", responseOrRef.Ref, responseName)) + } + typeDef.TypeName = SchemaNameToTypeName(refType) + } + types = append(types, typeDef) + } + } + return types, nil +} + +// 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) { + var types []TypeDefinition + + for _, bodyName := range SortedRequestBodyKeys(bodies) { + bodyOrRef := bodies[bodyName] + + // As for responses, we will only generate Go code for JSON bodies, + // the other body formats are up to the user. + response := bodyOrRef.Value + jsonBody, found := response.Content["application/json"] + if found { + goType, err := GenerateGoSchema(jsonBody.Schema, []string{bodyName}) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for schema in body %s", bodyName)) + } + + typeDef := TypeDefinition{ + JsonName: bodyName, + Schema: goType, + TypeName: SchemaNameToTypeName(bodyName), + } + + if bodyOrRef.Ref != "" { + // Generate a reference type for referenced bodies + refType, err := RefPathToGoType(bodyOrRef.Ref) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error generating Go type for (%s) in body %s", bodyOrRef.Ref, bodyName)) + } + typeDef.TypeName = SchemaNameToTypeName(refType) + } + types = append(types, typeDef) + } + } + return types, nil +} + +// Helper function to pass a bunch of types to the template engine, and buffer +// its output into a string. +func GenerateTypes(t *template.Template, types []TypeDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + context := struct { + Types []TypeDefinition + }{ + Types: types, + } + + err := t.ExecuteTemplate(w, "typedef.tmpl", context) + if err != nil { + return "", errors.Wrap(err, "error generating types") + } + err = w.Flush() + if err != nil { + return "", errors.Wrap(err, "error flushing output buffer for types") + } + return buf.String(), nil +} + +// Generate our import statements and package definition. +func GenerateImports(t *template.Template, imports []string, packageName string) (string, error) { + sort.Strings(imports) + + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + context := struct { + Imports []string + PackageName string + }{ + Imports: imports, + PackageName: packageName, + } + err := t.ExecuteTemplate(w, "imports.tmpl", context) + if err != nil { + return "", errors.Wrap(err, "error generating imports") + } + err = w.Flush() + if err != nil { + return "", errors.Wrap(err, "error flushing output buffer for imports") + } + return buf.String(), nil +} + +// Generate all the glue code which provides the API for interacting with +// additional properties and JSON-ification +func GenerateAdditionalPropertyBoilerplate(t *template.Template, typeDefs []TypeDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + var filteredTypes []TypeDefinition + for _, t := range typeDefs { + if t.Schema.HasAdditionalProperties { + filteredTypes = append(filteredTypes, t) + } + } + + context := struct { + Types []TypeDefinition + }{ + Types: filteredTypes, + } + + err := t.ExecuteTemplate(w, "additional-properties.tmpl", context) + if err != nil { + return "", errors.Wrap(err, "error generating additional properties code") + } + err = w.Flush() + if err != nil { + return "", errors.Wrap(err, "error flushing output buffer for additional properties") + } + return buf.String(), nil +} + +// SanitizeCode runs sanitizers across the generated Go code to ensure the +// generated code will be able to compile. +func SanitizeCode(goCode string) string { + // remove any byte-order-marks which break Go-Code + // See: https://groups.google.com/forum/#!topic/golang-nuts/OToNIPdfkks + return strings.Replace(goCode, "\uFEFF", "", -1) +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/extension.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/extension.go new file mode 100644 index 000000000..4aaca67be --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/extension.go @@ -0,0 +1,25 @@ +package codegen + +import ( + "encoding/json" + "fmt" + + "github.com/pkg/errors" +) + +const ( + extPropGoType = "x-go-type" +) + +func extTypeName(extPropValue interface{}) (string, error) { + raw, ok := extPropValue.(json.RawMessage) + if !ok { + return "", fmt.Errorf("failed to convert type: %T", extPropValue) + } + var name string + if err := json.Unmarshal(raw, &name); err != nil { + return "", errors.Wrap(err, "failed to unmarshal json") + } + + return name, nil +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/filter.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/filter.go new file mode 100644 index 000000000..61857d2a8 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/filter.go @@ -0,0 +1,46 @@ +package codegen + +import "github.com/getkin/kin-openapi/openapi3" + +func filterOperationsByTag(swagger *openapi3.Swagger, opts Options) { + if len(opts.ExcludeTags) > 0 { + excludeOperationsWithTags(swagger.Paths, opts.ExcludeTags) + } + if len(opts.IncludeTags) > 0 { + includeOperationsWithTags(swagger.Paths, opts.IncludeTags, false) + } +} + +func excludeOperationsWithTags(paths openapi3.Paths, tags []string) { + includeOperationsWithTags(paths, tags, true) +} + +func includeOperationsWithTags(paths openapi3.Paths, tags []string, exclude bool) { + for _, pathItem := range paths { + ops := pathItem.Operations() + names := make([]string, 0, len(ops)) + for name, op := range ops { + if operationHasTag(op, tags) == exclude { + names = append(names, name) + } + } + for _, name := range names { + pathItem.SetOperation(name, nil) + } + } +} + +//operationHasTag returns true if the operation is tagged with any of tags +func operationHasTag(op *openapi3.Operation, tags []string) bool { + if op == nil { + return false + } + for _, hasTag := range op.Tags { + for _, wantTag := range tags { + if hasTag == wantTag { + return true + } + } + } + return false +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/inline.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/inline.go new file mode 100644 index 000000000..76251c9a9 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/inline.go @@ -0,0 +1,77 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +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) { + // Marshal to json + encoded, err := swagger.MarshalJSON() + if err != nil { + return "", fmt.Errorf("error marshaling swagger: %s", err) + } + + // gzip + 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) + if err != nil { + return "", fmt.Errorf("error generating inlined spec: %s", err) + } + err = w.Flush() + if err != nil { + return "", fmt.Errorf("error flushing output buffer for inlined spec: %s", err) + } + return buf.String(), nil +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/operations.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/operations.go new file mode 100644 index 000000000..916643dd3 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/operations.go @@ -0,0 +1,790 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package codegen + +import ( + "bufio" + "bytes" + "fmt" + "strings" + "text/template" + "unicode" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/pkg/errors" +) + +type ParameterDefinition struct { + ParamName string // The original json parameter name, eg param_name + In string // Where the parameter is defined - path, header, cookie, query + Required bool // Is this a required parameter? + Spec *openapi3.Parameter + Schema Schema +} + +// This function is here as an adapter after a large refactoring so that I don't +// have to update all the templates. It returns the type definition for a parameter, +// without the leading '*' for optional ones. +func (pd ParameterDefinition) TypeDef() string { + typeDecl := pd.Schema.TypeDecl() + return typeDecl +} + +// Generate the JSON annotation to map GoType to json type name. If Parameter +// Foo is marshaled to json as "foo", this will create the annotation +// 'json:"foo"' +func (pd *ParameterDefinition) JsonTag() string { + if pd.Required { + return fmt.Sprintf("`json:\"%s\"`", pd.ParamName) + } else { + return fmt.Sprintf("`json:\"%s,omitempty\"`", pd.ParamName) + } +} + +func (pd *ParameterDefinition) IsJson() bool { + p := pd.Spec + if len(p.Content) == 1 { + _, found := p.Content["application/json"] + return found + } + return false +} + +func (pd *ParameterDefinition) IsPassThrough() bool { + p := pd.Spec + if len(p.Content) > 1 { + return true + } + if len(p.Content) == 1 { + return !pd.IsJson() + } + return false +} + +func (pd *ParameterDefinition) IsStyled() bool { + p := pd.Spec + return p.Schema != nil +} + +func (pd *ParameterDefinition) Style() string { + style := pd.Spec.Style + if style == "" { + in := pd.Spec.In + switch in { + case "path", "header": + return "simple" + case "query", "cookie": + return "form" + default: + panic("unknown parameter format") + } + } + return style +} + +func (pd *ParameterDefinition) Explode() bool { + if pd.Spec.Explode == nil { + in := pd.Spec.In + switch in { + case "path", "header": + return false + case "query", "cookie": + return true + default: + panic("unknown parameter format") + } + } + return *pd.Spec.Explode +} + +func (pd ParameterDefinition) GoVariableName() string { + name := LowercaseFirstCharacter(pd.GoName()) + if IsGoKeyword(name) { + name = "p" + UppercaseFirstCharacter(name) + } + if unicode.IsNumber([]rune(name)[0]) { + name = "n" + name + } + return name +} + +func (pd ParameterDefinition) GoName() string { + return ToCamelCase(pd.ParamName) +} + +func (pd ParameterDefinition) IndirectOptional() bool { + return !pd.Required && !pd.Schema.SkipOptionalPointer +} + +type ParameterDefinitions []ParameterDefinition + +func (p ParameterDefinitions) FindByName(name string) *ParameterDefinition { + for _, param := range p { + if param.ParamName == name { + return ¶m + } + } + return nil +} + +// 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) { + outParams := make([]ParameterDefinition, 0) + for _, paramOrRef := range params { + param := paramOrRef.Value + + goType, err := paramToGoType(param, append(path, param.Name)) + if err != nil { + return nil, fmt.Errorf("error generating type for param (%s): %s", + param.Name, err) + } + + pd := ParameterDefinition{ + ParamName: param.Name, + In: param.In, + Required: param.Required, + Spec: param, + Schema: goType, + } + + // If this is a reference to a predefined type, simply use the reference + // name as the type. $ref: "#/components/schemas/custom_type" becomes + // "CustomType". + if paramOrRef.Ref != "" { + goType, err := RefPathToGoType(paramOrRef.Ref) + if err != nil { + return nil, fmt.Errorf("error dereferencing (%s) for param (%s): %s", + paramOrRef.Ref, param.Name, err) + } + pd.Schema.GoType = goType + } + outParams = append(outParams, pd) + } + return outParams, nil +} + +type SecurityDefinition struct { + ProviderName string + Scopes []string +} + +func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequirements) []SecurityDefinition { + outDefs := make([]SecurityDefinition, 0) + + for _, sr := range securityRequirements { + for k, v := range sr { + outDefs = append(outDefs, SecurityDefinition{ProviderName: k, Scopes: v}) + } + } + + return outDefs +} + +// This structure describes an Operation +type OperationDefinition struct { + OperationId string // The operation_id description from Swagger, used to generate function names + + PathParams []ParameterDefinition // Parameters in the path, eg, /path/:param + HeaderParams []ParameterDefinition // Parameters in HTTP headers + QueryParams []ParameterDefinition // Parameters in the query, /path?param + CookieParams []ParameterDefinition // Parameters in cookies + TypeDefinitions []TypeDefinition // These are all the types we need to define for this operation + SecurityDefinitions []SecurityDefinition // These are the security providers + BodyRequired bool + Bodies []RequestBodyDefinition // The list of bodies for which to generate handlers. + 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} + Spec *openapi3.Operation +} + +// Returns the list of all parameters except Path parameters. Path parameters +// are handled differently from the rest, since they're mandatory. +func (o *OperationDefinition) Params() []ParameterDefinition { + result := append(o.QueryParams, o.HeaderParams...) + result = append(result, o.CookieParams...) + return result +} + +// Returns all parameters +func (o *OperationDefinition) AllParams() []ParameterDefinition { + result := append(o.QueryParams, o.HeaderParams...) + result = append(result, o.CookieParams...) + result = append(result, o.PathParams...) + return result +} + +// If we have parameters other than path parameters, they're bundled into an +// object. Returns true if we have any of those. This is used from the template +// engine. +func (o *OperationDefinition) RequiresParamObject() bool { + return len(o.Params()) > 0 +} + +// This is called by the template engine to determine whether to generate body +// marshaling code on the client. This is true for all body types, whether or +// not we generate types for them. +func (o *OperationDefinition) HasBody() bool { + return o.Spec.RequestBody != nil +} + +// This returns the Operations summary as a multi line comment +func (o *OperationDefinition) SummaryAsComment() string { + if o.Summary == "" { + return "" + } + trimmed := strings.TrimSuffix(o.Summary, "\n") + parts := strings.Split(trimmed, "\n") + for i, p := range parts { + parts[i] = "// " + p + } + return strings.Join(parts, "\n") +} + +// Produces a list of type definitions for a given Operation for the response +// types which we know how to parse. These will be turned into fields on a +// response object for automatic deserialization of responses in the generated +// Client code. See "client-with-responses.tmpl". +func (o *OperationDefinition) GetResponseTypeDefinitions() ([]TypeDefinition, error) { + var tds []TypeDefinition + + responses := o.Spec.Responses + sortedResponsesKeys := SortedResponsesKeys(responses) + for _, responseName := range sortedResponsesKeys { + responseRef := responses[responseName] + + // We can only generate a type if we have a value: + if responseRef.Value != nil { + sortedContentKeys := SortedContentKeys(responseRef.Value.Content) + for _, contentTypeName := range sortedContentKeys { + 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}) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("Unable to determine Go type for %s.%s", o.OperationId, contentTypeName)) + } + + var typeName string + switch { + case StringInArray(contentTypeName, contentTypesJSON): + typeName = fmt.Sprintf("JSON%s", ToCamelCase(responseName)) + // YAML: + case StringInArray(contentTypeName, contentTypesYAML): + typeName = fmt.Sprintf("YAML%s", ToCamelCase(responseName)) + // XML: + case StringInArray(contentTypeName, contentTypesXML): + typeName = fmt.Sprintf("XML%s", ToCamelCase(responseName)) + default: + continue + } + + td := TypeDefinition{ + TypeName: typeName, + Schema: responseSchema, + ResponseName: responseName, + } + if contentType.Schema.Ref != "" { + refType, err := RefPathToGoType(contentType.Schema.Ref) + if err != nil { + return nil, errors.Wrap(err, "error dereferencing response Ref") + } + td.Schema.RefType = refType + } + tds = append(tds, td) + } + } + } + } + return tds, nil +} + +// This describes a request body +type RequestBodyDefinition struct { + // Is this body required, or optional? + Required bool + + // This is the schema describing this body + Schema Schema + + // When we generate type names, we need a Tag for it, such as JSON, in + // which case we will produce "JSONBody". + NameTag string + + // This is the content type corresponding to the body, eg, application/json + ContentType string + + // Whether this is the default body type. For an operation named OpFoo, we + // will not add suffixes like OpFooJSONBody for this one. + Default bool +} + +// Returns the Go type definition for a request body +func (r RequestBodyDefinition) TypeDef() string { + return r.Schema.TypeDecl() +} + +// Returns whether the body is a custom inline type, or pre-defined. This is +// poorly named, but it's here for compatibility reasons post-refactoring +// TODO: clean up the templates code, it can be simpler. +func (r RequestBodyDefinition) CustomType() bool { + return r.Schema.RefType == "" +} + +// When we're generating multiple functions which relate to request bodies, +// this generates the suffix. Such as Operation DoFoo would be suffixed with +// DoFooWithXMLBody. +func (r RequestBodyDefinition) Suffix() string { + // The default response is never suffixed. + if r.Default { + return "" + } + return "With" + r.NameTag + "Body" +} + +// This function returns the subset of the specified parameters which are of the +// specified type. +func FilterParameterDefinitionByType(params []ParameterDefinition, in string) []ParameterDefinition { + var out []ParameterDefinition + for _, p := range params { + if p.In == in { + out = append(out, p) + } + } + return out +} + +// OperationDefinitions returns all operations for a swagger definition. +func OperationDefinitions(swagger *openapi3.Swagger) ([]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) + if err != nil { + return nil, fmt.Errorf("error describing global parameters for %s: %s", + requestPath, err) + } + + // Each path can have a number of operations, POST, GET, OPTIONS, etc. + pathOps := pathItem.Operations() + for _, opName := range SortedOperationsKeys(pathOps) { + op := pathOps[opName] + // We rely on OperationID to generate function names, it's required + if op.OperationID == "" { + op.OperationID, err = generateDefaultOperationID(opName, requestPath) + if err != nil { + return nil, fmt.Errorf("error generating default OperationID for %s/%s: %s", + opName, requestPath, err) + } + op.OperationID = op.OperationID + } else { + op.OperationID = ToCamelCase(op.OperationID) + } + + // These are parameters defined for the specific path method that + // we're iterating over. + localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"}) + if err != nil { + return nil, fmt.Errorf("error describing global parameters for %s/%s: %s", + opName, requestPath, err) + } + // All the parameters required by a handler are the union of the + // global parameters and the local parameters. + allParams := append(globalParams, localParams...) + + // Order the path parameters to match the order as specified in + // the path, not in the swagger spec, and validate that the parameter + // names match, as downstream code depends on that. + pathParams := FilterParameterDefinitionByType(allParams, "path") + pathParams, err = SortParamsByPath(requestPath, pathParams) + if err != nil { + return nil, err + } + + bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody) + if err != nil { + return nil, errors.Wrap(err, "error generating body definitions") + } + + opDef := OperationDefinition{ + PathParams: pathParams, + HeaderParams: FilterParameterDefinitionByType(allParams, "header"), + QueryParams: FilterParameterDefinitionByType(allParams, "query"), + CookieParams: FilterParameterDefinitionByType(allParams, "cookie"), + OperationId: ToCamelCase(op.OperationID), + // Replace newlines in summary. + Summary: op.Summary, + Method: opName, + Path: requestPath, + Spec: op, + Bodies: bodyDefinitions, + TypeDefinitions: typeDefinitions, + } + + // check for overrides of SecurityDefinitions. + // See: "Step 2. Applying security:" from the spec: + // https://swagger.io/docs/specification/authentication/ + if op.Security != nil { + opDef.SecurityDefinitions = DescribeSecurityDefinition(*op.Security) + } else { + // use global securityDefinitions + // globalSecurityDefinitions contains the top-level securityDefinitions. + // They are the default securityPermissions which are injected into each + // path, except for the case where a path explicitly overrides them. + opDef.SecurityDefinitions = DescribeSecurityDefinition(swagger.Security) + + } + + if op.RequestBody != nil { + opDef.BodyRequired = op.RequestBody.Value.Required + } + + // Generate all the type definitions needed for this operation + opDef.TypeDefinitions = append(opDef.TypeDefinitions, GenerateTypeDefsForOperation(opDef)...) + + operations = append(operations, opDef) + } + } + return operations, nil +} + +func generateDefaultOperationID(opName string, requestPath string) (string, error) { + var operationId string = strings.ToLower(opName) + + if opName == "" { + return "", fmt.Errorf("operation name cannot be an empty string") + } + + if requestPath == "" { + return "", fmt.Errorf("request path cannot be an empty string") + } + + for _, part := range strings.Split(requestPath, "/") { + if part != "" { + operationId = operationId + "-" + part + } + } + + return ToCamelCase(operationId), nil +} + +// 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) { + if bodyOrRef == nil { + return nil, nil, nil + } + body := bodyOrRef.Value + + var bodyDefinitions []RequestBodyDefinition + var typeDefinitions []TypeDefinition + + for contentType, content := range body.Content { + var tag string + var defaultBody bool + + switch contentType { + case "application/json": + tag = "JSON" + defaultBody = true + default: + continue + } + + bodyTypeName := operationID + tag + "Body" + bodySchema, err := GenerateGoSchema(content.Schema, []string{bodyTypeName}) + if err != nil { + return nil, nil, errors.Wrap(err, "error generating request body definition") + } + + // If the body is a pre-defined type + if bodyOrRef.Ref != "" { + // Convert the reference path to Go type + refType, err := RefPathToGoType(bodyOrRef.Ref) + if err != nil { + return nil, nil, errors.Wrap(err, fmt.Sprintf("error turning reference (%s) into a Go type", bodyOrRef.Ref)) + } + bodySchema.RefType = refType + } + + // If the request has a body, but it's not a user defined + // type under #/components, we'll define a type for it, so + // that we have an easy to use type for marshaling. + if bodySchema.RefType == "" { + td := TypeDefinition{ + TypeName: bodyTypeName, + Schema: bodySchema, + } + typeDefinitions = append(typeDefinitions, td) + // The body schema now is a reference to a type + bodySchema.RefType = bodyTypeName + } + + bd := RequestBodyDefinition{ + Required: body.Required, + Schema: bodySchema, + NameTag: tag, + ContentType: contentType, + Default: defaultBody, + } + bodyDefinitions = append(bodyDefinitions, bd) + } + return bodyDefinitions, typeDefinitions, nil +} + +func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { + var typeDefs []TypeDefinition + // Start with the params object itself + if len(op.Params()) != 0 { + typeDefs = append(typeDefs, GenerateParamsTypes(op)...) + } + + // Now, go through all the additional types we need to declare. + for _, param := range op.AllParams() { + typeDefs = append(typeDefs, param.Schema.GetAdditionalTypeDefs()...) + } + + for _, body := range op.Bodies { + typeDefs = append(typeDefs, body.Schema.GetAdditionalTypeDefs()...) + } + return typeDefs +} + +// This defines the schema for a parameters definition object which encapsulates +// all the query, header and cookie parameters for an operation. +func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { + var typeDefs []TypeDefinition + + objectParams := op.QueryParams + objectParams = append(objectParams, op.HeaderParams...) + objectParams = append(objectParams, op.CookieParams...) + + typeName := op.OperationId + "Params" + + s := Schema{} + for _, param := range objectParams { + pSchema := param.Schema + if pSchema.HasAdditionalProperties { + propRefName := strings.Join([]string{typeName, param.GoName()}, "_") + pSchema.RefType = propRefName + typeDefs = append(typeDefs, TypeDefinition{ + TypeName: propRefName, + Schema: param.Schema, + }) + } + prop := Property{ + Description: param.Spec.Description, + JsonFieldName: param.ParamName, + Required: param.Required, + Schema: pSchema, + } + s.Properties = append(s.Properties, prop) + } + + s.GoType = GenStructFromSchema(s) + + td := TypeDefinition{ + TypeName: typeName, + Schema: s, + } + return append(typeDefs, td) +} + +// Generates code for all types produced +func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "param-types.tmpl", ops) + if err != nil { + return "", errors.Wrap(err, "error generating types for params objects") + } + + err = t.ExecuteTemplate(w, "request-bodies.tmpl", ops) + if err != nil { + return "", errors.Wrap(err, "error generating request bodies for operations") + } + + // Generate boiler plate for all additional types. + var td []TypeDefinition + for _, op := range ops { + td = append(td, op.TypeDefinitions...) + } + + addProps, err := GenerateAdditionalPropertyBoilerplate(t, td) + if err != nil { + return "", errors.Wrap(err, "error generating additional properties boilerplate for operations") + } + + _, err = w.WriteString("\n") + if err != nil { + return "", errors.Wrap(err, "error generating additional properties boilerplate for operations") + } + + _, err = w.WriteString(addProps) + if err != nil { + return "", errors.Wrap(err, "error generating additional properties boilerplate for operations") + } + + err = w.Flush() + if err != nil { + return "", errors.Wrap(err, "error flushing output buffer for server interface") + } + + return buf.String(), nil +} + +// GenerateChiServer This function generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateChiServer(t *template.Template, operations []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "chi-interface.tmpl", operations) + if err != nil { + return "", errors.Wrap(err, "error generating server interface") + } + + err = t.ExecuteTemplate(w, "chi-middleware.tmpl", operations) + if err != nil { + return "", errors.Wrap(err, "error generating server middleware") + } + + err = t.ExecuteTemplate(w, "chi-handler.tmpl", operations) + if err != nil { + return "", errors.Wrap(err, "error generating server http handler") + } + + err = w.Flush() + if err != nil { + return "", errors.Wrap(err, "error flushing output buffer for server") + } + + return buf.String(), nil +} + +// GenerateEchoServer This function generates all the go code for the ServerInterface as well as +// all the wrapper functions around our handlers. +func GenerateEchoServer(t *template.Template, operations []OperationDefinition) (string, error) { + si, err := GenerateServerInterface(t, operations) + if err != nil { + return "", fmt.Errorf("Error generating server types and interface: %s", err) + } + + wrappers, err := GenerateWrappers(t, operations) + if err != nil { + return "", fmt.Errorf("Error generating handler wrappers: %s", err) + } + + register, err := GenerateRegistration(t, operations) + if err != nil { + return "", fmt.Errorf("Error generating handler registration: %s", err) + } + return strings.Join([]string{si, wrappers, register}, "\n"), nil +} + +// Uses the template engine to generate the server interface +func GenerateServerInterface(t *template.Template, ops []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "server-interface.tmpl", ops) + + if err != nil { + return "", fmt.Errorf("error generating server interface: %s", err) + } + err = w.Flush() + if err != nil { + return "", fmt.Errorf("error flushing output buffer for server interface: %s", err) + } + return buf.String(), nil +} + +// Uses the template engine to generate all the wrappers which wrap our simple +// interface functions and perform marshallin/unmarshalling from HTTP +// request objects. +func GenerateWrappers(t *template.Template, ops []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "wrappers.tmpl", ops) + + if err != nil { + return "", fmt.Errorf("error generating server interface: %s", err) + } + err = w.Flush() + if err != nil { + return "", fmt.Errorf("error flushing output buffer for server interface: %s", err) + } + return buf.String(), nil +} + +// Uses the template engine to generate the function which registers our wrappers +// as Echo path handlers. +func GenerateRegistration(t *template.Template, ops []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "register.tmpl", ops) + + if err != nil { + return "", fmt.Errorf("error generating route registration: %s", err) + } + err = w.Flush() + if err != nil { + return "", fmt.Errorf("error flushing output buffer for route registration: %s", err) + } + return buf.String(), nil +} + +// Uses the template engine to generate the function which registers our wrappers +// as Echo path handlers. +func GenerateClient(t *template.Template, ops []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "client.tmpl", ops) + + if err != nil { + return "", fmt.Errorf("error generating client bindings: %s", err) + } + err = w.Flush() + if err != nil { + return "", fmt.Errorf("error flushing output buffer for client: %s", err) + } + return buf.String(), nil +} + +// This generates a client which extends the basic client which does response +// unmarshaling. +func GenerateClientWithResponses(t *template.Template, ops []OperationDefinition) (string, error) { + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + + err := t.ExecuteTemplate(w, "client-with-responses.tmpl", ops) + + if err != nil { + return "", fmt.Errorf("error generating client bindings: %s", err) + } + err = w.Flush() + if err != nil { + return "", fmt.Errorf("error flushing output buffer for client: %s", err) + } + return buf.String(), nil +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/prune.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/prune.go new file mode 100644 index 000000000..a2804f0b6 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/prune.go @@ -0,0 +1,483 @@ +package codegen + +import ( + "fmt" + + "github.com/getkin/kin-openapi/openapi3" +) + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +type RefWrapper struct { + Ref string + HasValue bool + SourceRef interface{} +} + +func walkSwagger(swagger *openapi3.Swagger, doFn func(RefWrapper) (bool, error)) error { + if swagger == nil { + return nil + } + + for _, p := range swagger.Paths { + for _, param := range p.Parameters { + walkParameterRef(param, doFn) + } + for _, op := range p.Operations() { + walkOperation(op, doFn) + } + } + + walkComponents(&swagger.Components, doFn) + + return nil +} + +func walkOperation(op *openapi3.Operation, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if op == nil { + return nil + } + + for _, param := range op.Parameters { + _ = walkParameterRef(param, doFn) + } + + _ = walkRequestBodyRef(op.RequestBody, doFn) + + for _, response := range op.Responses { + walkResponseRef(response, doFn) + } + + for _, callback := range op.Callbacks { + walkCallbackRef(callback, doFn) + } + + return nil +} + +func walkComponents(components *openapi3.Components, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if components == nil { + return nil + } + + for _, schema := range components.Schemas { + _ = walkSchemaRef(schema, doFn) + } + + for _, param := range components.Parameters { + _ = walkParameterRef(param, doFn) + } + + for _, header := range components.Headers { + _ = walkHeaderRef(header, doFn) + } + + for _, requestBody := range components.RequestBodies { + _ = walkRequestBodyRef(requestBody, doFn) + } + + for _, response := range components.Responses { + _ = walkResponseRef(response, doFn) + } + + for _, securityScheme := range components.SecuritySchemes { + _ = walkSecuritySchemeRef(securityScheme, doFn) + } + + for _, example := range components.Examples { + _ = walkExampleRef(example, doFn) + } + + for _, link := range components.Links { + _ = walkLinkRef(link, doFn) + } + + for _, callback := range components.Callbacks { + _ = walkCallbackRef(callback, doFn) + } + + return nil +} + +func walkSchemaRef(ref *openapi3.SchemaRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + for _, ref := range ref.Value.OneOf { + walkSchemaRef(ref, doFn) + } + + for _, ref := range ref.Value.AnyOf { + walkSchemaRef(ref, doFn) + } + + for _, ref := range ref.Value.AllOf { + walkSchemaRef(ref, doFn) + } + + walkSchemaRef(ref.Value.Not, doFn) + walkSchemaRef(ref.Value.Items, doFn) + + for _, ref := range ref.Value.Properties { + walkSchemaRef(ref, doFn) + } + + walkSchemaRef(ref.Value.AdditionalProperties, doFn) + + return nil +} + +func walkParameterRef(ref *openapi3.ParameterRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + walkSchemaRef(ref.Value.Schema, doFn) + + for _, example := range ref.Value.Examples { + walkExampleRef(example, doFn) + } + + for _, mediaType := range ref.Value.Content { + if mediaType == nil { + continue + } + walkSchemaRef(mediaType.Schema, doFn) + + for _, example := range mediaType.Examples { + walkExampleRef(example, doFn) + } + } + + return nil +} + +func walkRequestBodyRef(ref *openapi3.RequestBodyRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + for _, mediaType := range ref.Value.Content { + if mediaType == nil { + continue + } + walkSchemaRef(mediaType.Schema, doFn) + + for _, example := range mediaType.Examples { + walkExampleRef(example, doFn) + } + } + + return nil +} + +func walkResponseRef(ref *openapi3.ResponseRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + for _, header := range ref.Value.Headers { + walkHeaderRef(header, doFn) + } + + for _, mediaType := range ref.Value.Content { + if mediaType == nil { + continue + } + walkSchemaRef(mediaType.Schema, doFn) + + for _, example := range mediaType.Examples { + walkExampleRef(example, doFn) + } + } + + for _, link := range ref.Value.Links { + walkLinkRef(link, doFn) + } + + return nil +} + +func walkCallbackRef(ref *openapi3.CallbackRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + for _, pathItem := range *ref.Value { + for _, parameter := range pathItem.Parameters { + walkParameterRef(parameter, doFn) + } + walkOperation(pathItem.Connect, doFn) + walkOperation(pathItem.Delete, doFn) + walkOperation(pathItem.Get, doFn) + walkOperation(pathItem.Head, doFn) + walkOperation(pathItem.Options, doFn) + walkOperation(pathItem.Patch, doFn) + walkOperation(pathItem.Post, doFn) + walkOperation(pathItem.Put, doFn) + walkOperation(pathItem.Trace, doFn) + } + + return nil +} + +func walkHeaderRef(ref *openapi3.HeaderRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + walkSchemaRef(ref.Value.Schema, doFn) + + return nil +} + +func walkSecuritySchemeRef(ref *openapi3.SecuritySchemeRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + // NOTE: `SecuritySchemeRef`s don't contain any children that can contain refs + + return nil +} + +func walkLinkRef(ref *openapi3.LinkRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + return nil +} + +func walkExampleRef(ref *openapi3.ExampleRef, doFn func(RefWrapper) (bool, error)) error { + // Not a valid ref, ignore it and continue + if ref == nil { + return nil + } + refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref} + shouldContinue, err := doFn(refWrapper) + if err != nil { + return err + } + if !shouldContinue { + return nil + } + if ref.Value == nil { + return nil + } + + // NOTE: `ExampleRef`s don't contain any children that can contain refs + + return nil +} + +func findComponentRefs(swagger *openapi3.Swagger) []string { + refs := []string{} + + walkSwagger(swagger, func(ref RefWrapper) (bool, error) { + if ref.Ref != "" { + refs = append(refs, ref.Ref) + return false, nil + } + return true, nil + }) + + return refs +} + +func removeOrphanedComponents(swagger *openapi3.Swagger, refs []string) int { + countRemoved := 0 + + for key, _ := range swagger.Components.Schemas { + ref := fmt.Sprintf("#/components/schemas/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Schemas, key) + } + } + + for key, _ := range swagger.Components.Parameters { + ref := fmt.Sprintf("#/components/parameters/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Parameters, key) + } + } + + // securitySchemes are an exception. definitions in securitySchemes + // are referenced directly by name. and not by $ref + + // for key, _ := range swagger.Components.SecuritySchemes { + // ref := fmt.Sprintf("#/components/securitySchemes/%s", key) + // if !stringInSlice(ref, refs) { + // countRemoved++ + // delete(swagger.Components.SecuritySchemes, key) + // } + // } + + for key, _ := range swagger.Components.RequestBodies { + ref := fmt.Sprintf("#/components/requestBodies/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.RequestBodies, key) + } + } + + for key, _ := range swagger.Components.Responses { + ref := fmt.Sprintf("#/components/responses/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Responses, key) + } + } + + for key, _ := range swagger.Components.Headers { + ref := fmt.Sprintf("#/components/headers/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Headers, key) + } + } + + for key, _ := range swagger.Components.Examples { + ref := fmt.Sprintf("#/components/examples/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Examples, key) + } + } + + for key, _ := range swagger.Components.Links { + ref := fmt.Sprintf("#/components/links/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Links, key) + } + } + + for key, _ := range swagger.Components.Callbacks { + ref := fmt.Sprintf("#/components/callbacks/%s", key) + if !stringInSlice(ref, refs) { + countRemoved++ + delete(swagger.Components.Callbacks, key) + } + } + + return countRemoved +} + +func pruneUnusedComponents(swagger *openapi3.Swagger) { + for { + refs := findComponentRefs(swagger) + countRemoved := removeOrphanedComponents(swagger, refs) + if countRemoved < 1 { + break + } + } +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/schema.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/schema.go new file mode 100644 index 000000000..cbee2860d --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/schema.go @@ -0,0 +1,489 @@ +package codegen + +import ( + "fmt" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/pkg/errors" +) + +// This describes a Schema, a type definition. +type Schema struct { + GoType string // The Go type needed to represent the schema + RefType string // If the type has a type name, this is set + + EnumValues map[string]string // Enum values + + Properties []Property // For an object, the fields with names + HasAdditionalProperties bool // Whether we support additional properties + AdditionalPropertiesType *Schema // And if we do, their type + AdditionalTypes []TypeDefinition // We may need to generate auxiliary helper types, stored here + + SkipOptionalPointer bool // Some types don't need a * in front when they're optional +} + +func (s Schema) IsRef() bool { + return s.RefType != "" +} + +func (s Schema) TypeDecl() string { + if s.IsRef() { + return s.RefType + } + return s.GoType +} + +func (s *Schema) MergeProperty(p Property) error { + // Scan all existing properties for a conflict + for _, e := range s.Properties { + if e.JsonFieldName == p.JsonFieldName && !PropertiesEqual(e, p) { + return errors.New(fmt.Sprintf("property '%s' already exists with a different type", e.JsonFieldName)) + } + } + s.Properties = append(s.Properties, p) + return nil +} + +func (s Schema) GetAdditionalTypeDefs() []TypeDefinition { + var result []TypeDefinition + for _, p := range s.Properties { + result = append(result, p.Schema.GetAdditionalTypeDefs()...) + } + result = append(result, s.AdditionalTypes...) + return result +} + +type Property struct { + Description string + JsonFieldName string + Schema Schema + Required bool + Nullable bool +} + +func (p Property) GoFieldName() string { + return SchemaNameToTypeName(p.JsonFieldName) +} + +func (p Property) GoTypeDef() string { + typeDef := p.Schema.TypeDecl() + if !p.Schema.SkipOptionalPointer && (!p.Required || p.Nullable) { + typeDef = "*" + typeDef + } + return typeDef +} + +type TypeDefinition struct { + TypeName string + JsonName string + ResponseName string + Schema Schema +} + +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) { + // If Ref is set on the SchemaRef, it means that this type is actually a reference to + // another type. We're not de-referencing, so simply use the referenced type. + var refType string + + // 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. + if sref == nil { + return Schema{GoType: "interface{}", RefType: refType}, nil + } + + schema := sref.Value + + if sref.Ref != "" { + var err error + // Convert the reference path to Go type + refType, err = RefPathToGoType(sref.Ref) + if err != nil { + return Schema{}, fmt.Errorf("error turning reference (%s) into a Go type: %s", + sref.Ref, err) + } + return Schema{ + GoType: refType, + }, nil + } + + // We can't support this in any meaningful way + if schema.AnyOf != nil { + return Schema{GoType: "interface{}", RefType: refType}, nil + } + // We can't support this in any meaningful way + if schema.OneOf != nil { + return Schema{GoType: "interface{}", RefType: refType}, nil + } + + // AllOf is interesting, and useful. It's the union of a number of other + // schemas. A common usage is to create a union of an object with an ID, + // 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) + if err != nil { + return Schema{}, errors.Wrap(err, "error merging schemas") + } + mergedSchema.RefType = refType + return mergedSchema, nil + } + + outSchema := Schema{ + RefType: refType, + } + + // Check for custom Go type extension + if extension, ok := schema.Extensions[extPropGoType]; ok { + typeName, err := extTypeName(extension) + if err != nil { + return outSchema, errors.Wrapf(err, "invalid value for %q", extPropGoType) + } + outSchema.GoType = typeName + return outSchema, nil + } + + // Schema type and format, eg. string / binary + t := schema.Type + // Handle objects and empty schemas first as a special case + if t == "" || t == "object" { + var outType string + + if len(schema.Properties) == 0 && !SchemaHasAdditionalProperties(schema) { + // If the object has no properties or additional properties, we + // have some special cases for its type. + if t == "object" { + // We have an object with no properties. This is a generic object + // expressed as a map. + outType = "map[string]interface{}" + } else { // t == "" + // If we don't even have the object designator, we're a completely + // generic type. + outType = "interface{}" + } + outSchema.GoType = outType + } else { + // We've got an object with some properties. + for _, pName := range SortedSchemaKeys(schema.Properties) { + p := schema.Properties[pName] + propertyPath := append(path, pName) + pSchema, err := GenerateGoSchema(p, propertyPath) + if err != nil { + return Schema{}, errors.Wrap(err, fmt.Sprintf("error generating Go schema for property '%s'", pName)) + } + + required := StringInArray(pName, schema.Required) + + if pSchema.HasAdditionalProperties && pSchema.RefType == "" { + // If we have fields present which have additional properties, + // but are not a pre-defined type, we need to define a type + // for them, which will be based on the field names we followed + // to get to the type. + typeName := PathToTypeName(propertyPath) + + typeDef := TypeDefinition{ + TypeName: typeName, + JsonName: strings.Join(propertyPath, "."), + Schema: pSchema, + } + pSchema.AdditionalTypes = append(pSchema.AdditionalTypes, typeDef) + + pSchema.RefType = typeName + } + description := "" + if p.Value != nil { + description = p.Value.Description + } + prop := Property{ + JsonFieldName: pName, + Schema: pSchema, + Required: required, + Description: description, + Nullable: p.Value.Nullable, + } + outSchema.Properties = append(outSchema.Properties, prop) + } + + outSchema.HasAdditionalProperties = SchemaHasAdditionalProperties(schema) + outSchema.AdditionalPropertiesType = &Schema{ + GoType: "interface{}", + } + if schema.AdditionalProperties != nil { + additionalSchema, err := GenerateGoSchema(schema.AdditionalProperties, path) + if err != nil { + return Schema{}, errors.Wrap(err, "error generating type for additional properties") + } + outSchema.AdditionalPropertiesType = &additionalSchema + } + + outSchema.GoType = GenStructFromSchema(outSchema) + } + return outSchema, nil + } else { + f := schema.Format + + switch t { + 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) + if err != nil { + return Schema{}, errors.Wrap(err, "error generating type for array") + } + outSchema.GoType = "[]" + arrayType.TypeDecl() + outSchema.Properties = arrayType.Properties + case "integer": + // We default to int if format doesn't ask for something else. + if f == "int64" { + outSchema.GoType = "int64" + } else if f == "int32" { + outSchema.GoType = "int32" + } else if f == "" { + outSchema.GoType = "int" + } else { + return Schema{}, fmt.Errorf("invalid integer format: %s", f) + } + case "number": + // We default to float for "number" + if f == "double" { + outSchema.GoType = "float64" + } else if f == "float" || f == "" { + outSchema.GoType = "float32" + } else { + return Schema{}, fmt.Errorf("invalid number format: %s", f) + } + case "boolean": + if f != "" { + return Schema{}, fmt.Errorf("invalid format (%s) for boolean", f) + } + outSchema.GoType = "bool" + case "string": + enumValues := make([]string, len(schema.Enum)) + for i, enumValue := range schema.Enum { + enumValues[i] = enumValue.(string) + } + + outSchema.EnumValues = SanitizeEnumNames(enumValues) + + // Special case string formats here. + switch f { + case "byte": + outSchema.GoType = "[]byte" + case "email": + outSchema.GoType = "openapi_types.Email" + case "date": + outSchema.GoType = "openapi_types.Date" + case "date-time": + outSchema.GoType = "time.Time" + case "json": + outSchema.GoType = "json.RawMessage" + outSchema.SkipOptionalPointer = true + default: + // All unrecognized formats are simply a regular string. + outSchema.GoType = "string" + } + default: + return Schema{}, fmt.Errorf("unhandled Schema type: %s", t) + } + } + return outSchema, nil +} + +// This describes a Schema, a type definition. +type SchemaDescriptor struct { + Fields []FieldDescriptor + HasAdditionalProperties bool + AdditionalPropertiesType string +} + +type FieldDescriptor struct { + Required bool // Is the schema required? If not, we'll pass by pointer + GoType string // The Go type needed to represent the json type. + GoName string // The Go compatible type name for the type + JsonName string // The json type name for the type + IsRef bool // Is this schema a reference to predefined object? +} + +// Given a list of schema descriptors, produce corresponding field names with +// JSON annotations +func GenFieldsFromProperties(props []Property) []string { + var fields []string + for _, p := range props { + field := "" + // Add a comment to a field in case we have one, otherwise skip. + if p.Description != "" { + // Separate the comment from a previous-defined, unrelated field. + // Make sure the actual field is separated by a newline. + field += fmt.Sprintf("\n%s\n", StringToGoComment(p.Description)) + } + field += fmt.Sprintf(" %s %s", p.GoFieldName(), p.GoTypeDef()) + if p.Required || p.Nullable { + field += fmt.Sprintf(" `json:\"%s\"`", p.JsonFieldName) + } else { + field += fmt.Sprintf(" `json:\"%s,omitempty\"`", p.JsonFieldName) + } + fields = append(fields, field) + } + return fields +} + +func GenStructFromSchema(schema Schema) string { + // Start out with struct { + objectParts := []string{"struct {"} + // Append all the field definitions + objectParts = append(objectParts, GenFieldsFromProperties(schema.Properties)...) + // Close the struct + if schema.HasAdditionalProperties { + addPropsType := schema.AdditionalPropertiesType.GoType + if schema.AdditionalPropertiesType.RefType != "" { + addPropsType = schema.AdditionalPropertiesType.RefType + } + + objectParts = append(objectParts, + fmt.Sprintf("AdditionalProperties map[string]%s `json:\"-\"`", addPropsType)) + } + objectParts = append(objectParts, "}") + return strings.Join(objectParts, "\n") +} + +// Merge all the fields in the schemas supplied into one giant schema. +func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) { + var outSchema Schema + for _, schemaOrRef := range allOf { + ref := schemaOrRef.Ref + + var refType string + var err error + if ref != "" { + refType, err = RefPathToGoType(ref) + if err != nil { + return Schema{}, errors.Wrap(err, "error converting reference path to a go type") + } + } + + schema, err := GenerateGoSchema(schemaOrRef, path) + if err != nil { + return Schema{}, errors.Wrap(err, "error generating Go schema in allOf") + } + schema.RefType = refType + + for _, p := range schema.Properties { + err = outSchema.MergeProperty(p) + if err != nil { + return Schema{}, errors.Wrap(err, "error merging properties") + } + } + + if schema.HasAdditionalProperties { + if outSchema.HasAdditionalProperties { + // Both this schema, and the aggregate schema have additional + // properties, they must match. + if schema.AdditionalPropertiesType.TypeDecl() != outSchema.AdditionalPropertiesType.TypeDecl() { + return Schema{}, errors.New("additional properties in allOf have incompatible types") + } + } else { + // We're switching from having no additional properties to having + // them + outSchema.HasAdditionalProperties = true + outSchema.AdditionalPropertiesType = schema.AdditionalPropertiesType + } + } + } + + // Now, we generate the struct which merges together all the fields. + var err error + outSchema.GoType, err = GenStructFromAllOf(allOf, path) + if err != nil { + return Schema{}, errors.Wrap(err, "unable to generate aggregate type for AllOf") + } + return outSchema, nil +} + +// 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) { + // Start out with struct { + objectParts := []string{"struct {"} + for _, schemaOrRef := range allOf { + ref := schemaOrRef.Ref + if ref != "" { + // We have a referenced type, we will generate an inlined struct + // member. + // struct { + // InlinedMember + // ... + // } + goType, err := RefPathToGoType(ref) + if err != nil { + return "", err + } + objectParts = append(objectParts, + fmt.Sprintf(" // Embedded struct due to allOf(%s)", ref)) + objectParts = append(objectParts, + fmt.Sprintf(" %s", goType)) + } 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) + if err != nil { + return "", err + } + objectParts = append(objectParts, " // Embedded fields due to inline allOf schema") + objectParts = append(objectParts, GenFieldsFromProperties(goSchema.Properties)...) + + if goSchema.HasAdditionalProperties { + addPropsType := goSchema.AdditionalPropertiesType.GoType + if goSchema.AdditionalPropertiesType.RefType != "" { + addPropsType = goSchema.AdditionalPropertiesType.RefType + } + + additionalPropertiesPart := fmt.Sprintf("AdditionalProperties map[string]%s `json:\"-\"`", addPropsType) + if !StringInArray(additionalPropertiesPart, objectParts) { + objectParts = append(objectParts, additionalPropertiesPart) + } + } + } + } + objectParts = append(objectParts, "}") + return strings.Join(objectParts, "\n"), nil +} + +// 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) { + 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) + } + + // At this point, we have a content type. We know how to deal with + // application/json, but if multiple formats are present, we can't do anything, + // so we'll return the parameter as a string, not bothering to decode it. + if len(param.Content) > 1 { + return Schema{ + GoType: "string", + }, nil + } + + // Otherwise, look for application/json in there + mt, found := param.Content["application/json"] + if !found { + // If we don't have json, it's a string + return Schema{ + GoType: "string", + }, nil + } + + // For json, we go through the standard schema mechanism + return GenerateGoSchema(mt.Schema, path) +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/template_helpers.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/template_helpers.go new file mode 100644 index 000000000..adf72305c --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/template_helpers.go @@ -0,0 +1,280 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package codegen + +import ( + "bytes" + "fmt" + "os" + "strings" + "text/template" + + "github.com/labstack/echo/v4" +) + +const ( + // These allow the case statements to be sorted later: + prefixMostSpecific, prefixLessSpecific, prefixLeastSpecific = "3", "6", "9" + responseTypeSuffix = "Response" +) + +var ( + contentTypesJSON = []string{echo.MIMEApplicationJSON, "text/x-json"} + contentTypesYAML = []string{"application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"} + contentTypesXML = []string{echo.MIMEApplicationXML, echo.MIMETextXML} +) + +// This function takes an array of Parameter definition, and generates a valid +// Go parameter declaration from them, eg: +// ", foo int, bar string, baz float32". The preceding comma is there to save +// a lot of work in the template engine. +func genParamArgs(params []ParameterDefinition) string { + if len(params) == 0 { + return "" + } + parts := make([]string, len(params)) + for i, p := range params { + paramName := p.GoVariableName() + parts[i] = fmt.Sprintf("%s %s", paramName, p.TypeDef()) + } + return ", " + strings.Join(parts, ", ") +} + +// This function is much like the one above, except it only produces the +// types of the parameters for a type declaration. It would produce this +// from the same input as above: +// ", int, string, float32". +func genParamTypes(params []ParameterDefinition) string { + if len(params) == 0 { + return "" + } + parts := make([]string, len(params)) + for i, p := range params { + parts[i] = p.TypeDef() + } + return ", " + strings.Join(parts, ", ") +} + +// This is another variation of the function above which generates only the +// parameter names: +// ", foo, bar, baz" +func genParamNames(params []ParameterDefinition) string { + if len(params) == 0 { + return "" + } + parts := make([]string, len(params)) + for i, p := range params { + parts[i] = p.GoVariableName() + } + return ", " + strings.Join(parts, ", ") +} + +func genParamFmtString(path string) string { + return ReplacePathParamsWithStr(path) +} + +// genResponsePayload generates the payload returned at the end of each client request function +func genResponsePayload(operationID string) string { + var buffer = bytes.NewBufferString("") + + // Here is where we build up a response: + fmt.Fprintf(buffer, "&%s{\n", genResponseTypeName(operationID)) + fmt.Fprintf(buffer, "Body: bodyBytes,\n") + fmt.Fprintf(buffer, "HTTPResponse: rsp,\n") + fmt.Fprintf(buffer, "}") + + return buffer.String() +} + +// genResponseUnmarshal generates unmarshaling steps for structured response payloads +func genResponseUnmarshal(op *OperationDefinition) string { + var buffer = bytes.NewBufferString("") + + var handledCaseClauses = make(map[string]string) + var unhandledCaseClauses = make(map[string]string) + + // Get the type definitions from the operation: + typeDefinitions, err := op.GetResponseTypeDefinitions() + if err != nil { + panic(err) + } + + // Add a case for each possible response: + responses := op.Spec.Responses + for _, typeDefinition := range typeDefinitions { + + responseRef, ok := responses[typeDefinition.ResponseName] + if !ok { + continue + } + + // We can't do much without a value: + if responseRef.Value == nil { + fmt.Fprintf(os.Stderr, "Response %s.%s has nil value\n", op.OperationId, typeDefinition.ResponseName) + continue + } + + // If there is no content-type then we have no unmarshaling to do: + if len(responseRef.Value.Content) == 0 { + caseAction := "break // No content-type" + if typeDefinition.ResponseName == "default" { + caseClauseKey := "default:" + unhandledCaseClauses[prefixLeastSpecific+caseClauseKey] = fmt.Sprintf("%s\n%s\n", caseClauseKey, caseAction) + } else { + caseClauseKey := fmt.Sprintf("case rsp.StatusCode == %s:", typeDefinition.ResponseName) + unhandledCaseClauses[prefixLessSpecific+caseClauseKey] = fmt.Sprintf("%s\n%s\n", caseClauseKey, caseAction) + } + continue + } + + // If we made it this far then we need to handle unmarshaling for each content-type: + sortedContentKeys := SortedContentKeys(responseRef.Value.Content) + for _, contentTypeName := range sortedContentKeys { + + // We get "interface{}" when using "anyOf" or "oneOf" (which doesn't work with Go types): + if typeDefinition.TypeName == "interface{}" { + // Unable to unmarshal this, so we leave it out: + continue + } + + // Add content-types here (json / yaml / xml etc): + switch { + + // JSON: + case StringInArray(contentTypeName, contentTypesJSON): + var caseAction string + + caseAction = fmt.Sprintf("var dest %s\n"+ + "if err := json.Unmarshal(bodyBytes, &dest); err != nil { \n"+ + " return nil, err \n"+ + "}\n"+ + "response.%s = &dest", + typeDefinition.Schema.TypeDecl(), + typeDefinition.TypeName) + + caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "json") + handledCaseClauses[caseKey] = caseClause + + // YAML: + case StringInArray(contentTypeName, contentTypesYAML): + var caseAction string + caseAction = fmt.Sprintf("var dest %s\n"+ + "if err := yaml.Unmarshal(bodyBytes, &dest); err != nil { \n"+ + " return nil, err \n"+ + "}\n"+ + "response.%s = &dest", + typeDefinition.Schema.TypeDecl(), + typeDefinition.TypeName) + caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "yaml") + handledCaseClauses[caseKey] = caseClause + + // XML: + case StringInArray(contentTypeName, contentTypesXML): + var caseAction string + caseAction = fmt.Sprintf("var dest %s\n"+ + "if err := xml.Unmarshal(bodyBytes, &dest); err != nil { \n"+ + " return nil, err \n"+ + "}\n"+ + "response.%s = &dest", + typeDefinition.Schema.TypeDecl(), + typeDefinition.TypeName) + caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "xml") + handledCaseClauses[caseKey] = caseClause + + // Everything else: + default: + caseAction := fmt.Sprintf("// Content-type (%s) unsupported", contentTypeName) + if typeDefinition.ResponseName == "default" { + caseClauseKey := "default:" + unhandledCaseClauses[prefixLeastSpecific+caseClauseKey] = fmt.Sprintf("%s\n%s\n", caseClauseKey, caseAction) + } else { + caseClauseKey := fmt.Sprintf("case rsp.StatusCode == %s:", typeDefinition.ResponseName) + unhandledCaseClauses[prefixLessSpecific+caseClauseKey] = fmt.Sprintf("%s\n%s\n", caseClauseKey, caseAction) + } + } + } + } + + // Now build the switch statement in order of most-to-least specific: + // See: https://github.com/deepmap/oapi-codegen/issues/127 for why we handle this in two separate + // groups. + fmt.Fprintf(buffer, "switch {\n") + for _, caseClauseKey := range SortedStringKeys(handledCaseClauses) { + + fmt.Fprintf(buffer, "%s\n", handledCaseClauses[caseClauseKey]) + } + for _, caseClauseKey := range SortedStringKeys(unhandledCaseClauses) { + + fmt.Fprintf(buffer, "%s\n", unhandledCaseClauses[caseClauseKey]) + } + fmt.Fprintf(buffer, "}\n") + + return buffer.String() +} + +// buildUnmarshalCase builds an unmarshalling case clause for different content-types: +func buildUnmarshalCase(typeDefinition TypeDefinition, caseAction string, contentType string) (caseKey string, caseClause string) { + caseKey = fmt.Sprintf("%s.%s.%s", prefixLeastSpecific, contentType, typeDefinition.ResponseName) + if typeDefinition.ResponseName == "default" { + caseClause = fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"%s\"):\n%s\n", echo.HeaderContentType, contentType, caseAction) + } else { + caseClause = fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"%s\") && rsp.StatusCode == %s:\n%s\n", echo.HeaderContentType, contentType, typeDefinition.ResponseName, caseAction) + } + return caseKey, caseClause +} + +// genResponseTypeName creates the name of generated response types (given the operationID): +func genResponseTypeName(operationID string) string { + return fmt.Sprintf("%s%s", UppercaseFirstCharacter(operationID), responseTypeSuffix) +} + +func getResponseTypeDefinitions(op *OperationDefinition) []TypeDefinition { + td, err := op.GetResponseTypeDefinitions() + if err != nil { + panic(err) + } + return td +} + +// This outputs a string array +func toStringArray(sarr []string) string { + return `[]string{"` + strings.Join(sarr, `","`) + `"}` +} + +func stripNewLines(s string) string { + r := strings.NewReplacer("\n", "") + return r.Replace(s) +} + +// 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, + "swaggerUriToChiUri": SwaggerUriToChiUri, + "lcFirst": LowercaseFirstCharacter, + "ucFirst": UppercaseFirstCharacter, + "camelCase": ToCamelCase, + "genResponsePayload": genResponsePayload, + "genResponseTypeName": genResponseTypeName, + "genResponseUnmarshal": genResponseUnmarshal, + "getResponseTypeDefinitions": getResponseTypeDefinitions, + "toStringArray": toStringArray, + "lower": strings.ToLower, + "title": strings.Title, + "stripNewLines": stripNewLines, +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/additional-properties.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/additional-properties.tmpl new file mode 100644 index 000000000..0430d9135 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/additional-properties.tmpl @@ -0,0 +1,70 @@ +{{range .Types}}{{$addType := .Schema.AdditionalPropertiesType.TypeDecl}} + +// Getter for additional properties for {{.TypeName}}. Returns the specified +// element and whether it was found +func (a {{.TypeName}}) Get(fieldName string) (value {{$addType}}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for {{.TypeName}} +func (a *{{.TypeName}}) Set(fieldName string, value {{$addType}}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]{{$addType}}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties +func (a *{{.TypeName}}) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } +{{range .Schema.Properties}} + if raw, found := object["{{.JsonFieldName}}"]; found { + err = json.Unmarshal(raw, &a.{{.GoFieldName}}) + if err != nil { + return errors.Wrap(err, "error reading '{{.JsonFieldName}}'") + } + delete(object, "{{.JsonFieldName}}") + } +{{end}} + if len(object) != 0 { + a.AdditionalProperties = make(map[string]{{$addType}}) + for fieldName, fieldBuf := range object { + var fieldVal {{$addType}} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error unmarshaling field %s", fieldName)) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties +func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) +{{range .Schema.Properties}} +{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}} + object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}}) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error marshaling '{{.JsonFieldName}}'")) + } +{{if not .Required}} }{{end}} +{{end}} + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error marshaling '%s'", fieldName)) + } + } + return json.Marshal(object) +} +{{end}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-handler.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-handler.tmpl new file mode 100644 index 000000000..16cd4610c --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-handler.tmpl @@ -0,0 +1,17 @@ +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerFromMux(si, chi.NewRouter()) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { +{{if .}}wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}r.Group(func(r chi.Router) { + r.{{.Method | lower | title }}("{{.Path | swaggerUriToChiUri}}", wrapper.{{.OperationId}}) +}) +{{end}} + return r +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-interface.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-interface.tmpl new file mode 100644 index 000000000..79a51fd75 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{end}} +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-middleware.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-middleware.tmpl new file mode 100644 index 000000000..ac9ee23ea --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/chi-middleware.tmpl @@ -0,0 +1,167 @@ +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +{{range .}}{{$opid := .OperationId}} + +// {{$opid}} operation middleware +func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error + {{end}} + + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} + + {{if .IsPassThrough}} + {{$varName}} = chi.URLParam(r, "{{.ParamName}}") + {{end}} + {{if .IsJson}} + err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}}) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + {{end}} + {{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return + } + {{end}} + + {{end}} + +{{range .SecurityDefinitions}} + ctx = context.WithValue(ctx, "{{.ProviderName}}.Scopes", {{toStringArray .Scopes}}) +{{end}} + + {{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + + {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + {{end}} + + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest) + return + }{{end}} + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return + } + {{end}} + {{end}} + + {{if .HeaderParams}} + headers := r.Header + + {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + http.Error(w, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n), http.StatusBadRequest) + return + } + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + {{end}} + + {{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + {{end}} + + {{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", valueList[0], &{{.GoName}}) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return + } + {{end}} + + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + + } {{if .Required}}else { + http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest) + return + }{{end}} + + {{end}} + {{end}} + + {{range .CookieParams}} + if cookie, err := r.Cookie("{{.ParamName}}"); err == nil { + + {{- if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + {{end}} + + {{- if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + http.Error(w, "Error unescaping cookie parameter '{{.ParamName}}'", http.StatusBadRequest) + return + } + + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + {{- if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + if err != nil { + http.Error(w, "Invalid format for parameter {{.ParamName}}: %s", http.StatusBadRequest) + return + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + } + + {{- if .Required}} else { + http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest) + return + } + {{- end}} + {{end}} + {{end}} + siw.Handler.{{.OperationId}}(w, r.WithContext(ctx){{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) +} +{{end}} + + + diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client-with-responses.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client-with-responses.tmpl new file mode 100644 index 000000000..0ea768303 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client-with-responses.tmpl @@ -0,0 +1,115 @@ +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + // {{$opid}} request {{if .HasBody}} with any body{{end}} + {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*{{genResponseTypeName $opid}}, error) +{{range .Bodies}} + {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*{{genResponseTypeName $opid}}, error) +{{end}}{{/* range .Bodies */}} +{{end}}{{/* range . $opid := .OperationId */}} +} + +{{range .}}{{$opid := .OperationId}}{{$op := .}} +type {{$opid | ucFirst}}Response struct { + Body []byte + HTTPResponse *http.Response + {{- range getResponseTypeDefinitions .}} + {{.TypeName}} *{{.Schema.TypeDecl}} + {{- end}} +} + +// Status returns HTTPResponse.Status +func (r {{$opid | ucFirst}}Response) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r {{$opid | ucFirst}}Response) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} +{{end}} + + +{{range .}} +{{$opid := .OperationId -}} +{{/* Generate client methods (with responses)*/}} + +// {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with arbitrary body{{end}} returning *{{$opid}}Response +func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*{{genResponseTypeName $opid}}, error){ + rsp, err := c.{{$opid}}{{if .HasBody}}WithBody{{end}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) +} + +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$bodyRequired := .BodyRequired -}} +{{range .Bodies}} +func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*{{genResponseTypeName $opid}}, error) { + rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) +} +{{end}} + +{{end}}{{/* operations */}} + +{{/* Generate parse functions for responses*/}} +{{range .}}{{$opid := .OperationId}} + +// Parse{{genResponseTypeName $opid | ucFirst}} parses an HTTP response from a {{$opid}}WithResponse call +func Parse{{genResponseTypeName $opid | ucFirst}}(rsp *http.Response) (*{{genResponseTypeName $opid}}, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := {{genResponsePayload $opid}} + + {{genResponseUnmarshal .}} + + return response, nil +} +{{end}}{{/* range . $opid := .OperationId */}} + diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client.tmpl new file mode 100644 index 000000000..a215895da --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/client.tmpl @@ -0,0 +1,273 @@ +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A callback for modifying requests which are generated before sending over + // the network. + RequestEditor RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = http.DefaultClient + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditor = fn + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + // {{$opid}} request {{if .HasBody}} with any body{{end}} + {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Response, error) +{{range .Bodies}} + {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Response, error) +{{end}}{{/* range .Bodies */}} +{{end}}{{/* range . $opid := .OperationId */}} +} + + +{{/* Generate client methods */}} +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + +func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Response, error) { + req, err := New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(c.Server{{genParamNames .PathParams}}{{if $hasParams}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +{{range .Bodies}} +func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Response, error) { + req, err := New{{$opid}}{{.Suffix}}Request(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} +{{end}}{{/* range .Bodies */}} +{{end}} + +{{/* Generate request builders */}} +{{range .}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$bodyRequired := .BodyRequired -}} +{{$opid := .OperationId -}} + +{{range .Bodies}} +// New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body +func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return New{{$opid}}RequestWithBody(server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, "{{.ContentType}}", bodyReader) +} +{{end}} + +// New{{$opid}}Request{{if .HasBody}}WithBody{{end}} generates requests for {{$opid}}{{if .HasBody}} with any type of body{{end}} +func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Request, error) { + var err error +{{range $paramIdx, $param := .PathParams}} + var pathParam{{$paramIdx}} string + {{if .IsPassThrough}} + pathParam{{$paramIdx}} = {{.ParamName}} + {{end}} + {{if .IsJson}} + var pathParamBuf{{$paramIdx}} []byte + pathParamBuf{{$paramIdx}}, err = json.Marshal({{.ParamName}}) + if err != nil { + return nil, err + } + pathParam{{$paramIdx}} = string(pathParamBuf{{$paramIdx}}) + {{end}} + {{if .IsStyled}} + pathParam{{$paramIdx}}, err = runtime.StyleParam("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{.GoVariableName}}) + if err != nil { + return nil, err + } + {{end}} +{{end}} + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("{{genParamFmtString .Path}}"{{range $paramIdx, $param := .PathParams}}, pathParam{{$paramIdx}}{{end}}) + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } +{{if .QueryParams}} + queryValues := queryUrl.Query() +{{range $paramIdx, $param := .QueryParams}} + {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + {{if .IsPassThrough}} + queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + {{end}} + {{if .IsJson}} + if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { + return nil, err + } else { + queryValues.Add("{{.ParamName}}", string(queryParamBuf)) + } + + {{end}} + {{if .IsStyled}} + if queryFrag, err := runtime.StyleParam("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + {{end}} + {{if not .Required}}}{{end}} +{{end}} + queryUrl.RawQuery = queryValues.Encode() +{{end}}{{/* if .QueryParams */}} + req, err := http.NewRequest("{{.Method}}", queryUrl.String(), {{if .HasBody}}body{{else}}nil{{end}}) + if err != nil { + return nil, err + } + +{{range $paramIdx, $param := .HeaderParams}} + {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + var headerParam{{$paramIdx}} string + {{if .IsPassThrough}} + headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} + {{end}} + {{if .IsJson}} + var headerParamBuf{{$paramIdx}} []byte + headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}}) + {{end}} + {{if .IsStyled}} + headerParam{{$paramIdx}}, err = runtime.StyleParam("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + {{end}} + req.Header.Add("{{.ParamName}}", headerParam{{$paramIdx}}) + {{if not .Required}}}{{end}} +{{end}} + +{{range $paramIdx, $param := .CookieParams}} + {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + var cookieParam{{$paramIdx}} string + {{if .IsPassThrough}} + cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} + {{end}} + {{if .IsJson}} + var cookieParamBuf{{$paramIdx}} []byte + cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}})) + {{end}} + {{if .IsStyled}} + cookieParam{{$paramIdx}}, err = runtime.StyleParam("simple", {{.Explode}}, "{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + {{end}} + cookie{{$paramIdx}} := &http.Cookie{ + Name:"{{.ParamName}}", + Value:cookieParam{{$paramIdx}}, + } + req.AddCookie(cookie{{$paramIdx}}) + {{if not .Required}}}{{end}} +{{end}} + {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}} + return req, nil +} + +{{end}}{{/* Range */}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/doc.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/doc.go new file mode 100644 index 000000000..864ab7b67 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/doc.go @@ -0,0 +1,3 @@ +package templates + +//go:generate go run github.com/cyberdelia/templates -s . -o templates.gen.go diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/imports.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/imports.tmpl new file mode 100644 index 000000000..b5c5f1ed8 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/imports.tmpl @@ -0,0 +1,10 @@ +// Package {{.PackageName}} provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package {{.PackageName}} + +{{if .Imports}} +import ( +{{range .Imports}} {{ . }} +{{end}}) +{{end}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/inline.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/inline.tmpl new file mode 100644 index 000000000..0ab3116ea --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/inline.tmpl @@ -0,0 +1,29 @@ +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ +{{range .}} + "{{.}}",{{end}} +} + +// 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) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes()) + if err != nil { + return nil, fmt.Errorf("error loading Swagger: %s", err) + } + return swagger, nil +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/param-types.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/param-types.tmpl new file mode 100644 index 000000000..b35cce5d7 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/param-types.tmpl @@ -0,0 +1,6 @@ +{{range .}}{{$opid := .OperationId}} +{{range .TypeDefinitions}} +// {{.TypeName}} defines parameters for {{$opid}}. +type {{.TypeName}} {{.Schema.TypeDecl}} +{{end}} +{{end}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/register.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/register.tmpl new file mode 100644 index 000000000..d7857f1b9 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/register.tmpl @@ -0,0 +1,27 @@ + + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { +{{if .}} + wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}router.{{.Method}}("{{.Path | swaggerUriToEchoUri}}", wrapper.{{.OperationId}}) +{{end}} +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/request-bodies.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/request-bodies.tmpl new file mode 100644 index 000000000..cec74bcb0 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/request-bodies.tmpl @@ -0,0 +1,6 @@ +{{range .}}{{$opid := .OperationId}} +{{range .Bodies}} +// {{$opid}}RequestBody defines body for {{$opid}} for application/json ContentType. +type {{$opid}}{{.NameTag}}RequestBody {{.TypeDef}} +{{end}} +{{end}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/server-interface.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/server-interface.tmpl new file mode 100644 index 000000000..738009169 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/server-interface.tmpl @@ -0,0 +1,7 @@ +// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(ctx echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error +{{end}} +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/templates.gen.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/templates.gen.go new file mode 100644 index 000000000..9b9017370 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/templates.gen.go @@ -0,0 +1,914 @@ +package templates + +import "text/template" + +var templates = map[string]string{"additional-properties.tmpl": `{{range .Types}}{{$addType := .Schema.AdditionalPropertiesType.TypeDecl}} + +// Getter for additional properties for {{.TypeName}}. Returns the specified +// element and whether it was found +func (a {{.TypeName}}) Get(fieldName string) (value {{$addType}}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for {{.TypeName}} +func (a *{{.TypeName}}) Set(fieldName string, value {{$addType}}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]{{$addType}}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties +func (a *{{.TypeName}}) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } +{{range .Schema.Properties}} + if raw, found := object["{{.JsonFieldName}}"]; found { + err = json.Unmarshal(raw, &a.{{.GoFieldName}}) + if err != nil { + return errors.Wrap(err, "error reading '{{.JsonFieldName}}'") + } + delete(object, "{{.JsonFieldName}}") + } +{{end}} + if len(object) != 0 { + a.AdditionalProperties = make(map[string]{{$addType}}) + for fieldName, fieldBuf := range object { + var fieldVal {{$addType}} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error unmarshaling field %s", fieldName)) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties +func (a {{.TypeName}}) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) +{{range .Schema.Properties}} +{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}} + object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}}) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error marshaling '{{.JsonFieldName}}'")) + } +{{if not .Required}} }{{end}} +{{end}} + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("error marshaling '%s'", fieldName)) + } + } + return json.Marshal(object) +} +{{end}} +`, + "chi-handler.tmpl": `// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerFromMux(si, chi.NewRouter()) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { +{{if .}}wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}r.Group(func(r chi.Router) { + r.{{.Method | lower | title }}("{{.Path | swaggerUriToChiUri}}", wrapper.{{.OperationId}}) +}) +{{end}} + return r +} +`, + "chi-interface.tmpl": `// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) +{{end}} +} +`, + "chi-middleware.tmpl": `// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +{{range .}}{{$opid := .OperationId}} + +// {{$opid}} operation middleware +func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + {{if or .RequiresParamObject (gt (len .PathParams) 0) }} + var err error + {{end}} + + {{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} + + {{if .IsPassThrough}} + {{$varName}} = chi.URLParam(r, "{{.ParamName}}") + {{end}} + {{if .IsJson}} + err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}}) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + {{end}} + {{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return + } + {{end}} + + {{end}} + +{{range .SecurityDefinitions}} + ctx = context.WithValue(ctx, "{{.ProviderName}}.Scopes", {{toStringArray .Scopes}}) +{{end}} + + {{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params + + {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" { + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + {{end}} + + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest) + return + }{{end}} + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), ¶ms.{{.GoName}}) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return + } + {{end}} + {{end}} + + {{if .HeaderParams}} + headers := r.Header + + {{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + http.Error(w, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n), http.StatusBadRequest) + return + } + + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] + {{end}} + + {{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + {{end}} + + {{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", valueList[0], &{{.GoName}}) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err), http.StatusBadRequest) + return + } + {{end}} + + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + + } {{if .Required}}else { + http.Error(w, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found: %s", err), http.StatusBadRequest) + return + }{{end}} + + {{end}} + {{end}} + + {{range .CookieParams}} + if cookie, err := r.Cookie("{{.ParamName}}"); err == nil { + + {{- if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + {{end}} + + {{- if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + http.Error(w, "Error unescaping cookie parameter '{{.ParamName}}'", http.StatusBadRequest) + return + } + + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + http.Error(w, "Error unmarshaling parameter '{{.ParamName}}' as JSON", http.StatusBadRequest) + return + } + + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + {{- if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + if err != nil { + http.Error(w, "Invalid format for parameter {{.ParamName}}: %s", http.StatusBadRequest) + return + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + + } + + {{- if .Required}} else { + http.Error(w, "Query argument {{.ParamName}} is required, but not found", http.StatusBadRequest) + return + } + {{- end}} + {{end}} + {{end}} + siw.Handler.{{.OperationId}}(w, r.WithContext(ctx){{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) +} +{{end}} + + + +`, + "client-with-responses.tmpl": `// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + // {{$opid}} request {{if .HasBody}} with any body{{end}} + {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*{{genResponseTypeName $opid}}, error) +{{range .Bodies}} + {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*{{genResponseTypeName $opid}}, error) +{{end}}{{/* range .Bodies */}} +{{end}}{{/* range . $opid := .OperationId */}} +} + +{{range .}}{{$opid := .OperationId}}{{$op := .}} +type {{$opid | ucFirst}}Response struct { + Body []byte + HTTPResponse *http.Response + {{- range getResponseTypeDefinitions .}} + {{.TypeName}} *{{.Schema.TypeDecl}} + {{- end}} +} + +// Status returns HTTPResponse.Status +func (r {{$opid | ucFirst}}Response) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r {{$opid | ucFirst}}Response) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} +{{end}} + + +{{range .}} +{{$opid := .OperationId -}} +{{/* Generate client methods (with responses)*/}} + +// {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with arbitrary body{{end}} returning *{{$opid}}Response +func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*{{genResponseTypeName $opid}}, error){ + rsp, err := c.{{$opid}}{{if .HasBody}}WithBody{{end}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) +} + +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$bodyRequired := .BodyRequired -}} +{{range .Bodies}} +func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*{{genResponseTypeName $opid}}, error) { + rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}(rsp) +} +{{end}} + +{{end}}{{/* operations */}} + +{{/* Generate parse functions for responses*/}} +{{range .}}{{$opid := .OperationId}} + +// Parse{{genResponseTypeName $opid | ucFirst}} parses an HTTP response from a {{$opid}}WithResponse call +func Parse{{genResponseTypeName $opid | ucFirst}}(rsp *http.Response) (*{{genResponseTypeName $opid}}, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := {{genResponsePayload $opid}} + + {{genResponseUnmarshal .}} + + return response, nil +} +{{end}}{{/* range . $opid := .OperationId */}} + +`, + "client.tmpl": `// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A callback for modifying requests which are generated before sending over + // the network. + RequestEditor RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = http.DefaultClient + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditor = fn + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + // {{$opid}} request {{if .HasBody}} with any body{{end}} + {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Response, error) +{{range .Bodies}} + {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Response, error) +{{end}}{{/* range .Bodies */}} +{{end}}{{/* range . $opid := .OperationId */}} +} + + +{{/* Generate client methods */}} +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + +func (c *Client) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Response, error) { + req, err := New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(c.Server{{genParamNames .PathParams}}{{if $hasParams}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +{{range .Bodies}} +func (c *Client) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Response, error) { + req, err := New{{$opid}}{{.Suffix}}Request(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} +{{end}}{{/* range .Bodies */}} +{{end}} + +{{/* Generate request builders */}} +{{range .}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$bodyRequired := .BodyRequired -}} +{{$opid := .OperationId -}} + +{{range .Bodies}} +// New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body +func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return New{{$opid}}RequestWithBody(server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, "{{.ContentType}}", bodyReader) +} +{{end}} + +// New{{$opid}}Request{{if .HasBody}}WithBody{{end}} generates requests for {{$opid}}{{if .HasBody}} with any type of body{{end}} +func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Request, error) { + var err error +{{range $paramIdx, $param := .PathParams}} + var pathParam{{$paramIdx}} string + {{if .IsPassThrough}} + pathParam{{$paramIdx}} = {{.ParamName}} + {{end}} + {{if .IsJson}} + var pathParamBuf{{$paramIdx}} []byte + pathParamBuf{{$paramIdx}}, err = json.Marshal({{.ParamName}}) + if err != nil { + return nil, err + } + pathParam{{$paramIdx}} = string(pathParamBuf{{$paramIdx}}) + {{end}} + {{if .IsStyled}} + pathParam{{$paramIdx}}, err = runtime.StyleParam("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{.GoVariableName}}) + if err != nil { + return nil, err + } + {{end}} +{{end}} + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("{{genParamFmtString .Path}}"{{range $paramIdx, $param := .PathParams}}, pathParam{{$paramIdx}}{{end}}) + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } +{{if .QueryParams}} + queryValues := queryUrl.Query() +{{range $paramIdx, $param := .QueryParams}} + {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + {{if .IsPassThrough}} + queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + {{end}} + {{if .IsJson}} + if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { + return nil, err + } else { + queryValues.Add("{{.ParamName}}", string(queryParamBuf)) + } + + {{end}} + {{if .IsStyled}} + if queryFrag, err := runtime.StyleParam("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + {{end}} + {{if not .Required}}}{{end}} +{{end}} + queryUrl.RawQuery = queryValues.Encode() +{{end}}{{/* if .QueryParams */}} + req, err := http.NewRequest("{{.Method}}", queryUrl.String(), {{if .HasBody}}body{{else}}nil{{end}}) + if err != nil { + return nil, err + } + +{{range $paramIdx, $param := .HeaderParams}} + {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + var headerParam{{$paramIdx}} string + {{if .IsPassThrough}} + headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} + {{end}} + {{if .IsJson}} + var headerParamBuf{{$paramIdx}} []byte + headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}}) + {{end}} + {{if .IsStyled}} + headerParam{{$paramIdx}}, err = runtime.StyleParam("{{.Style}}", {{.Explode}}, "{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + {{end}} + req.Header.Add("{{.ParamName}}", headerParam{{$paramIdx}}) + {{if not .Required}}}{{end}} +{{end}} + +{{range $paramIdx, $param := .CookieParams}} + {{if not .Required}} if params.{{.GoName}} != nil { {{end}} + var cookieParam{{$paramIdx}} string + {{if .IsPassThrough}} + cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} + {{end}} + {{if .IsJson}} + var cookieParamBuf{{$paramIdx}} []byte + cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}})) + {{end}} + {{if .IsStyled}} + cookieParam{{$paramIdx}}, err = runtime.StyleParam("simple", {{.Explode}}, "{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) + if err != nil { + return nil, err + } + {{end}} + cookie{{$paramIdx}} := &http.Cookie{ + Name:"{{.ParamName}}", + Value:cookieParam{{$paramIdx}}, + } + req.AddCookie(cookie{{$paramIdx}}) + {{if not .Required}}}{{end}} +{{end}} + {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}} + return req, nil +} + +{{end}}{{/* Range */}} +`, + "imports.tmpl": `// Package {{.PackageName}} provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package {{.PackageName}} + +{{if .Imports}} +import ( +{{range .Imports}} {{ . }} +{{end}}) +{{end}} +`, + "inline.tmpl": `// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ +{{range .}} + "{{.}}",{{end}} +} + +// 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) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes()) + if err != nil { + return nil, fmt.Errorf("error loading Swagger: %s", err) + } + return swagger, nil +} +`, + "param-types.tmpl": `{{range .}}{{$opid := .OperationId}} +{{range .TypeDefinitions}} +// {{.TypeName}} defines parameters for {{$opid}}. +type {{.TypeName}} {{.Schema.TypeDecl}} +{{end}} +{{end}} +`, + "register.tmpl": ` + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { +{{if .}} + wrapper := ServerInterfaceWrapper{ + Handler: si, + } +{{end}} +{{range .}}router.{{.Method}}("{{.Path | swaggerUriToEchoUri}}", wrapper.{{.OperationId}}) +{{end}} +} +`, + "request-bodies.tmpl": `{{range .}}{{$opid := .OperationId}} +{{range .Bodies}} +// {{$opid}}RequestBody defines body for {{$opid}} for application/json ContentType. +type {{$opid}}{{.NameTag}}RequestBody {{.TypeDef}} +{{end}} +{{end}} +`, + "server-interface.tmpl": `// ServerInterface represents all server handlers. +type ServerInterface interface { +{{range .}}{{.SummaryAsComment }} +// ({{.Method}} {{.Path}}) +{{.OperationId}}(ctx echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error +{{end}} +} +`, + "typedef.tmpl": `{{range .Types}} +// {{.TypeName}} defines model for {{.JsonName}}. +type {{.TypeName}} {{.Schema.TypeDecl}} +{{- if gt (len .Schema.EnumValues) 0 }} +// List of {{ .TypeName }} +const ( + {{- $typeName := .TypeName }} + {{- range $key, $value := .Schema.EnumValues }} + {{ $typeName }}_{{ $key }} {{ $typeName }} = "{{ $value }}" + {{- end }} +) +{{- end }} +{{end}} +`, + "wrappers.tmpl": `// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +{{range .}}{{$opid := .OperationId}}// {{$opid}} converts echo context to params. +func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { + var err error +{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} +{{if .IsPassThrough}} + {{$varName}} = ctx.Param("{{.ParamName}}") +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(ctx.Param("{{.ParamName}}")), &{{$varName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", ctx.Param("{{.ParamName}}"), &{{$varName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} +{{end}} + +{{range .SecurityDefinitions}} + ctx.Set("{{.ProviderName}}.Scopes", {{toStringArray .Scopes}}) +{{end}} + +{{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params +{{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), ¶ms.{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + {{else}} + if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + {{end}} +{{end}} + +{{if .HeaderParams}} + headers := ctx.Request().Header +{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)) + } +{{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", valueList[0], &{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + } {{if .Required}}else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found")) + }{{end}} +{{end}} +{{end}} + +{{range .CookieParams}} + if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unescaping cookie parameter '{{.ParamName}}'") + } + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + {{if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + +{{end}}{{/* .CookieParams */}} + +{{end}}{{/* .RequiresParamObject */}} + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + return err +} +{{end}} +`, +} + +// Parse parses declared templates. +func Parse(t *template.Template) (*template.Template, error) { + for name, s := range templates { + var tmpl *template.Template + if t == nil { + t = template.New(name) + } + if name == t.Name() { + tmpl = t + } else { + tmpl = t.New(name) + } + if _, err := tmpl.Parse(s); err != nil { + return nil, err + } + } + return t, nil +} + diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/typedef.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/typedef.tmpl new file mode 100644 index 000000000..170984fb1 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/typedef.tmpl @@ -0,0 +1,13 @@ +{{range .Types}} +// {{.TypeName}} defines model for {{.JsonName}}. +type {{.TypeName}} {{.Schema.TypeDecl}} +{{- if gt (len .Schema.EnumValues) 0 }} +// List of {{ .TypeName }} +const ( + {{- $typeName := .TypeName }} + {{- range $key, $value := .Schema.EnumValues }} + {{ $typeName }}_{{ $key }} {{ $typeName }} = "{{ $value }}" + {{- end }} +) +{{- end }} +{{end}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/wrappers.tmpl b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/wrappers.tmpl new file mode 100644 index 000000000..48887d1d0 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/templates/wrappers.tmpl @@ -0,0 +1,128 @@ +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +{{range .}}{{$opid := .OperationId}}// {{$opid}} converts echo context to params. +func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error { + var err error +{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" ------------- + var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}} +{{if .IsPassThrough}} + {{$varName}} = ctx.Param("{{.ParamName}}") +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(ctx.Param("{{.ParamName}}")), &{{$varName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", ctx.Param("{{.ParamName}}"), &{{$varName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} +{{end}} + +{{range .SecurityDefinitions}} + ctx.Set("{{.ProviderName}}.Scopes", {{toStringArray .Scopes}}) +{{end}} + +{{if .RequiresParamObject}} + // Parameter object where we will unmarshal all parameters from the context + var params {{.OperationId}}Params +{{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" ------------- + {{if .IsStyled}} + err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), ¶ms.{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + {{else}} + if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + err = json.Unmarshal([]byte(paramValue), &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + {{end}} +{{end}} + +{{if .HeaderParams}} + headers := ctx.Request().Header +{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found { + var {{.GoName}} {{.TypeDef}} + n := len(valueList) + if n != 1 { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n)) + } +{{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0] +{{end}} +{{if .IsJson}} + err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } +{{end}} +{{if .IsStyled}} + err = runtime.BindStyledParameter("{{.Style}}",{{.Explode}}, "{{.ParamName}}", valueList[0], &{{.GoName}}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } +{{end}} + params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}} + } {{if .Required}}else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found")) + }{{end}} +{{end}} +{{end}} + +{{range .CookieParams}} + if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil { + {{if .IsPassThrough}} + params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value + {{end}} + {{if .IsJson}} + var value {{.TypeDef}} + var decoded string + decoded, err := url.QueryUnescape(cookie.Value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unescaping cookie parameter '{{.ParamName}}'") + } + err = json.Unmarshal([]byte(decoded), &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON") + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + {{if .IsStyled}} + var value {{.TypeDef}} + err = runtime.BindStyledParameter("simple",{{.Explode}}, "{{.ParamName}}", cookie.Value, &value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err)) + } + params.{{.GoName}} = {{if not .Required}}&{{end}}value + {{end}} + }{{if .Required}} else { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found")) + }{{end}} + +{{end}}{{/* .CookieParams */}} + +{{end}}{{/* .RequiresParamObject */}} + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}) + return err +} +{{end}} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/utils.go b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/utils.go new file mode 100644 index 000000000..67313636e --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/codegen/utils.go @@ -0,0 +1,534 @@ +// Copyright 2019 DeepMap, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package codegen + +import ( + "fmt" + "regexp" + "sort" + "strconv" + "strings" + "unicode" + + "github.com/getkin/kin-openapi/openapi3" +) + +var pathParamRE *regexp.Regexp + +func init() { + pathParamRE = regexp.MustCompile("{[.;?]?([^{}*]+)\\*?}") +} + +// Uppercase the first character in a string. This assumes UTF-8, so we have +// to be careful with unicode, don't treat it as a byte array. +func UppercaseFirstCharacter(str string) string { + if str == "" { + return "" + } + runes := []rune(str) + runes[0] = unicode.ToUpper(runes[0]) + return string(runes) +} + +// Same as above, except lower case +func LowercaseFirstCharacter(str string) string { + if str == "" { + return "" + } + runes := []rune(str) + runes[0] = unicode.ToLower(runes[0]) + return string(runes) +} + +// This function will convert query-arg style strings to CamelCase. We will +// use `., -, +, :, ;, _, ~, ' ', (, ), {, }, [, ]` as valid delimiters for words. +// So, "word.word-word+word:word;word_word~word word(word)word{word}[word]" +// would be converted to WordWordWordWordWordWordWordWordWordWordWordWordWord +func ToCamelCase(str string) string { + separators := "-#@!$&=.+:;_~ (){}[]" + s := strings.Trim(str, " ") + + n := "" + capNext := true + for _, v := range s { + if unicode.IsUpper(v) { + n += string(v) + } + if unicode.IsDigit(v) { + n += string(v) + } + if unicode.IsLower(v) { + if capNext { + n += strings.ToUpper(string(v)) + } else { + n += string(v) + } + } + + if strings.ContainsRune(separators, v) { + capNext = true + } else { + capNext = false + } + } + return n +} + +// This function returns the keys of the given SchemaRef dictionary in sorted +// order, since Golang scrambles dictionary keys +func SortedSchemaKeys(dict map[string]*openapi3.SchemaRef) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This function is the same as above, except it sorts the keys for a Paths +// dictionary. +func SortedPathsKeys(dict openapi3.Paths) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This function returns Operation dictionary keys in sorted order +func SortedOperationsKeys(dict map[string]*openapi3.Operation) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This function returns Responses dictionary keys in sorted order +func SortedResponsesKeys(dict openapi3.Responses) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This returns Content dictionary keys in sorted order +func SortedContentKeys(dict openapi3.Content) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This returns string map keys in sorted order +func SortedStringKeys(dict map[string]string) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This returns sorted keys for a ParameterRef dict +func SortedParameterKeys(dict map[string]*openapi3.ParameterRef) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +func SortedRequestBodyKeys(dict map[string]*openapi3.RequestBodyRef) []string { + keys := make([]string, len(dict)) + i := 0 + for key := range dict { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +// This function checks whether the specified string is present in an array +// of strings +func StringInArray(str string, array []string) bool { + for _, elt := range array { + if elt == str { + return true + } + } + return false +} + +// This function takes a $ref value and converts it to a Go typename. +// #/components/schemas/Foo -> Foo +// #/components/parameters/Bar -> Bar +// #/components/responses/Baz -> Baz +// 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) { + if refPath[0] == '#' { + pathParts := strings.Split(refPath, "/") + if depth := len(pathParts); depth != 4 { + return "", fmt.Errorf("Parameter nesting is deeper than supported: %s has %d", refPath, depth) + } + return SchemaNameToTypeName(pathParts[3]), nil + } + 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 + } +} + +// This function converts a swagger style path URI with parameters to a +// Echo compatible path URI. We need to replace all of Swagger parameters with +// ":param". Valid input parameters are: +// {param} +// {param*} +// {.param} +// {.param*} +// {;param} +// {;param*} +// {?param} +// {?param*} +func SwaggerUriToEchoUri(uri string) string { + return pathParamRE.ReplaceAllString(uri, ":$1") +} + +// This function converts a swagger style path URI with parameters to a +// Chi compatible path URI. We need to replace all of Swagger parameters with +// "{param}". Valid input parameters are: +// {param} +// {param*} +// {.param} +// {.param*} +// {;param} +// {;param*} +// {?param} +// {?param*} +func SwaggerUriToChiUri(uri string) string { + return pathParamRE.ReplaceAllString(uri, "{$1}") +} + +// Returns the argument names, in order, in a given URI string, so for +// /path/{param1}/{.param2*}/{?param3}, it would return param1, param2, param3 +func OrderedParamsFromUri(uri string) []string { + matches := pathParamRE.FindAllStringSubmatch(uri, -1) + result := make([]string, len(matches)) + for i, m := range matches { + result[i] = m[1] + } + return result +} + +// Replaces path parameters with %s +func ReplacePathParamsWithStr(uri string) string { + return pathParamRE.ReplaceAllString(uri, "%s") +} + +// Reorders the given parameter definitions to match those in the path URI. +func SortParamsByPath(path string, in []ParameterDefinition) ([]ParameterDefinition, error) { + pathParams := OrderedParamsFromUri(path) + n := len(in) + if len(pathParams) != n { + return nil, fmt.Errorf("path '%s' has %d positional parameters, but spec has %d declared", + path, len(pathParams), n) + } + out := make([]ParameterDefinition, len(in)) + for i, name := range pathParams { + p := ParameterDefinitions(in).FindByName(name) + if p == nil { + return nil, fmt.Errorf("path '%s' refers to parameter '%s', which doesn't exist in specification", + path, name) + } + out[i] = *p + } + return out, nil +} + +// Returns whether the given string is a go keyword +func IsGoKeyword(str string) bool { + keywords := []string{ + "break", + "case", + "chan", + "const", + "continue", + "default", + "defer", + "else", + "fallthrough", + "for", + "func", + "go", + "goto", + "if", + "import", + "interface", + "map", + "package", + "range", + "return", + "select", + "struct", + "switch", + "type", + "var", + } + + for _, k := range keywords { + if k == str { + return true + } + } + return false +} + +// IsPredeclaredGoIdentifier returns whether the given string +// is a predefined go indentifier. +// +// See https://golang.org/ref/spec#Predeclared_identifiers +func IsPredeclaredGoIdentifier(str string) bool { + predeclaredIdentifiers := []string{ + // Types + "bool", + "byte", + "complex64", + "complex128", + "error", + "float32", + "float64", + "int", + "int8", + "int16", + "int32", + "int64", + "rune", + "string", + "uint", + "uint8", + "uint16", + "uint32", + "uint64", + "uintptr", + // Constants + "true", + "false", + "iota", + // Zero value + "nil", + // Functions + "append", + "cap", + "close", + "complex", + "copy", + "delete", + "imag", + "len", + "make", + "new", + "panic", + "print", + "println", + "real", + "recover", + } + + for _, k := range predeclaredIdentifiers { + if k == str { + return true + } + } + + return false +} + +// IsGoIdentity checks if the given string can be used as an identity +// in the generated code like a type name or constant name. +// +// See https://golang.org/ref/spec#Identifiers +func IsGoIdentity(str string) bool { + for i, c := range str { + if !isValidRuneForGoID(i, c) { + return false + } + } + + return IsGoKeyword(str) +} + +func isValidRuneForGoID(index int, char rune) bool { + if index == 0 && unicode.IsNumber(char) { + return false + } + + return unicode.IsLetter(char) || char == '_' || unicode.IsNumber(char) +} + +// IsValidGoIdentity checks if the given string can be used as a +// name of variable, constant, or type. +func IsValidGoIdentity(str string) bool { + if IsGoIdentity(str) { + return false + } + + return !IsPredeclaredGoIdentifier(str) +} + +// SanitizeGoIdentity deletes and replaces the illegal runes in the given +// string to use the string as a valid identity. +func SanitizeGoIdentity(str string) string { + sanitized := []rune(str) + + for i, c := range sanitized { + if !isValidRuneForGoID(i, c) { + sanitized[i] = '_' + } else { + sanitized[i] = c + } + } + + str = string(sanitized) + + if IsGoKeyword(str) || IsPredeclaredGoIdentifier(str) { + str = "_" + str + } + + if !IsValidGoIdentity(str) { + panic("here is a bug") + } + + return str +} + +// SanitizeEnumNames fixes illegal chars in the enum names +// and removes duplicates +func SanitizeEnumNames(enumNames []string) map[string]string { + dupCheck := make(map[string]int, len(enumNames)) + deDup := make([]string, 0, len(enumNames)) + + for _, n := range enumNames { + if _, dup := dupCheck[n]; !dup { + deDup = append(deDup, n) + } + + dupCheck[n] = 0 + } + + dupCheck = make(map[string]int, len(deDup)) + sanitizedDeDup := make(map[string]string, len(deDup)) + + for _, n := range deDup { + sanitized := SanitizeGoIdentity(n) + + if _, dup := dupCheck[sanitized]; !dup { + sanitizedDeDup[sanitized] = n + dupCheck[sanitized]++ + } else { + sanitizedDeDup[sanitized+strconv.Itoa(dupCheck[sanitized])] = n + } + } + + return sanitizedDeDup +} + +// Converts a Schema name to a valid Go type name. It converts to camel case, and makes sure the name is +// valid in Go +func SchemaNameToTypeName(name string) string { + name = ToCamelCase(name) + // Prepend "N" to schemas starting with a number + if name != "" && unicode.IsDigit([]rune(name)[0]) { + name = "N" + name + } + return name +} + +// According to the spec, additionalProperties may be true, false, or a +// schema. If not present, true is implied. If it's a schema, true is implied. +// If it's false, no additional properties are allowed. We're going to act a little +// differently, in that if you want additionalProperties code to be generated, +// you must specify an additionalProperties type +// If additionalProperties it true/false, this field will be non-nil. +func SchemaHasAdditionalProperties(schema *openapi3.Schema) bool { + if schema.AdditionalPropertiesAllowed != nil { + return *schema.AdditionalPropertiesAllowed + } + if schema.AdditionalProperties != nil { + return true + } + return false +} + +// This converts a path, like Object/field1/nestedField into a go +// type name. +func PathToTypeName(path []string) string { + for i, p := range path { + path[i] = ToCamelCase(p) + } + return strings.Join(path, "_") +} + +// StringToGoComment renders a possible multi-line string as a valid Go-Comment. +// Each line is prefixed as a comment. +func StringToGoComment(in string) string { + // Normalize newlines from Windows/Mac to Linux + in = strings.Replace(in, "\r\n", "\n", -1) + in = strings.Replace(in, "\r", "\n", -1) + + // Add comment to each line + var lines []string + for _, line := range strings.Split(in, "\n") { + lines = append(lines, fmt.Sprintf("// %s", line)) + } + in = strings.Join(lines, "\n") + + // in case we have a multiline string which ends with \n, we would generate + // empty-line-comments, like `// `. Therefore remove this line comment. + in = strings.TrimSuffix(in, "\n// ") + return in +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/util/inputmapping.go b/vendor/github.com/deepmap/oapi-codegen/pkg/util/inputmapping.go new file mode 100644 index 000000000..3557ce56a --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/util/inputmapping.go @@ -0,0 +1,60 @@ +package util + +import ( + "fmt" + "strings" +) + +// The input mapping is experessed on the command line as `key1:value1,key2:value2,...` +// We parse it here, but need to keep in mind that keys or values may contain +// commas and colons. We will allow escaping those using double quotes, so +// when passing in "key1":"value1", we will not look inside the quoted sections. +func ParseCommandlineMap(src string) (map[string]string, error) { + result := make(map[string]string) + tuples := splitString(src, ',') + for _, t := range tuples { + kv := splitString(t, ':') + if len(kv) != 2 { + return nil, fmt.Errorf("expected key:value, got :%s", t) + } + key := strings.TrimLeft(kv[0], `"`) + key = strings.TrimRight(key, `"`) + + value := strings.TrimLeft(kv[1], `"`) + value = strings.TrimRight(value, `"`) + + result[key] = value + } + return result, nil +} + +// This function splits a string along the specifed separator, but it +// ignores anything between double quotes for splitting. We do simple +// inside/outside quote counting. Quotes are not stripped from output. +func splitString(s string, sep rune) ([]string) { + const escapeChar rune = '"' + + var parts []string + var part string + inQuotes := false + + for _, c := range s { + if c == escapeChar{ + if inQuotes { + inQuotes = false + } else { + inQuotes = true + } + } + + // If we've gotten the separator rune, consider the previous part + // complete, but only if we're outside of quoted sections + if c == sep && !inQuotes { + parts = append(parts, part) + part = "" + continue + } + part = part + string(c) + } + return append(parts, part) +} diff --git a/vendor/github.com/deepmap/oapi-codegen/pkg/util/loader.go b/vendor/github.com/deepmap/oapi-codegen/pkg/util/loader.go new file mode 100644 index 000000000..482562016 --- /dev/null +++ b/vendor/github.com/deepmap/oapi-codegen/pkg/util/loader.go @@ -0,0 +1,20 @@ +package util + +import ( + "net/url" + + "github.com/getkin/kin-openapi/openapi3" +) + +func LoadSwagger(filePath string) (swagger *openapi3.Swagger, err error) { + + loader := openapi3.NewSwaggerLoader() + loader.IsExternalRefsAllowed = true + + u, err := url.Parse(filePath) + if err == nil && u.Scheme != "" && u.Host != "" { + return loader.LoadSwaggerFromURI(u) + } else { + return loader.LoadSwaggerFromFile(filePath) + } +} diff --git a/vendor/github.com/getkin/kin-openapi/LICENSE b/vendor/github.com/getkin/kin-openapi/LICENSE new file mode 100644 index 000000000..992b9831e --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2018 the project authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/doc.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/doc.go new file mode 100644 index 000000000..e59ec2c34 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/doc.go @@ -0,0 +1,2 @@ +// Package jsoninfo provides information and functions for marshalling/unmarshalling JSON. +package jsoninfo diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go new file mode 100644 index 000000000..d949a79d3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go @@ -0,0 +1,122 @@ +package jsoninfo + +import ( + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// FieldInfo contains information about JSON serialization of a field. +type FieldInfo struct { + MultipleFields bool // Whether multiple Go fields share this JSON name + HasJSONTag bool + TypeIsMarshaller bool + TypeIsUnmarshaller bool + JSONOmitEmpty bool + JSONString bool + Index []int + Type reflect.Type + JSONName string +} + +func AppendFields(fields []FieldInfo, parentIndex []int, t reflect.Type) []FieldInfo { + // For each field + numField := t.NumField() +iteration: + for i := 0; i < numField; i++ { + f := t.Field(i) + index := make([]int, 0, len(parentIndex)+1) + index = append(index, parentIndex...) + index = append(index, i) + + // See whether this is an embedded field + if f.Anonymous { + if f.Tag.Get("json") == "-" { + continue + } + fields = AppendFields(fields, index, f.Type) + continue iteration + } + + // Ignore certain types + switch f.Type.Kind() { + case reflect.Func, reflect.Chan: + continue iteration + } + + // Is it a private (lowercase) field? + firstRune, _ := utf8.DecodeRuneInString(f.Name) + if unicode.IsLower(firstRune) { + continue iteration + } + + // Declare a field + field := FieldInfo{ + Index: index, + Type: f.Type, + JSONName: f.Name, + } + + // Read "json" tag + jsonTag := f.Tag.Get("json") + + // Read our custom "multijson" tag that + // allows multiple fields with the same name. + if v := f.Tag.Get("multijson"); len(v) > 0 { + field.MultipleFields = true + jsonTag = v + } + + // Handle "-" + if jsonTag == "-" { + continue + } + + // Parse the tag + if len(jsonTag) > 0 { + field.HasJSONTag = true + for i, part := range strings.Split(jsonTag, ",") { + if i == 0 { + if len(part) > 0 { + field.JSONName = part + } + } else { + switch part { + case "omitempty": + field.JSONOmitEmpty = true + case "string": + field.JSONString = true + } + } + } + } + + if _, ok := field.Type.MethodByName("MarshalJSON"); ok { + field.TypeIsMarshaller = true + } + if _, ok := field.Type.MethodByName("UnmarshalJSON"); ok { + field.TypeIsUnmarshaller = true + } + + // Field is done + fields = append(fields, field) + } + + return fields +} + +type sortableFieldInfos []FieldInfo + +func (list sortableFieldInfos) Len() int { + return len(list) +} + +func (list sortableFieldInfos) Less(i, j int) bool { + return list[i].JSONName < list[j].JSONName +} + +func (list sortableFieldInfos) Swap(i, j int) { + a, b := list[i], list[j] + list[i], list[j] = b, a +} diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go new file mode 100644 index 000000000..93de99a56 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go @@ -0,0 +1,162 @@ +package jsoninfo + +import ( + "encoding/json" + "fmt" + "reflect" +) + +// MarshalStrictStruct function: +// * Marshals struct fields, ignoring MarshalJSON() and fields without 'json' tag. +// * Correctly handles StrictStruct semantics. +func MarshalStrictStruct(value StrictStruct) ([]byte, error) { + encoder := NewObjectEncoder() + if err := value.EncodeWith(encoder, value); err != nil { + return nil, err + } + return encoder.Bytes() +} + +type ObjectEncoder struct { + result map[string]json.RawMessage +} + +func NewObjectEncoder() *ObjectEncoder { + return &ObjectEncoder{ + result: make(map[string]json.RawMessage, 8), + } +} + +// Bytes returns the result of encoding. +func (encoder *ObjectEncoder) Bytes() ([]byte, error) { + return json.Marshal(encoder.result) +} + +// EncodeExtension adds a key/value to the current JSON object. +func (encoder *ObjectEncoder) EncodeExtension(key string, value interface{}) error { + data, err := json.Marshal(value) + if err != nil { + return err + } + encoder.result[key] = data + return nil +} + +// EncodeExtensionMap adds all properties to the result. +func (encoder *ObjectEncoder) EncodeExtensionMap(value map[string]json.RawMessage) error { + if value != nil { + result := encoder.result + for k, v := range value { + result[k] = v + } + } + return nil +} + +func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{}) error { + reflection := reflect.ValueOf(value) + + // Follow "encoding/json" semantics + if reflection.Kind() != reflect.Ptr { + // Panic because this is a clear programming error + panic(fmt.Errorf("Value %s is not a pointer", reflection.Type().String())) + } + if reflection.IsNil() { + // Panic because this is a clear programming error + panic(fmt.Errorf("Value %s is nil", reflection.Type().String())) + } + + // Take the element + reflection = reflection.Elem() + + // Obtain typeInfo + typeInfo := GetTypeInfo(reflection.Type()) + + // Declare result + result := encoder.result + + // Supported fields +iteration: + for _, field := range typeInfo.Fields { + // Fields without JSON tag are ignored + if !field.HasJSONTag { + continue + } + + // Marshal + fieldValue := reflection.FieldByIndex(field.Index) + if v, ok := fieldValue.Interface().(json.Marshaler); ok { + if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + if field.JSONOmitEmpty { + continue iteration + } + result[field.JSONName] = []byte("null") + continue + } + fieldData, err := v.MarshalJSON() + if err != nil { + return err + } + result[field.JSONName] = fieldData + continue + } + switch fieldValue.Kind() { + case reflect.Ptr, reflect.Interface: + if fieldValue.IsNil() { + if field.JSONOmitEmpty { + continue iteration + } + result[field.JSONName] = []byte("null") + continue + } + case reflect.Struct: + case reflect.Map: + if field.JSONOmitEmpty && (fieldValue.IsNil() || fieldValue.Len() == 0) { + continue iteration + } + case reflect.Slice: + if field.JSONOmitEmpty && fieldValue.Len() == 0 { + continue iteration + } + case reflect.Bool: + x := fieldValue.Bool() + if field.JSONOmitEmpty && !x { + continue iteration + } + s := "false" + if x { + s = "true" + } + result[field.JSONName] = []byte(s) + continue iteration + case reflect.Int64, reflect.Int, reflect.Int32: + if field.JSONOmitEmpty && fieldValue.Int() == 0 { + continue iteration + } + case reflect.Uint64, reflect.Uint, reflect.Uint32: + if field.JSONOmitEmpty && fieldValue.Uint() == 0 { + continue iteration + } + case reflect.Float64: + if field.JSONOmitEmpty && fieldValue.Float() == 0.0 { + continue iteration + } + case reflect.String: + if field.JSONOmitEmpty && len(fieldValue.String()) == 0 { + continue iteration + } + default: + panic(fmt.Errorf("Field '%s' has unsupported type %s", field.JSONName, field.Type.String())) + } + + // No special treament is needed + // Use plain old "encoding/json".Marshal + fieldData, err := json.Marshal(fieldValue.Addr().Interface()) + if err != nil { + return err + } + result[field.JSONName] = fieldData + } + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go new file mode 100644 index 000000000..9738bf08f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go @@ -0,0 +1,30 @@ +package jsoninfo + +import ( + "encoding/json" +) + +func MarshalRef(value string, otherwise interface{}) ([]byte, error) { + if len(value) > 0 { + return json.Marshal(&refProps{ + Ref: value, + }) + } + return json.Marshal(otherwise) +} + +func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error { + refProps := &refProps{} + if err := json.Unmarshal(data, refProps); err == nil { + ref := refProps.Ref + if len(ref) > 0 { + *destRef = ref + return nil + } + } + return json.Unmarshal(data, destOtherwise) +} + +type refProps struct { + Ref string `json:"$ref,omitempty"` +} diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/strict_struct.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/strict_struct.go new file mode 100644 index 000000000..6b4d83977 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/strict_struct.go @@ -0,0 +1,6 @@ +package jsoninfo + +type StrictStruct interface { + EncodeWith(encoder *ObjectEncoder, value interface{}) error + DecodeWith(decoder *ObjectDecoder, value interface{}) error +} diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/type_info.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/type_info.go new file mode 100644 index 000000000..3dbb8d5d6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/type_info.go @@ -0,0 +1,68 @@ +package jsoninfo + +import ( + "reflect" + "sort" + "sync" +) + +var ( + typeInfos = map[reflect.Type]*TypeInfo{} + typeInfosMutex sync.RWMutex +) + +// TypeInfo contains information about JSON serialization of a type +type TypeInfo struct { + Type reflect.Type + Fields []FieldInfo +} + +func GetTypeInfoForValue(value interface{}) *TypeInfo { + return GetTypeInfo(reflect.TypeOf(value)) +} + +// GetTypeInfo returns TypeInfo for the given type. +func GetTypeInfo(t reflect.Type) *TypeInfo { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + typeInfosMutex.RLock() + typeInfo, exists := typeInfos[t] + typeInfosMutex.RUnlock() + if exists { + return typeInfo + } + if t.Kind() != reflect.Struct { + typeInfo = &TypeInfo{ + Type: t, + } + } else { + // Allocate + typeInfo = &TypeInfo{ + Type: t, + Fields: make([]FieldInfo, 0, 16), + } + + // Add fields + typeInfo.Fields = AppendFields(nil, nil, t) + + // Sort fields + sort.Sort(sortableFieldInfos(typeInfo.Fields)) + } + + // Publish + typeInfosMutex.Lock() + typeInfos[t] = typeInfo + typeInfosMutex.Unlock() + return typeInfo +} + +// FieldNames returns all field names +func (typeInfo *TypeInfo) FieldNames() []string { + fields := typeInfo.Fields + names := make([]string, 0, len(fields)) + for _, field := range fields { + names = append(names, field.JSONName) + } + return names +} diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go new file mode 100644 index 000000000..329718758 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go @@ -0,0 +1,121 @@ +package jsoninfo + +import ( + "encoding/json" + "fmt" + "reflect" +) + +// UnmarshalStrictStruct function: +// * Unmarshals struct fields, ignoring UnmarshalJSON(...) and fields without 'json' tag. +// * Correctly handles StrictStruct +func UnmarshalStrictStruct(data []byte, value StrictStruct) error { + decoder, err := NewObjectDecoder(data) + if err != nil { + return err + } + return value.DecodeWith(decoder, value) +} + +type ObjectDecoder struct { + Data []byte + remainingFields map[string]json.RawMessage +} + +func NewObjectDecoder(data []byte) (*ObjectDecoder, error) { + var remainingFields map[string]json.RawMessage + if err := json.Unmarshal(data, &remainingFields); err != nil { + return nil, fmt.Errorf("Failed to unmarshal extension properties: %v\nInput: %s", err, data) + } + return &ObjectDecoder{ + Data: data, + remainingFields: remainingFields, + }, nil +} + +// DecodeExtensionMap returns all properties that were not decoded previously. +func (decoder *ObjectDecoder) DecodeExtensionMap() map[string]json.RawMessage { + return decoder.remainingFields +} + +func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) error { + reflection := reflect.ValueOf(value) + if reflection.Kind() != reflect.Ptr { + panic(fmt.Errorf("Value %T is not a pointer", value)) + } + if reflection.IsNil() { + panic(fmt.Errorf("Value %T is nil", value)) + } + reflection = reflection.Elem() + for (reflection.Kind() == reflect.Interface || reflection.Kind() == reflect.Ptr) && !reflection.IsNil() { + reflection = reflection.Elem() + } + reflectionType := reflection.Type() + if reflectionType.Kind() != reflect.Struct { + panic(fmt.Errorf("Value %T is not a struct", value)) + } + typeInfo := GetTypeInfo(reflectionType) + + // Supported fields + fields := typeInfo.Fields + remainingFields := decoder.remainingFields + for fieldIndex, field := range fields { + // Fields without JSON tag are ignored + if !field.HasJSONTag { + continue + } + + // Get data + fieldData, exists := remainingFields[field.JSONName] + if !exists { + continue + } + + // Unmarshal + if field.TypeIsUnmarshaller { + fieldType := field.Type + isPtr := false + if fieldType.Kind() == reflect.Ptr { + fieldType = fieldType.Elem() + isPtr = true + } + fieldValue := reflect.New(fieldType) + if err := fieldValue.Interface().(json.Unmarshaler).UnmarshalJSON(fieldData); err != nil { + if field.MultipleFields { + i := fieldIndex + 1 + if i < len(fields) && fields[i].JSONName == field.JSONName { + continue + } + } + return fmt.Errorf("Error while unmarshalling property '%s' (%s): %v", + field.JSONName, fieldValue.Type().String(), err) + } + if !isPtr { + fieldValue = fieldValue.Elem() + } + reflection.FieldByIndex(field.Index).Set(fieldValue) + + // Remove the field from remaining fields + delete(remainingFields, field.JSONName) + } else { + fieldPtr := reflection.FieldByIndex(field.Index) + if fieldPtr.Kind() != reflect.Ptr || fieldPtr.IsNil() { + fieldPtr = fieldPtr.Addr() + } + if err := json.Unmarshal(fieldData, fieldPtr.Interface()); err != nil { + if field.MultipleFields { + i := fieldIndex + 1 + if i < len(fields) && fields[i].JSONName == field.JSONName { + continue + } + } + return fmt.Errorf("Error while unmarshalling property '%s' (%s): %v", + field.JSONName, fieldPtr.Type().String(), err) + } + + // Remove the field from remaining fields + delete(remainingFields, field.JSONName) + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go new file mode 100644 index 000000000..258efef28 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go @@ -0,0 +1,45 @@ +package jsoninfo + +import ( + "encoding/json" + "fmt" + "sort" + "strings" +) + +// UnsupportedPropertiesError is a helper for extensions that want to refuse +// unsupported JSON object properties. +// +// It produces a helpful error message. +type UnsupportedPropertiesError struct { + Value interface{} + UnsupportedProperties map[string]json.RawMessage +} + +func NewUnsupportedPropertiesError(v interface{}, m map[string]json.RawMessage) error { + return &UnsupportedPropertiesError{ + Value: v, + UnsupportedProperties: m, + } +} + +func (err *UnsupportedPropertiesError) Error() string { + m := err.UnsupportedProperties + typeInfo := GetTypeInfoForValue(err.Value) + if m == nil || typeInfo == nil { + return "Invalid UnsupportedPropertiesError" + } + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + supported := typeInfo.FieldNames() + if len(supported) == 0 { + return fmt.Sprintf("Type '%T' doesn't take any properties. Unsupported properties: '%s'\n", + err.Value, strings.Join(keys, "', '")) + } + return fmt.Sprintf("Unsupported properties: '%s'\nSupported properties are: '%s'", + strings.Join(keys, "', '"), + strings.Join(supported, "', '")) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go new file mode 100644 index 000000000..60196ba16 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -0,0 +1,15 @@ +package openapi3 + +import "context" + +// Callback is specified by OpenAPI/Swagger standard version 3.0. +type Callback map[string]*PathItem + +func (value Callback) Validate(c context.Context) error { + for _, v := range value { + if err := v.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/components.go b/vendor/github.com/getkin/kin-openapi/openapi3/components.go new file mode 100644 index 000000000..78b66aa31 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/components.go @@ -0,0 +1,104 @@ +package openapi3 + +import ( + "context" + "fmt" + "regexp" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Components is specified by OpenAPI/Swagger standard version 3.0. +type Components struct { + ExtensionProps + Schemas map[string]*SchemaRef `json:"schemas,omitempty" yaml:"schemas,omitempty"` + Parameters map[string]*ParameterRef `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Headers map[string]*HeaderRef `json:"headers,omitempty" yaml:"headers,omitempty"` + RequestBodies map[string]*RequestBodyRef `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` + Responses map[string]*ResponseRef `json:"responses,omitempty" yaml:"responses,omitempty"` + SecuritySchemes map[string]*SecuritySchemeRef `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` + Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"` + Links map[string]*LinkRef `json:"links,omitempty" yaml:"links,omitempty"` + Callbacks map[string]*CallbackRef `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` +} + +func NewComponents() Components { + return Components{} +} + +func (components *Components) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(components) +} + +func (components *Components) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, components) +} + +func (components *Components) Validate(c context.Context) (err error) { + for k, v := range components.Schemas { + if err = ValidateIdentifier(k); err != nil { + return + } + if err = v.Validate(c); err != nil { + return + } + } + + for k, v := range components.Parameters { + if err = ValidateIdentifier(k); err != nil { + return + } + if err = v.Validate(c); err != nil { + return + } + } + + for k, v := range components.RequestBodies { + if err = ValidateIdentifier(k); err != nil { + return + } + if err = v.Validate(c); err != nil { + return + } + } + + for k, v := range components.Responses { + if err = ValidateIdentifier(k); err != nil { + return + } + if err = v.Validate(c); err != nil { + return + } + } + + for k, v := range components.Headers { + if err = ValidateIdentifier(k); err != nil { + return + } + if err = v.Validate(c); err != nil { + return + } + } + + for k, v := range components.SecuritySchemes { + if err = ValidateIdentifier(k); err != nil { + return + } + if err = v.Validate(c); err != nil { + return + } + } + + return +} + +const identifierPattern = `^[a-zA-Z0-9.\-_]+$` + +var identifierRegExp = regexp.MustCompile(identifierPattern) + +func ValidateIdentifier(value string) error { + if identifierRegExp.MatchString(value) { + return nil + } + return fmt.Errorf("Identifier '%s' is not supported by OpenAPI version 3 standard (regexp: '%s')", value, identifierPattern) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/content.go b/vendor/github.com/getkin/kin-openapi/openapi3/content.go new file mode 100644 index 000000000..8d187fd91 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/content.go @@ -0,0 +1,77 @@ +package openapi3 + +import ( + "context" + "strings" +) + +// Content is specified by OpenAPI/Swagger 3.0 standard. +type Content map[string]*MediaType + +func NewContent() Content { + return make(map[string]*MediaType, 4) +} + +func NewContentWithJSONSchema(schema *Schema) Content { + return Content{ + "application/json": NewMediaType().WithSchema(schema), + } +} +func NewContentWithJSONSchemaRef(schema *SchemaRef) Content { + return Content{ + "application/json": NewMediaType().WithSchemaRef(schema), + } +} + +func (content Content) Get(mime string) *MediaType { + // If the mime is empty then short-circuit to the wildcard. + // We do this here so that we catch only the specific case of + // and empty mime rather than a present, but invalid, mime type. + if mime == "" { + return content["*/*"] + } + // Start by making the most specific match possible + // by using the mime type in full. + if v := content[mime]; v != nil { + return v + } + // If an exact match is not found then we strip all + // metadata from the mime type and only use the x/y + // portion. + i := strings.IndexByte(mime, ';') + if i < 0 { + // If there is no metadata then preserve the full mime type + // string for later wildcard searches. + i = len(mime) + } + mime = mime[:i] + if v := content[mime]; v != nil { + return v + } + // If the x/y pattern has no specific match then we + // try the x/* pattern. + i = strings.IndexByte(mime, '/') + if i < 0 { + // In the case that the given mime type is not valid because it is + // missing the subtype we return nil so that this does not accidentally + // resolve with the wildcard. + return nil + } + mime = mime[:i] + "/*" + if v := content[mime]; v != nil { + return v + } + // Finally, the most generic match of */* is returned + // as a catch-all. + return content["*/*"] +} + +func (content Content) Validate(c context.Context) error { + for _, v := range content { + // Validate MediaType + if err := v.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go new file mode 100644 index 000000000..de518d578 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -0,0 +1,26 @@ +package openapi3 + +import ( + "context" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Discriminator is specified by OpenAPI/Swagger standard version 3.0. +type Discriminator struct { + ExtensionProps + PropertyName string `json:"propertyName" yaml:"propertyName"` + Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` +} + +func (value *Discriminator) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(value) +} + +func (value *Discriminator) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *Discriminator) Validate(c context.Context) error { + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go new file mode 100644 index 000000000..9f9554962 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go @@ -0,0 +1,5 @@ +// Package openapi3 parses and writes OpenAPI 3 specifications. +// +// The OpenAPI 3.0 specification can be found at: +// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.md +package openapi3 diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go new file mode 100644 index 000000000..a60bddf82 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -0,0 +1,93 @@ +package openapi3 + +import ( + "context" + "fmt" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Encoding is specified by OpenAPI/Swagger 3.0 standard. +type Encoding struct { + ExtensionProps + + ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` + Headers map[string]*HeaderRef `json:"headers,omitempty" yaml:"headers,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` +} + +func NewEncoding() *Encoding { + return &Encoding{} +} + +func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding { + return encoding.WithHeaderRef(name, &HeaderRef{ + Value: header, + }) +} + +func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding { + headers := encoding.Headers + if headers == nil { + headers = make(map[string]*HeaderRef) + encoding.Headers = headers + } + headers[name] = ref + return encoding +} + +func (encoding *Encoding) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(encoding) +} + +func (encoding *Encoding) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, encoding) +} + +// SerializationMethod returns a serialization method of request body. +// When serialization method is not defined the method returns the default serialization method. +func (encoding *Encoding) SerializationMethod() *SerializationMethod { + sm := &SerializationMethod{Style: SerializationForm, Explode: true} + if encoding != nil { + if encoding.Style != "" { + sm.Style = encoding.Style + } + if encoding.Explode != nil { + sm.Explode = *encoding.Explode + } + } + return sm +} + +func (encoding *Encoding) Validate(c context.Context) error { + if encoding == nil { + return nil + } + for k, v := range encoding.Headers { + if err := ValidateIdentifier(k); err != nil { + return nil + } + if err := v.Validate(c); err != nil { + return nil + } + } + + // Validate a media types's serialization method. + sm := encoding.SerializationMethod() + switch { + case sm.Style == SerializationForm && sm.Explode, + sm.Style == SerializationForm && !sm.Explode, + sm.Style == SerializationSpaceDelimited && sm.Explode, + sm.Style == SerializationSpaceDelimited && !sm.Explode, + sm.Style == SerializationPipeDelimited && sm.Explode, + sm.Style == SerializationPipeDelimited && !sm.Explode, + sm.Style == SerializationDeepObject && sm.Explode: + // it is a valid + default: + return fmt.Errorf("Serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode) + } + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/examples.go b/vendor/github.com/getkin/kin-openapi/openapi3/examples.go new file mode 100644 index 000000000..d89263ebc --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/examples.go @@ -0,0 +1,29 @@ +package openapi3 + +import ( + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Example is specified by OpenAPI/Swagger 3.0 standard. +type Example struct { + ExtensionProps + + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` + ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"` +} + +func NewExample(value interface{}) *Example { + return &Example{ + Value: value, + } +} + +func (example *Example) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(example) +} + +func (example *Example) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, example) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/extension.go b/vendor/github.com/getkin/kin-openapi/openapi3/extension.go new file mode 100644 index 000000000..f6b7ef9bb --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/extension.go @@ -0,0 +1,38 @@ +package openapi3 + +import ( + "github.com/getkin/kin-openapi/jsoninfo" +) + +// ExtensionProps provides support for OpenAPI extensions. +// It reads/writes all properties that begin with "x-". +type ExtensionProps struct { + Extensions map[string]interface{} `json:"-" yaml:"-"` +} + +// Assert that the type implements the interface +var _ jsoninfo.StrictStruct = &ExtensionProps{} + +// EncodeWith will be invoked by package "jsoninfo" +func (props *ExtensionProps) EncodeWith(encoder *jsoninfo.ObjectEncoder, value interface{}) error { + for k, v := range props.Extensions { + if err := encoder.EncodeExtension(k, v); err != nil { + return err + } + } + return encoder.EncodeStructFieldsAndExtensions(value) +} + +// DecodeWith will be invoked by package "jsoninfo" +func (props *ExtensionProps) DecodeWith(decoder *jsoninfo.ObjectDecoder, value interface{}) error { + if err := decoder.DecodeStructFieldsAndExtensions(value); err != nil { + return err + } + source := decoder.DecodeExtensionMap() + result := make(map[string]interface{}, len(source)) + for k, v := range source { + result[k] = v + } + props.Extensions = result + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go new file mode 100644 index 000000000..5a1476bde --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go @@ -0,0 +1,21 @@ +package openapi3 + +import ( + "github.com/getkin/kin-openapi/jsoninfo" +) + +// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0. +type ExternalDocs struct { + ExtensionProps + + Description string `json:"description,omitempty"` + URL string `json:"url,omitempty"` +} + +func (e *ExternalDocs) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(e) +} + +func (e *ExternalDocs) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, e) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/vendor/github.com/getkin/kin-openapi/openapi3/header.go new file mode 100644 index 000000000..310ef9f92 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/header.go @@ -0,0 +1,33 @@ +package openapi3 + +import ( + "context" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +type Header struct { + ExtensionProps + + // Optional description. Should use CommonMark syntax. + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` + Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` +} + +func (value *Header) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *Header) Validate(c context.Context) error { + if v := value.Schema; v != nil { + if err := v.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/info.go b/vendor/github.com/getkin/kin-openapi/openapi3/info.go new file mode 100644 index 000000000..25e675e66 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/info.go @@ -0,0 +1,93 @@ +package openapi3 + +import ( + "context" + "errors" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Info is specified by OpenAPI/Swagger standard version 3.0. +type Info struct { + ExtensionProps + Title string `json:"title" yaml:"title"` // Required + Description string `json:"description,omitempty" yaml:"description,omitempty"` + TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` + Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"` + License *License `json:"license,omitempty" yaml:"license,omitempty"` + Version string `json:"version" yaml:"version"` // Required +} + +func (value *Info) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(value) +} + +func (value *Info) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *Info) Validate(c context.Context) error { + if contact := value.Contact; contact != nil { + if err := contact.Validate(c); err != nil { + return err + } + } + + if license := value.License; license != nil { + if err := license.Validate(c); err != nil { + return err + } + } + + if value.Version == "" { + return errors.New("value of version must be a non-empty JSON string") + } + + if value.Title == "" { + return errors.New("value of title must be a non-empty JSON string") + } + + return nil +} + +// Contact is specified by OpenAPI/Swagger standard version 3.0. +type Contact struct { + ExtensionProps + Name string `json:"name,omitempty" yaml:"name,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` + Email string `json:"email,omitempty" yaml:"email,omitempty"` +} + +func (value *Contact) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(value) +} + +func (value *Contact) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *Contact) Validate(c context.Context) error { + return nil +} + +// License is specified by OpenAPI/Swagger standard version 3.0. +type License struct { + ExtensionProps + Name string `json:"name" yaml:"name"` // Required + URL string `json:"url,omitempty" yaml:"url,omitempty"` +} + +func (value *License) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(value) +} + +func (value *License) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *License) Validate(c context.Context) error { + if value.Name == "" { + return errors.New("value of license name must be a non-empty JSON string") + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/link.go b/vendor/github.com/getkin/kin-openapi/openapi3/link.go new file mode 100644 index 000000000..2c1ec013f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -0,0 +1,38 @@ +package openapi3 + +import ( + "context" + "errors" + "fmt" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Link is specified by OpenAPI/Swagger standard version 3.0. +type Link struct { + ExtensionProps + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Server *Server `json:"server,omitempty" yaml:"server,omitempty"` + RequestBody interface{} `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` +} + +func (value *Link) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(value) +} + +func (value *Link) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *Link) Validate(c context.Context) error { + if value.OperationID == "" && value.OperationRef == "" { + return errors.New("missing operationId or operationRef on link") + } + if value.OperationID != "" && value.OperationRef != "" { + return fmt.Errorf("operationId '%s' and operationRef '%s' are mutually exclusive", value.OperationID, value.OperationRef) + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go new file mode 100644 index 000000000..3b8be81c7 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -0,0 +1,79 @@ +package openapi3 + +import ( + "context" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// MediaType is specified by OpenAPI/Swagger 3.0 standard. +type MediaType struct { + ExtensionProps + + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` + Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"` + Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"` +} + +func NewMediaType() *MediaType { + return &MediaType{} +} + +func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType { + if schema == nil { + mediaType.Schema = nil + } else { + mediaType.Schema = &SchemaRef{ + Value: schema, + } + } + return mediaType +} + +func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType { + mediaType.Schema = schema + return mediaType +} + +func (mediaType *MediaType) WithExample(name string, value interface{}) *MediaType { + example := mediaType.Examples + if example == nil { + example = make(map[string]*ExampleRef) + mediaType.Examples = example + } + example[name] = &ExampleRef{ + Value: NewExample(value), + } + return mediaType +} + +func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType { + encoding := mediaType.Encoding + if encoding == nil { + encoding = make(map[string]*Encoding) + mediaType.Encoding = encoding + } + encoding[name] = enc + return mediaType +} + +func (mediaType *MediaType) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(mediaType) +} + +func (mediaType *MediaType) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, mediaType) +} + +func (mediaType *MediaType) Validate(c context.Context) error { + if mediaType == nil { + return nil + } + if schema := mediaType.Schema; schema != nil { + if err := schema.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go new file mode 100644 index 000000000..9e64f031e --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -0,0 +1,102 @@ +package openapi3 + +import ( + "context" + "errors" + "strconv" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard. +type Operation struct { + ExtensionProps + + // Optional tags for documentation. + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + + // Optional short summary. + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + + // Optional description. Should use CommonMark syntax. + Description string `json:"description,omitempty" yaml:"description,omitempty"` + + // Optional operation ID. + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + + // Optional parameters. + Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` + + // Optional body parameter. + RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + + // Responses. + Responses Responses `json:"responses" yaml:"responses"` // Required + + // Optional callbacks + Callbacks map[string]*CallbackRef `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` + + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + + // Optional security requirements that overrides top-level security. + Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + + // Optional servers that overrides top-level servers. + Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} + +func NewOperation() *Operation { + return &Operation{} +} + +func (operation *Operation) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(operation) +} + +func (operation *Operation) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, operation) +} + +func (operation *Operation) AddParameter(p *Parameter) { + operation.Parameters = append(operation.Parameters, &ParameterRef{ + Value: p, + }) +} + +func (operation *Operation) AddResponse(status int, response *Response) { + responses := operation.Responses + if responses == nil { + responses = NewResponses() + operation.Responses = responses + } + code := "default" + if status != 0 { + code = strconv.FormatInt(int64(status), 10) + } + responses[code] = &ResponseRef{ + Value: response, + } +} + +func (operation *Operation) Validate(c context.Context) error { + if v := operation.Parameters; v != nil { + if err := v.Validate(c); err != nil { + return err + } + } + if v := operation.RequestBody; v != nil { + if err := v.Validate(c); err != nil { + return err + } + } + if v := operation.Responses; v != nil { + if err := v.Validate(c); err != nil { + return err + } + } else { + return errors.New("value of responses must be a JSON object") + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go new file mode 100644 index 000000000..1e2f55e17 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -0,0 +1,224 @@ +package openapi3 + +import ( + "context" + "errors" + "fmt" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Parameters is specified by OpenAPI/Swagger 3.0 standard. +type Parameters []*ParameterRef + +func NewParameters() Parameters { + return make(Parameters, 0, 4) +} + +func (parameters Parameters) GetByInAndName(in string, name string) *Parameter { + for _, item := range parameters { + if v := item.Value; v != nil { + if v.Name == name && v.In == in { + return v + } + } + } + return nil +} + +func (parameters Parameters) Validate(c context.Context) error { + dupes := make(map[string]struct{}) + for _, item := range parameters { + if v := item.Value; v != nil { + key := v.In + ":" + v.Name + if _, ok := dupes[key]; ok { + return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name) + } + dupes[key] = struct{}{} + } + + if err := item.Validate(c); err != nil { + return err + } + } + return nil +} + +// Parameter is specified by OpenAPI/Swagger 3.0 standard. +type Parameter struct { + ExtensionProps + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` + Examples map[string]*ExampleRef `json:"examples,omitempty" yaml:"examples,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` +} + +const ( + ParameterInPath = "path" + ParameterInQuery = "query" + ParameterInHeader = "header" + ParameterInCookie = "cookie" +) + +func NewPathParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInPath, + Required: true, + } +} + +func NewQueryParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInQuery, + } +} + +func NewHeaderParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInHeader, + } +} + +func NewCookieParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInCookie, + } +} + +func (parameter *Parameter) WithDescription(value string) *Parameter { + parameter.Description = value + return parameter +} + +func (parameter *Parameter) WithRequired(value bool) *Parameter { + parameter.Required = value + return parameter +} + +func (parameter *Parameter) WithSchema(value *Schema) *Parameter { + if value == nil { + parameter.Schema = nil + } else { + parameter.Schema = &SchemaRef{ + Value: value, + } + } + return parameter +} + +func (parameter *Parameter) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(parameter) +} + +func (parameter *Parameter) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, parameter) +} + +// SerializationMethod returns a parameter's serialization method. +// When a parameter's serialization method is not defined the method returns +// the default serialization method corresponding to a parameter's location. +func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) { + switch parameter.In { + case ParameterInPath, ParameterInHeader: + style := parameter.Style + if style == "" { + style = SerializationSimple + } + explode := false + if parameter.Explode != nil { + explode = *parameter.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil + case ParameterInQuery, ParameterInCookie: + style := parameter.Style + if style == "" { + style = SerializationForm + } + explode := true + if parameter.Explode != nil { + explode = *parameter.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil + default: + return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In) + } +} + +func (parameter *Parameter) Validate(c context.Context) error { + if parameter.Name == "" { + return errors.New("parameter name can't be blank") + } + in := parameter.In + switch in { + case + ParameterInPath, + ParameterInQuery, + ParameterInHeader, + ParameterInCookie: + default: + return fmt.Errorf("parameter can't have 'in' value %q", parameter.In) + } + + // Validate a parameter's serialization method. + sm, err := parameter.SerializationMethod() + if err != nil { + return err + } + var smSupported bool + switch { + case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode, + parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode, + + parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode, + parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode, + + parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode, + parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode, + + parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode, + parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode: + smSupported = true + } + if !smSupported { + e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in) + return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e) + } + + if (parameter.Schema == nil) == (parameter.Content == nil) { + e := errors.New("parameter must contain exactly one of content and schema") + return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e) + } + if schema := parameter.Schema; schema != nil { + if err := schema.Validate(c); err != nil { + return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, err) + } + } + if content := parameter.Content; content != nil { + if err := content.Validate(c); err != nil { + return fmt.Errorf("parameter %q content is invalid: %v", parameter.Name, err) + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go new file mode 100644 index 000000000..2ed578aa0 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -0,0 +1,126 @@ +package openapi3 + +import ( + "context" + "fmt" + "net/http" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +type PathItem struct { + ExtensionProps + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"` + Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"` + Get *Operation `json:"get,omitempty" yaml:"get,omitempty"` + Head *Operation `json:"head,omitempty" yaml:"head,omitempty"` + Options *Operation `json:"options,omitempty" yaml:"options,omitempty"` + Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"` + Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` + Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` + Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` +} + +func (pathItem *PathItem) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(pathItem) +} + +func (pathItem *PathItem) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, pathItem) +} + +func (pathItem *PathItem) Operations() map[string]*Operation { + operations := make(map[string]*Operation, 4) + if v := pathItem.Connect; v != nil { + operations[http.MethodConnect] = v + } + if v := pathItem.Delete; v != nil { + operations[http.MethodDelete] = v + } + if v := pathItem.Get; v != nil { + operations[http.MethodGet] = v + } + if v := pathItem.Head; v != nil { + operations[http.MethodHead] = v + } + if v := pathItem.Options; v != nil { + operations[http.MethodOptions] = v + } + if v := pathItem.Patch; v != nil { + operations[http.MethodPatch] = v + } + if v := pathItem.Post; v != nil { + operations[http.MethodPost] = v + } + if v := pathItem.Put; v != nil { + operations[http.MethodPut] = v + } + if v := pathItem.Trace; v != nil { + operations[http.MethodTrace] = v + } + return operations +} + +func (pathItem *PathItem) GetOperation(method string) *Operation { + switch method { + case http.MethodConnect: + return pathItem.Connect + case http.MethodDelete: + return pathItem.Delete + case http.MethodGet: + return pathItem.Get + case http.MethodHead: + return pathItem.Head + case http.MethodOptions: + return pathItem.Options + case http.MethodPatch: + return pathItem.Patch + case http.MethodPost: + return pathItem.Post + case http.MethodPut: + return pathItem.Put + case http.MethodTrace: + return pathItem.Trace + default: + panic(fmt.Errorf("Unsupported HTTP method '%s'", method)) + } +} + +func (pathItem *PathItem) SetOperation(method string, operation *Operation) { + switch method { + case http.MethodConnect: + pathItem.Connect = operation + case http.MethodDelete: + pathItem.Delete = operation + case http.MethodGet: + pathItem.Get = operation + case http.MethodHead: + pathItem.Head = operation + case http.MethodOptions: + pathItem.Options = operation + case http.MethodPatch: + pathItem.Patch = operation + case http.MethodPost: + pathItem.Post = operation + case http.MethodPut: + pathItem.Put = operation + case http.MethodTrace: + pathItem.Trace = operation + default: + panic(fmt.Errorf("Unsupported HTTP method '%s'", method)) + } +} + +func (pathItem *PathItem) Validate(c context.Context) error { + for _, operation := range pathItem.Operations() { + if err := operation.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go new file mode 100644 index 000000000..0bfeb2c79 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go @@ -0,0 +1,122 @@ +package openapi3 + +import ( + "context" + "fmt" + "strings" +) + +// Paths is specified by OpenAPI/Swagger standard version 3.0. +type Paths map[string]*PathItem + +func (paths Paths) Validate(c context.Context) error { + normalizedPaths := make(map[string]string) + for path, pathItem := range paths { + if path == "" || path[0] != '/' { + return fmt.Errorf("path %q does not start with a forward slash (/)", path) + } + + normalizedPath, pathParamsCount := normalizeTemplatedPath(path) + if oldPath, ok := normalizedPaths[normalizedPath]; ok { + return fmt.Errorf("conflicting paths %q and %q", path, oldPath) + } + normalizedPaths[path] = path + + var globalCount uint + for _, parameterRef := range pathItem.Parameters { + if parameterRef != nil { + if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { + globalCount++ + } + } + } + for method, operation := range pathItem.Operations() { + var count uint + for _, parameterRef := range operation.Parameters { + if parameterRef != nil { + if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { + count++ + } + } + } + if count+globalCount != pathParamsCount { + return fmt.Errorf("operation %s %s must define exactly all path parameters", method, path) + } + } + + if err := pathItem.Validate(c); err != nil { + return err + } + } + return nil +} + +// Find returns a path that matches the key. +// +// The method ignores differences in template variable names (except possible "*" suffix). +// +// For example: +// +// paths := openapi3.Paths { +// "/person/{personName}": &openapi3.PathItem{}, +// } +// pathItem := path.Find("/person/{name}") +// +// would return the correct path item. +func (paths Paths) Find(key string) *PathItem { + // Try directly access the map + pathItem := paths[key] + if pathItem != nil { + return pathItem + } + + normalizedPath, expected := normalizeTemplatedPath(key) + for path, pathItem := range paths { + pathNormalized, got := normalizeTemplatedPath(path) + if got == expected && pathNormalized == normalizedPath { + return pathItem + } + } + return nil +} + +func normalizeTemplatedPath(path string) (string, uint) { + if strings.IndexByte(path, '{') < 0 { + return path, 0 + } + + var buf strings.Builder + buf.Grow(len(path)) + + var ( + cc rune + count uint + isVariable bool + ) + for i, c := range path { + if isVariable { + if c == '}' { + // End path variables + // First append possible '*' before this character + // The character '}' will be appended + if i > 0 && cc == '*' { + buf.WriteRune(cc) + } + isVariable = false + } else { + // Skip this character + continue + } + } else if c == '{' { + // Begin path variable + // The character '{' will be appended + isVariable = true + count++ + } + + // Append the character + buf.WriteRune(c) + cc = c + } + return buf.String(), count +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go new file mode 100644 index 000000000..9790b4705 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -0,0 +1,199 @@ +package openapi3 + +import ( + "context" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +type CallbackRef struct { + Ref string + Value *Callback +} + +func (value *CallbackRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *CallbackRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *CallbackRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type ExampleRef struct { + Ref string + Value *Example +} + +func (value *ExampleRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *ExampleRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *ExampleRef) Validate(c context.Context) error { + return nil +} + +type HeaderRef struct { + Ref string + Value *Header +} + +func (value *HeaderRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *HeaderRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *HeaderRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type LinkRef struct { + Ref string + Value *Link +} + +func (value *LinkRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *LinkRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *LinkRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type ParameterRef struct { + Ref string + Value *Parameter +} + +func (value *ParameterRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *ParameterRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *ParameterRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type ResponseRef struct { + Ref string + Value *Response +} + +func (value *ResponseRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *ResponseRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *ResponseRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type RequestBodyRef struct { + Ref string + Value *RequestBody +} + +func (value *RequestBodyRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *RequestBodyRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *RequestBodyRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type SchemaRef struct { + Ref string + Value *Schema +} + +func NewSchemaRef(ref string, value *Schema) *SchemaRef { + return &SchemaRef{ + Ref: ref, + Value: value, + } +} + +func (value *SchemaRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *SchemaRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *SchemaRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} + +type SecuritySchemeRef struct { + Ref string + Value *SecurityScheme +} + +func (value *SecuritySchemeRef) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalRef(value.Ref, value.Value) +} + +func (value *SecuritySchemeRef) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) +} + +func (value *SecuritySchemeRef) Validate(c context.Context) error { + v := value.Value + if v == nil { + return foundUnresolvedRef(value.Ref) + } + return v.Validate(c) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go new file mode 100644 index 000000000..6e8c8fc72 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -0,0 +1,69 @@ +package openapi3 + +import ( + "context" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// RequestBody is specified by OpenAPI/Swagger 3.0 standard. +type RequestBody struct { + ExtensionProps + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` +} + +func NewRequestBody() *RequestBody { + return &RequestBody{} +} + +func (requestBody *RequestBody) WithDescription(value string) *RequestBody { + requestBody.Description = value + return requestBody +} + +func (requestBody *RequestBody) WithRequired(value bool) *RequestBody { + requestBody.Required = value + return requestBody +} + +func (requestBody *RequestBody) WithContent(content Content) *RequestBody { + requestBody.Content = content + return requestBody +} + +func (requestBody *RequestBody) WithJSONSchemaRef(value *SchemaRef) *RequestBody { + requestBody.Content = NewContentWithJSONSchemaRef(value) + return requestBody +} + +func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody { + requestBody.Content = NewContentWithJSONSchema(value) + return requestBody +} + +func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType { + m := requestBody.Content + if m == nil { + return nil + } + return m[mediaType] +} + +func (requestBody *RequestBody) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(requestBody) +} + +func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, requestBody) +} + +func (requestBody *RequestBody) Validate(c context.Context) error { + if v := requestBody.Content; v != nil { + if err := v.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/vendor/github.com/getkin/kin-openapi/openapi3/response.go new file mode 100644 index 000000000..a89b28e2f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -0,0 +1,92 @@ +package openapi3 + +import ( + "context" + "errors" + "strconv" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// Responses is specified by OpenAPI/Swagger 3.0 standard. +type Responses map[string]*ResponseRef + +func NewResponses() Responses { + r := make(Responses) + r["default"] = &ResponseRef{Value: NewResponse().WithDescription("")} + return r +} + +func (responses Responses) Default() *ResponseRef { + return responses["default"] +} + +func (responses Responses) Get(status int) *ResponseRef { + return responses[strconv.FormatInt(int64(status), 10)] +} + +func (responses Responses) Validate(c context.Context) error { + if len(responses) == 0 { + return errors.New("the responses object MUST contain at least one response code") + } + for _, v := range responses { + if err := v.Validate(c); err != nil { + return err + } + } + return nil +} + +// Response is specified by OpenAPI/Swagger 3.0 standard. +type Response struct { + ExtensionProps + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + Headers map[string]*HeaderRef `json:"headers,omitempty" yaml:"headers,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` + Links map[string]*LinkRef `json:"links,omitempty" yaml:"links,omitempty"` +} + +func NewResponse() *Response { + return &Response{} +} + +func (response *Response) WithDescription(value string) *Response { + response.Description = &value + return response +} + +func (response *Response) WithContent(content Content) *Response { + response.Content = content + return response +} + +func (response *Response) WithJSONSchema(schema *Schema) *Response { + response.Content = NewContentWithJSONSchema(schema) + return response +} + +func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response { + response.Content = NewContentWithJSONSchemaRef(schema) + return response +} + +func (response *Response) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(response) +} + +func (response *Response) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, response) +} + +func (response *Response) Validate(c context.Context) error { + if response.Description == nil { + return errors.New("a short description of the response is required") + } + + if content := response.Content; content != nil { + if err := content.Validate(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go new file mode 100644 index 000000000..427c376a4 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -0,0 +1,1235 @@ +package openapi3 + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "regexp" + "strconv" + "unicode/utf16" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +var ( + // SchemaErrorDetailsDisabled disables printing of details about schema errors. + SchemaErrorDetailsDisabled = false + + //SchemaFormatValidationDisabled disables validation of schema type formats. + SchemaFormatValidationDisabled = false + + errSchema = errors.New("Input does not match the schema") + + ErrSchemaInputNaN = errors.New("NaN is not allowed") + ErrSchemaInputInf = errors.New("Inf is not allowed") +) + +// Float64Ptr is a helper for defining OpenAPI schemas. +func Float64Ptr(value float64) *float64 { + return &value +} + +// BoolPtr is a helper for defining OpenAPI schemas. +func BoolPtr(value bool) *bool { + return &value +} + +// Int64Ptr is a helper for defining OpenAPI schemas. +func Int64Ptr(value int64) *int64 { + return &value +} + +// Uint64Ptr is a helper for defining OpenAPI schemas. +func Uint64Ptr(value uint64) *uint64 { + return &value +} + +// Schema is specified by OpenAPI/Swagger 3.0 standard. +type Schema struct { + ExtensionProps + + OneOf []*SchemaRef `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` + AnyOf []*SchemaRef `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` + AllOf []*SchemaRef `json:"allOf,omitempty" yaml:"allOf,omitempty"` + Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` + Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` + Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + // Object-related, here for struct compactness + AdditionalPropertiesAllowed *bool `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"` + // Array-related, here for struct compactness + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + // Number-related, here for struct compactness + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + // Properties + Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + XML interface{} `json:"xml,omitempty" yaml:"xml,omitempty"` + + // Number + Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + + // String + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + compiledPattern *compiledPattern + + // Array + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` + + // Object + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Properties map[string]*SchemaRef `json:"properties,omitempty" yaml:"properties,omitempty"` + MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + AdditionalProperties *SchemaRef `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"` + Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` +} + +func NewSchema() *Schema { + return &Schema{} +} + +func (schema *Schema) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(schema) +} + +func (schema *Schema) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, schema) +} + +func (schema *Schema) NewRef() *SchemaRef { + return &SchemaRef{ + Value: schema, + } +} + +func NewOneOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + OneOf: refs, + } +} + +func NewAnyOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + AnyOf: refs, + } +} + +func NewAllOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + AllOf: refs, + } +} + +func NewBoolSchema() *Schema { + return &Schema{ + Type: "boolean", + } +} + +func NewFloat64Schema() *Schema { + return &Schema{ + Type: "number", + } +} + +func NewIntegerSchema() *Schema { + return &Schema{ + Type: "integer", + } +} + +func NewInt32Schema() *Schema { + return &Schema{ + Type: "integer", + Format: "int32", + } +} + +func NewInt64Schema() *Schema { + return &Schema{ + Type: "integer", + Format: "int64", + } +} + +func NewStringSchema() *Schema { + return &Schema{ + Type: "string", + } +} + +func NewDateTimeSchema() *Schema { + return &Schema{ + Type: "string", + Format: "date-time", + } +} + +func NewUUIDSchema() *Schema { + return &Schema{ + Type: "string", + Format: "uuid", + } +} + +func NewBytesSchema() *Schema { + return &Schema{ + Type: "string", + Format: "byte", + } +} + +func NewArraySchema() *Schema { + return &Schema{ + Type: "array", + } +} + +func NewObjectSchema() *Schema { + return &Schema{ + Type: "object", + Properties: make(map[string]*SchemaRef), + } +} + +type compiledPattern struct { + Regexp *regexp.Regexp + ErrReason string +} + +func (schema *Schema) WithNullable() *Schema { + schema.Nullable = true + return schema +} + +func (schema *Schema) WithMin(value float64) *Schema { + schema.Min = &value + return schema +} + +func (schema *Schema) WithMax(value float64) *Schema { + schema.Max = &value + return schema +} +func (schema *Schema) WithExclusiveMin(value bool) *Schema { + schema.ExclusiveMin = value + return schema +} + +func (schema *Schema) WithExclusiveMax(value bool) *Schema { + schema.ExclusiveMax = value + return schema +} + +func (schema *Schema) WithEnum(values ...interface{}) *Schema { + schema.Enum = values + return schema +} + +func (schema *Schema) WithDefault(defaultValue interface{}) *Schema { + schema.Default = defaultValue + return schema +} + +func (schema *Schema) WithFormat(value string) *Schema { + schema.Format = value + return schema +} + +func (schema *Schema) WithLength(i int64) *Schema { + n := uint64(i) + schema.MinLength = n + schema.MaxLength = &n + return schema +} + +func (schema *Schema) WithMinLength(i int64) *Schema { + n := uint64(i) + schema.MinLength = n + return schema +} + +func (schema *Schema) WithMaxLength(i int64) *Schema { + n := uint64(i) + schema.MaxLength = &n + return schema +} + +func (schema *Schema) WithLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + v := (n*8 + 5) / 6 + schema.MinLength = v + schema.MaxLength = &v + return schema +} + +func (schema *Schema) WithMinLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + schema.MinLength = (n*8 + 5) / 6 + return schema +} + +func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + schema.MinLength = (n*8 + 5) / 6 + return schema +} + +func (schema *Schema) WithPattern(pattern string) *Schema { + schema.Pattern = pattern + return schema +} + +func (schema *Schema) WithItems(value *Schema) *Schema { + schema.Items = &SchemaRef{ + Value: value, + } + return schema +} + +func (schema *Schema) WithMinItems(i int64) *Schema { + n := uint64(i) + schema.MinItems = n + return schema +} + +func (schema *Schema) WithMaxItems(i int64) *Schema { + n := uint64(i) + schema.MaxItems = &n + return schema +} + +func (schema *Schema) WithUniqueItems(unique bool) *Schema { + schema.UniqueItems = unique + return schema +} + +func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema { + return schema.WithPropertyRef(name, &SchemaRef{ + Value: propertySchema, + }) +} + +func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { + properties := schema.Properties + if properties == nil { + properties = make(map[string]*SchemaRef) + schema.Properties = properties + } + properties[name] = ref + return schema +} + +func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema { + result := make(map[string]*SchemaRef, len(properties)) + for k, v := range properties { + result[k] = &SchemaRef{ + Value: v, + } + } + schema.Properties = result + return schema +} + +func (schema *Schema) WithMinProperties(i int64) *Schema { + n := uint64(i) + schema.MinProps = n + return schema +} + +func (schema *Schema) WithMaxProperties(i int64) *Schema { + n := uint64(i) + schema.MaxProps = &n + return schema +} + +func (schema *Schema) WithAnyAdditionalProperties() *Schema { + schema.AdditionalProperties = nil + t := true + schema.AdditionalPropertiesAllowed = &t + return schema +} + +func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema { + if v == nil { + schema.AdditionalProperties = nil + } else { + schema.AdditionalProperties = &SchemaRef{ + Value: v, + } + } + return schema +} + +func (schema *Schema) IsEmpty() bool { + if schema.Type != "" || schema.Format != "" || len(schema.Enum) != 0 || + schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax || + !schema.Nullable || + schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil || + schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" || + schema.MinItems != 0 || schema.MaxItems != nil || + len(schema.Required) != 0 || + schema.MinProps != 0 || schema.MaxProps != nil { + return false + } + if n := schema.Not; n != nil && !n.Value.IsEmpty() { + return false + } + if ap := schema.AdditionalProperties; ap != nil && !ap.Value.IsEmpty() { + return false + } + if apa := schema.AdditionalPropertiesAllowed; apa != nil && !*apa { + return false + } + if items := schema.Items; items != nil && !items.Value.IsEmpty() { + return false + } + for _, s := range schema.Properties { + if !s.Value.IsEmpty() { + return false + } + } + for _, s := range schema.OneOf { + if !s.Value.IsEmpty() { + return false + } + } + for _, s := range schema.AnyOf { + if !s.Value.IsEmpty() { + return false + } + } + for _, s := range schema.AllOf { + if !s.Value.IsEmpty() { + return false + } + } + return true +} + +func (schema *Schema) Validate(c context.Context) error { + return schema.validate(c, []*Schema{}) +} + +func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { + for _, existing := range stack { + if existing == schema { + return + } + } + stack = append(stack, schema) + + for _, item := range schema.OneOf { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref) + } + if err = v.validate(c, stack); err == nil { + return + } + } + + for _, item := range schema.AnyOf { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref) + } + if err = v.validate(c, stack); err != nil { + return + } + } + + for _, item := range schema.AllOf { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref) + } + if err = v.validate(c, stack); err != nil { + return + } + } + + if ref := schema.Not; ref != nil { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err = v.validate(c, stack); err != nil { + return + } + } + + schemaType := schema.Type + switch schemaType { + case "": + case "boolean": + case "number": + if format := schema.Format; len(format) > 0 { + switch format { + case "float", "double": + default: + if !SchemaFormatValidationDisabled { + return unsupportedFormat(format) + } + } + } + case "integer": + if format := schema.Format; len(format) > 0 { + switch format { + case "int32", "int64": + default: + if !SchemaFormatValidationDisabled { + return unsupportedFormat(format) + } + } + } + case "string": + if format := schema.Format; len(format) > 0 { + switch format { + // Supported by OpenAPIv3.0.1: + case "byte", "binary", "date", "date-time", "password": + // In JSON Draft-07 (not validated yet though): + case "regex": + case "time", "email", "idn-email": + case "hostname", "idn-hostname", "ipv4", "ipv6": + case "uri", "uri-reference", "iri", "iri-reference", "uri-template": + case "json-pointer", "relative-json-pointer": + default: + // Try to check for custom defined formats + if _, ok := SchemaStringFormats[format]; !ok && !SchemaFormatValidationDisabled { + return unsupportedFormat(format) + } + } + } + case "array": + if schema.Items == nil { + return errors.New("When schema type is 'array', schema 'items' must be non-null") + } + case "object": + default: + return fmt.Errorf("Unsupported 'type' value '%s'", schemaType) + } + + if ref := schema.Items; ref != nil { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err = v.validate(c, stack); err != nil { + return + } + } + + for _, ref := range schema.Properties { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err = v.validate(c, stack); err != nil { + return + } + } + + if ref := schema.AdditionalProperties; ref != nil { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err = v.validate(c, stack); err != nil { + return + } + } + + return +} + +func (schema *Schema) IsMatching(value interface{}) bool { + return schema.visitJSON(value, true) == nil +} + +func (schema *Schema) IsMatchingJSONBoolean(value bool) bool { + return schema.visitJSON(value, true) == nil +} + +func (schema *Schema) IsMatchingJSONNumber(value float64) bool { + return schema.visitJSON(value, true) == nil +} + +func (schema *Schema) IsMatchingJSONString(value string) bool { + return schema.visitJSON(value, true) == nil +} + +func (schema *Schema) IsMatchingJSONArray(value []interface{}) bool { + return schema.visitJSON(value, true) == nil +} + +func (schema *Schema) IsMatchingJSONObject(value map[string]interface{}) bool { + return schema.visitJSON(value, true) == nil +} + +func (schema *Schema) VisitJSON(value interface{}) error { + return schema.visitJSON(value, false) +} + +func (schema *Schema) visitJSON(value interface{}, fast bool) (err error) { + switch value := value.(type) { + case nil: + return schema.visitJSONNull(fast) + case float64: + if math.IsNaN(value) { + return ErrSchemaInputNaN + } + if math.IsInf(value, 0) { + return ErrSchemaInputInf + } + } + + if schema.IsEmpty() { + return + } + if err = schema.visitSetOperations(value, fast); err != nil { + return + } + + switch value := value.(type) { + case nil: + return schema.visitJSONNull(fast) + case bool: + return schema.visitJSONBoolean(value, fast) + case float64: + return schema.visitJSONNumber(value, fast) + case string: + return schema.visitJSONString(value, fast) + case []interface{}: + return schema.visitJSONArray(value, fast) + case map[string]interface{}: + return schema.visitJSONObject(value, fast) + default: + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: fmt.Sprintf("Not a JSON value: %T", value), + } + } +} + +func (schema *Schema) visitSetOperations(value interface{}, fast bool) (err error) { + if enum := schema.Enum; len(enum) != 0 { + for _, v := range enum { + if value == v { + return + } + } + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "enum", + Reason: "JSON value is not one of the allowed values", + } + } + + if ref := schema.Not; ref != nil { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err := v.visitJSON(value, true); err == nil { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "not", + } + } + } + + if v := schema.OneOf; len(v) > 0 { + ok := 0 + for _, item := range v { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref) + } + if err := v.visitJSON(value, true); err == nil { + ok++ + } + } + if ok != 1 { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "oneOf", + } + } + } + + if v := schema.AnyOf; len(v) > 0 { + ok := false + for _, item := range v { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref) + } + if err := v.visitJSON(value, true); err == nil { + ok = true + break + } + } + if !ok { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "anyOf", + } + } + } + + for _, item := range schema.AllOf { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref) + } + if err := v.visitJSON(value, false); err != nil { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "allOf", + Origin: err, + } + } + } + return +} + +func (schema *Schema) visitJSONNull(fast bool) (err error) { + if schema.Nullable { + return + } + if fast { + return errSchema + } + return &SchemaError{ + Value: nil, + Schema: schema, + SchemaField: "nullable", + Reason: "Value is not nullable", + } +} + +func (schema *Schema) VisitJSONBoolean(value bool) error { + return schema.visitJSONBoolean(value, false) +} + +func (schema *Schema) visitJSONBoolean(value bool, fast bool) (err error) { + if schemaType := schema.Type; schemaType != "" && schemaType != "boolean" { + return schema.expectedType("boolean", fast) + } + return +} + +func (schema *Schema) VisitJSONNumber(value float64) error { + return schema.visitJSONNumber(value, false) +} + +func (schema *Schema) visitJSONNumber(value float64, fast bool) (err error) { + schemaType := schema.Type + if schemaType == "integer" { + if bigFloat := big.NewFloat(value); !bigFloat.IsInt() { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: "Value must be an integer", + } + } + } else if schemaType != "" && schemaType != "number" { + return schema.expectedType("number, integer", fast) + } + + // "exclusiveMinimum" + if v := schema.ExclusiveMin; v && !(*schema.Min < value) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMinimum", + Reason: fmt.Sprintf("Number must be more than %g", *schema.Min), + } + } + + // "exclusiveMaximum" + if v := schema.ExclusiveMax; v && !(*schema.Max > value) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMaximum", + Reason: fmt.Sprintf("Number must be less than %g", *schema.Max), + } + } + + // "minimum" + if v := schema.Min; v != nil && !(*v <= value) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minimum", + Reason: fmt.Sprintf("Number must be at least %g", *v), + } + } + + // "maximum" + if v := schema.Max; v != nil && !(*v >= value) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maximum", + Reason: fmt.Sprintf("Number must be most %g", *v), + } + } + + // "multipleOf" + if v := schema.MultipleOf; v != nil { + // "A numeric instance is valid only if division by this keyword's + // value results in an integer." + if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "multipleOf", + } + } + } + return +} + +func (schema *Schema) VisitJSONString(value string) error { + return schema.visitJSONString(value, false) +} + +func (schema *Schema) visitJSONString(value string, fast bool) (err error) { + if schemaType := schema.Type; schemaType != "" && schemaType != "string" { + return schema.expectedType("string", fast) + } + + // "minLength" and "maxLength" + minLength := schema.MinLength + maxLength := schema.MaxLength + if minLength != 0 || maxLength != nil { + // JSON schema string lengths are UTF-16, not UTF-8! + length := int64(0) + for _, r := range value { + if utf16.IsSurrogate(r) { + length += 2 + } else { + length++ + } + } + if minLength != 0 && length < int64(minLength) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minLength", + Reason: fmt.Sprintf("Minimum string length is %d", minLength), + } + } + if maxLength != nil && length > int64(*maxLength) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxLength", + Reason: fmt.Sprintf("Maximum string length is %d", *maxLength), + } + } + } + + // "format" and "pattern" + cp := schema.compiledPattern + if cp == nil { + pattern := schema.Pattern + if v := schema.Pattern; len(v) > 0 { + // Pattern + re, err := regexp.Compile(v) + if err != nil { + return fmt.Errorf("Error while compiling regular expression '%s': %v", pattern, err) + } + cp = &compiledPattern{ + Regexp: re, + ErrReason: "JSON string doesn't match the regular expression '" + v + "'", + } + schema.compiledPattern = cp + } else if v := schema.Format; len(v) > 0 { + // No pattern, but does have a format + re := SchemaStringFormats[v] + if re != nil { + cp = &compiledPattern{ + Regexp: re, + ErrReason: "JSON string doesn't match the format '" + v + " (regular expression `" + re.String() + "`)'", + } + schema.compiledPattern = cp + } + } + } + if cp != nil { + if !cp.Regexp.MatchString(value) { + field := "format" + if schema.Pattern != "" { + field = "pattern" + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: field, + Reason: cp.ErrReason, + } + } + } + return +} + +func (schema *Schema) VisitJSONArray(value []interface{}) error { + return schema.visitJSONArray(value, false) +} + +func (schema *Schema) visitJSONArray(value []interface{}, fast bool) (err error) { + if schemaType := schema.Type; schemaType != "" && schemaType != "array" { + return schema.expectedType("array", fast) + } + + lenValue := int64(len(value)) + + // "minItems" + if v := schema.MinItems; v != 0 && lenValue < int64(v) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minItems", + Reason: fmt.Sprintf("Minimum number of items is %d", v), + } + } + + // "maxItems" + if v := schema.MaxItems; v != nil && lenValue > int64(*v) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxItems", + Reason: fmt.Sprintf("Maximum number of items is %d", *v), + } + } + + // "uniqueItems" + if v := schema.UniqueItems; v && !sliceUniqueItemsChecker(value) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "uniqueItems", + Reason: fmt.Sprintf("Duplicate items found"), + } + } + + // "items" + if itemSchemaRef := schema.Items; itemSchemaRef != nil { + itemSchema := itemSchemaRef.Value + if itemSchema == nil { + return foundUnresolvedRef(itemSchemaRef.Ref) + } + for i, item := range value { + if err := itemSchema.VisitJSON(item); err != nil { + return markSchemaErrorIndex(err, i) + } + } + } + return +} + +func (schema *Schema) VisitJSONObject(value map[string]interface{}) error { + return schema.visitJSONObject(value, false) +} + +func (schema *Schema) visitJSONObject(value map[string]interface{}, fast bool) (err error) { + if schemaType := schema.Type; schemaType != "" && schemaType != "object" { + return schema.expectedType("object", fast) + } + + // "properties" + properties := schema.Properties + lenValue := int64(len(value)) + + // "minProperties" + if v := schema.MinProps; v != 0 && lenValue < int64(v) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minProperties", + Reason: fmt.Sprintf("There must be at least %d properties", v), + } + } + + // "maxProperties" + if v := schema.MaxProps; v != nil && lenValue > int64(*v) { + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxProperties", + Reason: fmt.Sprintf("There must be at most %d properties", *v), + } + } + + // "additionalProperties" + var additionalProperties *Schema + if ref := schema.AdditionalProperties; ref != nil { + additionalProperties = ref.Value + } + for k, v := range value { + if properties != nil { + propertyRef := properties[k] + if propertyRef != nil { + p := propertyRef.Value + if p == nil { + return foundUnresolvedRef(propertyRef.Ref) + } + if err := p.VisitJSON(v); err != nil { + if fast { + return errSchema + } + return markSchemaErrorKey(err, k) + } + continue + } + } + allowed := schema.AdditionalPropertiesAllowed + if additionalProperties != nil || allowed == nil || (allowed != nil && *allowed) { + if additionalProperties != nil { + if err := additionalProperties.VisitJSON(v); err != nil { + if fast { + return errSchema + } + return markSchemaErrorKey(err, k) + } + } + continue + } + if fast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "properties", + Reason: fmt.Sprintf("Property '%s' is unsupported", k), + } + } + for _, k := range schema.Required { + if _, ok := value[k]; !ok { + if fast { + return errSchema + } + return markSchemaErrorKey(&SchemaError{ + Value: value, + Schema: schema, + SchemaField: "required", + Reason: fmt.Sprintf("Property '%s' is missing", k), + }, k) + } + } + return +} + +func (schema *Schema) expectedType(typ string, fast bool) error { + if fast { + return errSchema + } + return &SchemaError{ + Value: typ, + Schema: schema, + SchemaField: "type", + Reason: "Field must be set to " + schema.Type + " or not be present", + } +} + +type SchemaError struct { + Value interface{} + reversePath []string + Schema *Schema + SchemaField string + Reason string + Origin error +} + +func markSchemaErrorKey(err error, key string) error { + if v, ok := err.(*SchemaError); ok { + v.reversePath = append(v.reversePath, key) + return v + } + return err +} + +func markSchemaErrorIndex(err error, index int) error { + if v, ok := err.(*SchemaError); ok { + v.reversePath = append(v.reversePath, strconv.FormatInt(int64(index), 10)) + return v + } + return err +} + +func (err *SchemaError) JSONPointer() []string { + reversePath := err.reversePath + path := append([]string(nil), reversePath...) + for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 { + path[left], path[right] = path[right], path[left] + } + return path +} + +func (err *SchemaError) Error() string { + if err.Origin != nil { + return err.Origin.Error() + } + + buf := bytes.NewBuffer(make([]byte, 0, 256)) + if len(err.reversePath) > 0 { + buf.WriteString(`Error at "`) + reversePath := err.reversePath + for i := len(reversePath) - 1; i >= 0; i-- { + buf.WriteByte('/') + buf.WriteString(reversePath[i]) + } + buf.WriteString(`":`) + } + reason := err.Reason + if reason == "" { + buf.WriteString(`Doesn't match schema "`) + buf.WriteString(err.SchemaField) + buf.WriteString(`"`) + } else { + buf.WriteString(reason) + } + if !SchemaErrorDetailsDisabled { + buf.WriteString("\nSchema:\n ") + encoder := json.NewEncoder(buf) + encoder.SetIndent(" ", " ") + if err := encoder.Encode(err.Schema); err != nil { + panic(err) + } + buf.WriteString("\nValue:\n ") + if err := encoder.Encode(err.Value); err != nil { + panic(err) + } + } + return buf.String() +} + +func isSliceOfUniqueItems(xs []interface{}) bool { + s := len(xs) + m := make(map[string]struct{}, s) + for _, x := range xs { + // The input slice is coverted from a JSON string, there shall + // have no error when covert it back. + key, _ := json.Marshal(&x) + m[string(key)] = struct{}{} + } + return s == len(m) +} + +// SliceUniqueItemsChecker is an function used to check if an given slice +// have unique items. +type SliceUniqueItemsChecker func(items []interface{}) bool + +// By default using predefined func isSliceOfUniqueItems which make use of +// json.Marshal to generate a key for map used to check if a given slice +// have unique items. +var sliceUniqueItemsChecker SliceUniqueItemsChecker = isSliceOfUniqueItems + +// RegisterArrayUniqueItemsChecker is used to register a customized function +// used to check if JSON array have unique items. +func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) { + sliceUniqueItemsChecker = fn +} + +func unsupportedFormat(format string) error { + return fmt.Errorf("Unsupported 'format' value '%s'", format) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go new file mode 100644 index 000000000..746e40882 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -0,0 +1,38 @@ +package openapi3 + +import ( + "fmt" + "regexp" +) + +const ( + // FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122 + FormatOfStringForUUIDOfRFC4122 = `^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$` +) + +var SchemaStringFormats = make(map[string]*regexp.Regexp, 8) + +func DefineStringFormat(name string, pattern string) { + re, err := regexp.Compile(pattern) + if err != nil { + err := fmt.Errorf("Format '%v' has invalid pattern '%v': %v", name, pattern, err) + panic(err) + } + SchemaStringFormats[name] = re +} + +func init() { + // This pattern catches only some suspiciously wrong-looking email addresses. + // Use DefineStringFormat(...) if you need something stricter. + DefineStringFormat("email", `^[^@]+@[^@<>",\s]+$`) + + // Base64 + // The pattern supports base64 and b./ase64url. Padding ('=') is supported. + DefineStringFormat("byte", `(^$|^[a-zA-Z0-9+/\-_]*=*$)`) + + // date + DefineStringFormat("date", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`) + + // date-time + DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go new file mode 100644 index 000000000..1d2c745f7 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -0,0 +1,43 @@ +package openapi3 + +import ( + "context" +) + +type SecurityRequirements []SecurityRequirement + +func NewSecurityRequirements() *SecurityRequirements { + return &SecurityRequirements{} +} + +func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements { + *srs = append(*srs, securityRequirement) + return srs +} + +func (srs SecurityRequirements) Validate(c context.Context) error { + for _, item := range srs { + if err := item.Validate(c); err != nil { + return err + } + } + return nil +} + +type SecurityRequirement map[string][]string + +func NewSecurityRequirement() SecurityRequirement { + return make(SecurityRequirement) +} + +func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement { + if len(scopes) == 0 { + scopes = []string{} // Forces the variable to be encoded as an array instead of null + } + security[provider] = scopes + return security +} + +func (security SecurityRequirement) Validate(c context.Context) error { + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go new file mode 100644 index 000000000..0e991fb67 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -0,0 +1,214 @@ +package openapi3 + +import ( + "context" + "errors" + "fmt" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +type SecurityScheme struct { + ExtensionProps + + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"` + BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"` + Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"` +} + +func NewSecurityScheme() *SecurityScheme { + return &SecurityScheme{} +} + +func NewCSRFSecurityScheme() *SecurityScheme { + return &SecurityScheme{ + Type: "apiKey", + In: "header", + Name: "X-XSRF-TOKEN", + } +} + +func NewJWTSecurityScheme() *SecurityScheme { + return &SecurityScheme{ + Type: "http", + Scheme: "bearer", + BearerFormat: "JWT", + } +} + +func (ss *SecurityScheme) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(ss) +} + +func (ss *SecurityScheme) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, ss) +} + +func (ss *SecurityScheme) WithType(value string) *SecurityScheme { + ss.Type = value + return ss +} + +func (ss *SecurityScheme) WithDescription(value string) *SecurityScheme { + ss.Description = value + return ss +} + +func (ss *SecurityScheme) WithName(value string) *SecurityScheme { + ss.Name = value + return ss +} + +func (ss *SecurityScheme) WithIn(value string) *SecurityScheme { + ss.In = value + return ss +} + +func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme { + ss.Scheme = value + return ss +} + +func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme { + ss.BearerFormat = value + return ss +} + +func (ss *SecurityScheme) Validate(c context.Context) error { + hasIn := false + hasBearerFormat := false + hasFlow := false + switch ss.Type { + case "apiKey": + hasIn = true + case "http": + scheme := ss.Scheme + switch scheme { + case "bearer": + hasBearerFormat = true + case "basic": + default: + return fmt.Errorf("Security scheme of type 'http' has invalid 'scheme' value '%s'", scheme) + } + case "oauth2": + hasFlow = true + case "openIdConnect": + return fmt.Errorf("Support for security schemes with type '%v' has not been implemented", ss.Type) + default: + return fmt.Errorf("Security scheme 'type' can't be '%v'", ss.Type) + } + + // Validate "in" and "name" + if hasIn { + switch ss.In { + case "query", "header", "cookie": + default: + return fmt.Errorf("Security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not '%s'", ss.In) + } + if ss.Name == "" { + return errors.New("Security scheme of type 'apiKey' should have 'name'") + } + } else if len(ss.In) > 0 { + return fmt.Errorf("Security scheme of type '%s' can't have 'in'", ss.Type) + } else if len(ss.Name) > 0 { + return errors.New("Security scheme of type 'apiKey' can't have 'name'") + } + + // Validate "format" + // "bearerFormat" is an arbitrary string so we only check if the scheme supports it + if !hasBearerFormat && len(ss.BearerFormat) > 0 { + return fmt.Errorf("Security scheme of type '%v' can't have 'bearerFormat'", ss.Type) + } + + // Validate "flow" + if hasFlow { + flow := ss.Flows + if flow == nil { + return fmt.Errorf("Security scheme of type '%v' should have 'flows'", ss.Type) + } + if err := flow.Validate(c); err != nil { + return fmt.Errorf("Security scheme 'flow' is invalid: %v", err) + } + } else if ss.Flows != nil { + return fmt.Errorf("Security scheme of type '%s' can't have 'flows'", ss.Type) + } + return nil +} + +type OAuthFlows struct { + ExtensionProps + Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"` + Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` + ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"` + AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"` +} + +type oAuthFlowType int + +const ( + oAuthFlowTypeImplicit oAuthFlowType = iota + oAuthFlowTypePassword + oAuthFlowTypeClientCredentials + oAuthFlowAuthorizationCode +) + +func (flows *OAuthFlows) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(flows) +} + +func (flows *OAuthFlows) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, flows) +} + +func (flows *OAuthFlows) Validate(c context.Context) error { + if v := flows.Implicit; v != nil { + return v.Validate(c, oAuthFlowTypeImplicit) + } + if v := flows.Password; v != nil { + return v.Validate(c, oAuthFlowTypePassword) + } + if v := flows.ClientCredentials; v != nil { + return v.Validate(c, oAuthFlowTypeClientCredentials) + } + if v := flows.AuthorizationCode; v != nil { + return v.Validate(c, oAuthFlowAuthorizationCode) + } + return errors.New("No OAuth flow is defined") +} + +type OAuthFlow struct { + ExtensionProps + AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` + RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` + Scopes map[string]string `json:"scopes" yaml:"scopes"` +} + +func (flow *OAuthFlow) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(flow) +} + +func (flow *OAuthFlow) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, flow) +} + +func (flow *OAuthFlow) Validate(c context.Context, typ oAuthFlowType) error { + if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit { + if v := flow.AuthorizationURL; v == "" { + return errors.New("An OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '") + } + } + if typ != oAuthFlowTypeImplicit { + if v := flow.TokenURL; v == "" { + return errors.New("An OAuth flow is missing 'tokenUrl in not implicit'") + } + } + if v := flow.Scopes; v == nil { + return errors.New("An OAuth flow is missing 'scopes'") + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go b/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go new file mode 100644 index 000000000..2ec8bd2db --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go @@ -0,0 +1,17 @@ +package openapi3 + +const ( + SerializationSimple = "simple" + SerializationLabel = "label" + SerializationMatrix = "matrix" + SerializationForm = "form" + SerializationSpaceDelimited = "spaceDelimited" + SerializationPipeDelimited = "pipeDelimited" + SerializationDeepObject = "deepObject" +) + +// SerializationMethod describes a serialization method of HTTP request's parameters and body. +type SerializationMethod struct { + Style string + Explode bool +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/vendor/github.com/getkin/kin-openapi/openapi3/server.go new file mode 100644 index 000000000..2594d2b30 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -0,0 +1,148 @@ +package openapi3 + +import ( + "context" + "errors" + "math" + "net/url" + "strings" +) + +// Servers is specified by OpenAPI/Swagger standard version 3.0. +type Servers []*Server + +func (servers Servers) Validate(c context.Context) error { + for _, v := range servers { + if err := v.Validate(c); err != nil { + return err + } + } + return nil +} + +func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) { + rawURL := parsedURL.String() + if i := strings.IndexByte(rawURL, '?'); i >= 0 { + rawURL = rawURL[:i] + } + for _, server := range servers { + pathParams, remaining, ok := server.MatchRawURL(rawURL) + if ok { + return server, pathParams, remaining + } + } + return nil, nil, "" +} + +// Server is specified by OpenAPI/Swagger standard version 3.0. +type Server struct { + URL string `json:"url" yaml:"url"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"` +} + +func (server Server) ParameterNames() ([]string, error) { + pattern := server.URL + var params []string + for len(pattern) > 0 { + i := strings.IndexByte(pattern, '{') + if i < 0 { + break + } + pattern = pattern[i+1:] + i = strings.IndexByte(pattern, '}') + if i < 0 { + return nil, errors.New("Missing '}'") + } + params = append(params, strings.TrimSpace(pattern[:i])) + pattern = pattern[i+1:] + } + return params, nil +} + +func (server Server) MatchRawURL(input string) ([]string, string, bool) { + pattern := server.URL + var params []string + for len(pattern) > 0 { + c := pattern[0] + if len(pattern) == 1 && c == '/' { + break + } + if c == '{' { + // Find end of pattern + i := strings.IndexByte(pattern, '}') + if i < 0 { + return nil, "", false + } + pattern = pattern[i+1:] + + // Find next matching pattern character or next '/' whichever comes first + np := -1 + if len(pattern) > 0 { + np = strings.IndexByte(input, pattern[0]) + } + ns := strings.IndexByte(input, '/') + + if np < 0 { + i = ns + } else if ns < 0 { + i = np + } else { + i = int(math.Min(float64(np), float64(ns))) + } + if i < 0 { + i = len(input) + } + params = append(params, input[:i]) + input = input[i:] + continue + } + if len(input) == 0 || input[0] != c { + return nil, "", false + } + pattern = pattern[1:] + input = input[1:] + } + if input == "" { + input = "/" + } + if input[0] != '/' { + return nil, "", false + } + return params, input, true +} + +func (server *Server) Validate(c context.Context) (err error) { + if server.URL == "" { + return errors.New("value of url must be a non-empty JSON string") + } + for _, v := range server.Variables { + if err = v.Validate(c); err != nil { + return + } + } + return +} + +// ServerVariable is specified by OpenAPI/Swagger standard version 3.0. +type ServerVariable struct { + Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` + Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` +} + +func (serverVariable *ServerVariable) Validate(c context.Context) error { + switch serverVariable.Default.(type) { + case float64, string: + default: + return errors.New("value of default must be either JSON number or JSON string") + } + for _, item := range serverVariable.Enum { + switch item.(type) { + case float64, string: + default: + return errors.New("Every variable 'enum' item must be number of string") + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/swagger.go b/vendor/github.com/getkin/kin-openapi/openapi3/swagger.go new file mode 100644 index 000000000..06be8a343 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/swagger.go @@ -0,0 +1,104 @@ +package openapi3 + +import ( + "context" + "errors" + "fmt" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +type Swagger struct { + ExtensionProps + OpenAPI string `json:"openapi" yaml:"openapi"` // Required + Components Components `json:"components,omitempty" yaml:"components,omitempty"` + Info *Info `json:"info" yaml:"info"` // Required + Paths Paths `json:"paths" yaml:"paths"` // Required + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} + +func (swagger *Swagger) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(swagger) +} + +func (swagger *Swagger) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, swagger) +} + +func (swagger *Swagger) AddOperation(path string, method string, operation *Operation) { + paths := swagger.Paths + if paths == nil { + paths = make(Paths) + swagger.Paths = paths + } + pathItem := paths[path] + if pathItem == nil { + pathItem = &PathItem{} + paths[path] = pathItem + } + pathItem.SetOperation(method, operation) +} + +func (swagger *Swagger) AddServer(server *Server) { + swagger.Servers = append(swagger.Servers, server) +} + +func (swagger *Swagger) Validate(c context.Context) error { + if swagger.OpenAPI == "" { + return errors.New("value of openapi must be a non-empty JSON string") + } + + // NOTE: only mention info/components/paths/... key in this func's errors. + + { + wrap := func(e error) error { return fmt.Errorf("invalid components: %v", e) } + if err := swagger.Components.Validate(c); err != nil { + return wrap(err) + } + } + + { + wrap := func(e error) error { return fmt.Errorf("invalid info: %v", e) } + if v := swagger.Info; v != nil { + if err := v.Validate(c); err != nil { + return wrap(err) + } + } else { + return wrap(errors.New("must be a JSON object")) + } + } + + { + wrap := func(e error) error { return fmt.Errorf("invalid paths: %v", e) } + if v := swagger.Paths; v != nil { + if err := v.Validate(c); err != nil { + return wrap(err) + } + } else { + return wrap(errors.New("must be a JSON object")) + } + } + + { + wrap := func(e error) error { return fmt.Errorf("invalid security: %v", e) } + if v := swagger.Security; v != nil { + if err := v.Validate(c); err != nil { + return wrap(err) + } + } + } + + { + wrap := func(e error) error { return fmt.Errorf("invalid servers: %v", e) } + if v := swagger.Servers; v != nil { + if err := v.Validate(c); err != nil { + return wrap(err) + } + } + } + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go new file mode 100644 index 000000000..83767340b --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go @@ -0,0 +1,887 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "reflect" + "strconv" + "strings" + + "github.com/ghodss/yaml" +) + +func foundUnresolvedRef(ref string) error { + return fmt.Errorf("Found unresolved ref: '%s'", ref) +} + +func failedToResolveRefFragment(value string) error { + return fmt.Errorf("Failed to resolve fragment in URI: '%s'", value) +} + +func failedToResolveRefFragmentPart(value string, what string) error { + return fmt.Errorf("Failed to resolve '%s' in fragment in URI: '%s'", what, value) +} + +type SwaggerLoader struct { + IsExternalRefsAllowed bool + Context context.Context + LoadSwaggerFromURIFunc func(loader *SwaggerLoader, url *url.URL) (*Swagger, error) + visited map[interface{}]struct{} + visitedFiles map[string]struct{} +} + +func NewSwaggerLoader() *SwaggerLoader { + return &SwaggerLoader{} +} + +func (swaggerLoader *SwaggerLoader) reset() { + swaggerLoader.visitedFiles = make(map[string]struct{}) +} + +func (swaggerLoader *SwaggerLoader) LoadSwaggerFromURI(location *url.URL) (*Swagger, error) { + swaggerLoader.reset() + return swaggerLoader.loadSwaggerFromURIInternal(location) +} + +func (swaggerLoader *SwaggerLoader) loadSwaggerFromURIInternal(location *url.URL) (*Swagger, error) { + f := swaggerLoader.LoadSwaggerFromURIFunc + if f != nil { + return f(swaggerLoader, location) + } + data, err := readURL(location) + if err != nil { + return nil, err + } + return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, location) +} + +// loadSingleElementFromURI read the data from ref and unmarshal to JSON to the +// passed element. +func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) error { + if !swaggerLoader.IsExternalRefsAllowed { + return fmt.Errorf("encountered non-allowed external reference: '%s'", ref) + } + + parsedURL, err := url.Parse(ref) + if err != nil { + return err + } + + if parsedURL.Fragment != "" { + return errors.New("references to files which contain more than one element definition are not supported") + } + + resolvedPath, err := resolvePath(rootPath, parsedURL) + if err != nil { + return fmt.Errorf("could not resolve path: %v", err) + } + + data, err := readURL(resolvedPath) + if err != nil { + return err + } + if err := yaml.Unmarshal(data, element); err != nil { + return err + } + + return nil +} + +func readURL(location *url.URL) ([]byte, error) { + if location.Scheme != "" && location.Host != "" { + resp, err := http.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 data, nil + } + 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 data, nil +} + +func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger, error) { + swaggerLoader.reset() + return swaggerLoader.loadSwaggerFromFileInternal(path) +} + +func (swaggerLoader *SwaggerLoader) loadSwaggerFromFileInternal(path string) (*Swagger, error) { + f := swaggerLoader.LoadSwaggerFromURIFunc + if f != nil { + return f(swaggerLoader, &url.URL{ + Path: path, + }) + } + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, &url.URL{ + Path: path, + }) +} + +func (swaggerLoader *SwaggerLoader) LoadSwaggerFromData(data []byte) (*Swagger, error) { + swaggerLoader.reset() + return swaggerLoader.loadSwaggerFromDataInternal(data) +} + +func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataInternal(data []byte) (*Swagger, error) { + swagger := &Swagger{} + if err := yaml.Unmarshal(data, swagger); err != nil { + return nil, err + } + return swagger, swaggerLoader.ResolveRefsIn(swagger, nil) +} + +// LoadSwaggerFromDataWithPath takes the OpenApi spec data in bytes and a path where the resolver can find referred +// elements and returns a *Swagger with all resolved data or an error if unable to load data or resolve refs. +func (swaggerLoader *SwaggerLoader) LoadSwaggerFromDataWithPath(data []byte, path *url.URL) (*Swagger, error) { + swaggerLoader.reset() + return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, path) +} + +func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataWithPathInternal(data []byte, path *url.URL) (*Swagger, error) { + swagger := &Swagger{} + if err := yaml.Unmarshal(data, swagger); err != nil { + return nil, err + } + return swagger, swaggerLoader.ResolveRefsIn(swagger, path) +} + +func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.URL) (err error) { + swaggerLoader.visited = make(map[interface{}]struct{}) + if swaggerLoader.visitedFiles == nil { + swaggerLoader.visitedFiles = make(map[string]struct{}) + } + + // Visit all components + components := swagger.Components + for _, component := range components.Headers { + if err = swaggerLoader.resolveHeaderRef(swagger, component, path); err != nil { + return + } + } + for _, component := range components.Parameters { + if err = swaggerLoader.resolveParameterRef(swagger, component, path); err != nil { + return + } + } + for _, component := range components.RequestBodies { + if err = swaggerLoader.resolveRequestBodyRef(swagger, component, path); err != nil { + return + } + } + for _, component := range components.Responses { + if err = swaggerLoader.resolveResponseRef(swagger, component, path); err != nil { + return + } + } + for _, component := range components.Schemas { + if err = swaggerLoader.resolveSchemaRef(swagger, component, path); err != nil { + return + } + } + for _, component := range components.SecuritySchemes { + if err = swaggerLoader.resolveSecuritySchemeRef(swagger, component, path); err != nil { + return + } + } + for _, component := range components.Examples { + if err = swaggerLoader.resolveExampleRef(swagger, component, path); err != nil { + return + } + } + + // Visit all operations + for entrypoint, pathItem := range swagger.Paths { + if pathItem == nil { + continue + } + if err = swaggerLoader.resolvePathItemRef(swagger, entrypoint, pathItem, path); err != nil { + return + } + } + + return +} + +func copyURL(basePath *url.URL) (*url.URL, error) { + return url.Parse(basePath.String()) +} + +func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) { + if basePath == nil { + return relativePath, nil + } + newPath, err := copyURL(basePath) + if err != nil { + return nil, fmt.Errorf("Can't copy path: '%s'", basePath.String()) + } + newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) + return newPath, nil +} + +func resolvePath(basePath *url.URL, componentPath *url.URL) (*url.URL, error) { + if componentPath.Scheme == "" && componentPath.Host == "" { + return join(basePath, componentPath) + } + return componentPath, nil +} + +func isSingleRefElement(ref string) bool { + return !strings.Contains(ref, "#") +} + +func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref string, path *url.URL) ( + cursor interface{}, + componentPath *url.URL, + err error, +) { + if swagger, ref, componentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, path); err != nil { + return nil, nil, err + } + + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, nil, fmt.Errorf("Can't parse reference: '%s': %v", ref, parsedURL) + } + fragment := parsedURL.Fragment + if !strings.HasPrefix(fragment, "/") { + err := fmt.Errorf("expected fragment prefix '#/' in URI '%s'", ref) + return nil, nil, err + } + + cursor = swagger + for _, pathPart := range strings.Split(fragment[1:], "/") { + + pathPart = strings.Replace(pathPart, "~1", "/", -1) + pathPart = strings.Replace(pathPart, "~0", "~", -1) + + if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil { + return nil, nil, fmt.Errorf("Failed to resolve '%s' in fragment in URI: '%s': %v", ref, pathPart, err.Error()) + } + if cursor == nil { + return nil, nil, failedToResolveRefFragmentPart(ref, pathPart) + } + } + + return cursor, componentPath, nil +} + +func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, error) { + switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() { + case reflect.Map: + elementValue := val.MapIndex(reflect.ValueOf(fieldName)) + if !elementValue.IsValid() { + return nil, fmt.Errorf("Map key not found: %v", fieldName) + } + return elementValue.Interface(), nil + + case reflect.Slice: + i, err := strconv.ParseUint(fieldName, 10, 32) + if err != nil { + return nil, err + } + index := int(i) + if index >= val.Len() { + return nil, errors.New("slice index out of bounds") + } + return val.Index(index).Interface(), nil + + case reflect.Struct: + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + tagValue := field.Tag.Get("yaml") + yamlKey := strings.Split(tagValue, ",")[0] + if yamlKey == fieldName { + return val.Field(i).Interface(), nil + } + } + // if cursor if a "ref wrapper" struct (e.g. RequestBodyRef), try digging into its Value field + _, ok := val.Type().FieldByName("Value") + if ok { + return drillIntoSwaggerField(val.FieldByName("Value").Interface(), fieldName) // recurse into .Value + } + // give up + return nil, fmt.Errorf("Struct field not found: %v", fieldName) + + default: + return nil, errors.New("not a map, slice nor struct") + } +} + +func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref string, path *url.URL) (*Swagger, string, *url.URL, error) { + componentPath := path + if !strings.HasPrefix(ref, "#") { + if !swaggerLoader.IsExternalRefsAllowed { + return nil, "", nil, fmt.Errorf("Encountered non-allowed external reference: '%s'", ref) + } + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, "", nil, fmt.Errorf("Can't parse reference: '%s': %v", ref, parsedURL) + } + fragment := parsedURL.Fragment + parsedURL.Fragment = "" + + resolvedPath, err := resolvePath(path, parsedURL) + if err != nil { + return nil, "", nil, fmt.Errorf("Error while resolving path: %v", err) + } + + if swagger, err = swaggerLoader.loadSwaggerFromURIInternal(resolvedPath); err != nil { + return nil, "", nil, fmt.Errorf("Error while resolving reference '%s': %v", ref, err) + } + ref = fmt.Sprintf("#%s", fragment) + componentPath = resolvedPath + } + return swagger, ref, componentPath, nil +} + +func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, path *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/headers/" + if component == nil { + return errors.New("invalid header: value MUST be a JSON object") + } + if ref := component.Ref; len(ref) > 0 { + if isSingleRefElement(ref) { + var header Header + if err := swaggerLoader.loadSingleElementFromURI(ref, path, &header); err != nil { + return err + } + + component.Value = &header + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*HeaderRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveHeaderRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + value := component.Value + if value == nil { + return nil + } + if schema := value.Schema; schema != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, schema, path); err != nil { + return err + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/parameters/" + if component == nil { + return errors.New("invalid parameter: value MUST be a JSON object") + } + ref := component.Ref + if len(ref) > 0 { + if isSingleRefElement(ref) { + var param Parameter + if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil { + return err + } + component.Value = ¶m + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*ParameterRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveParameterRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + value := component.Value + if value == nil { + return nil + } + + refDocumentPath, err := referencedDocumentPath(documentPath, ref) + if err != nil { + return err + } + + if value.Content != nil && value.Schema != nil { + return errors.New("Cannot contain both schema and content in a parameter") + } + for _, contentType := range value.Content { + if schema := contentType.Schema; schema != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil { + return err + } + } + } + if schema := value.Schema; schema != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil { + return err + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, path *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/requestBodies/" + if component == nil { + return errors.New("invalid requestBody: value MUST be a JSON object") + } + if ref := component.Ref; len(ref) > 0 { + if isSingleRefElement(ref) { + var requestBody RequestBody + if err := swaggerLoader.loadSingleElementFromURI(ref, path, &requestBody); err != nil { + return err + } + + component.Value = &requestBody + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*RequestBodyRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err = swaggerLoader.resolveRequestBodyRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + value := component.Value + if value == nil { + return nil + } + for _, contentType := range value.Content { + for name, example := range contentType.Examples { + if err := swaggerLoader.resolveExampleRef(swagger, example, path); err != nil { + return err + } + contentType.Examples[name] = example + } + if schema := contentType.Schema; schema != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, schema, path); err != nil { + return err + } + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/responses/" + if component == nil { + return errors.New("invalid response: value MUST be a JSON object") + } + ref := component.Ref + if len(ref) > 0 { + + if isSingleRefElement(ref) { + var resp Response + if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil { + return err + } + + component.Value = &resp + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*ResponseRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveResponseRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + refDocumentPath, err := referencedDocumentPath(documentPath, ref) + if err != nil { + return err + } + + value := component.Value + if value == nil { + return nil + } + for _, header := range value.Headers { + if err := swaggerLoader.resolveHeaderRef(swagger, header, refDocumentPath); err != nil { + return err + } + } + for _, contentType := range value.Content { + if contentType == nil { + continue + } + for name, example := range contentType.Examples { + if err := swaggerLoader.resolveExampleRef(swagger, example, refDocumentPath); err != nil { + return err + } + contentType.Examples[name] = example + } + if schema := contentType.Schema; schema != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil { + return err + } + contentType.Schema = schema + } + } + for _, link := range value.Links { + if err := swaggerLoader.resolveLinkRef(swagger, link, refDocumentPath); err != nil { + return err + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/schemas/" + if component == nil { + return errors.New("invalid schema: value MUST be a JSON object") + } + ref := component.Ref + if len(ref) > 0 { + if isSingleRefElement(ref) { + var schema Schema + if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil { + return err + } + component.Value = &schema + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath) + if err != nil { + return err + } + + resolved, ok := untypedResolved.(*SchemaRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveSchemaRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + + refDocumentPath, err := referencedDocumentPath(documentPath, ref) + if err != nil { + return err + } + + value := component.Value + if value == nil { + return nil + } + + // ResolveRefs referred schemas + if v := value.Items; v != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + for _, v := range value.Properties { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + if v := value.AdditionalProperties; v != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + if v := value.Not; v != nil { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + for _, v := range value.AllOf { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + for _, v := range value.AnyOf { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + for _, v := range value.OneOf { + if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { + return err + } + } + + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, path *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/securitySchemes/" + if component == nil { + return errors.New("invalid securityScheme: value MUST be a JSON object") + } + if ref := component.Ref; len(ref) > 0 { + if isSingleRefElement(ref) { + var scheme SecurityScheme + if err := swaggerLoader.loadSingleElementFromURI(ref, path, &scheme); err != nil { + return err + } + + component.Value = &scheme + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*SecuritySchemeRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveSecuritySchemeRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, path *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/examples/" + if component == nil { + return errors.New("invalid example: value MUST be a JSON object") + } + if ref := component.Ref; len(ref) > 0 { + if isSingleRefElement(ref) { + var example Example + if err := swaggerLoader.loadSingleElementFromURI(ref, path, &example); err != nil { + return err + } + + component.Value = &example + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*ExampleRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveExampleRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, path *url.URL) error { + visited := swaggerLoader.visited + if _, isVisited := visited[component]; isVisited { + return nil + } + visited[component] = struct{}{} + + const prefix = "#/components/links/" + if component == nil { + return errors.New("invalid link: value MUST be a JSON object") + } + if ref := component.Ref; len(ref) > 0 { + if isSingleRefElement(ref) { + var link Link + if err := swaggerLoader.loadSingleElementFromURI(ref, path, &link); err != nil { + return err + } + + component.Value = &link + } else { + untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path) + if err != nil { + return err + } + resolved, ok := untypedResolved.(*LinkRef) + if !ok { + return failedToResolveRefFragment(ref) + } + if err := swaggerLoader.resolveLinkRef(swagger, resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + } + } + return nil +} + +func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) { + visited := swaggerLoader.visitedFiles + key := "_" + if documentPath != nil { + key = documentPath.EscapedPath() + } + key += entrypoint + if _, isVisited := visited[key]; isVisited { + return nil + } + visited[key] = struct{}{} + + const prefix = "#/paths/" + if pathItem == nil { + return errors.New("invalid path item: value MUST be a JSON object") + } + ref := pathItem.Ref + if ref != "" { + if isSingleRefElement(ref) { + var p PathItem + if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil { + return err + } + *pathItem = p + } else { + if swagger, ref, documentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, documentPath); err != nil { + return + } + + if !strings.HasPrefix(ref, prefix) { + err = fmt.Errorf("expected prefix '%s' in URI '%s'", prefix, ref) + return + } + id := unescapeRefString(ref[len(prefix):]) + + definitions := swagger.Paths + if definitions == nil { + return failedToResolveRefFragmentPart(ref, "paths") + } + resolved := definitions[id] + if resolved == nil { + return failedToResolveRefFragmentPart(ref, id) + } + + *pathItem = *resolved + } + } + + refDocumentPath, err := referencedDocumentPath(documentPath, ref) + if err != nil { + return err + } + + for _, parameter := range pathItem.Parameters { + if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil { + return + } + } + for _, operation := range pathItem.Operations() { + for _, parameter := range operation.Parameters { + if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil { + return + } + } + if requestBody := operation.RequestBody; requestBody != nil { + if err = swaggerLoader.resolveRequestBodyRef(swagger, requestBody, refDocumentPath); err != nil { + return + } + } + for _, response := range operation.Responses { + if err = swaggerLoader.resolveResponseRef(swagger, response, refDocumentPath); err != nil { + return + } + } + } + + return nil +} + +func unescapeRefString(ref string) string { + return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1) +} + +func referencedDocumentPath(documentPath *url.URL, ref string) (*url.URL, error) { + newDocumentPath := documentPath + if documentPath != nil { + refDirectory, err := url.Parse(path.Dir(ref)) + if err != nil { + return nil, err + } + joinedDirectory := path.Join(path.Dir(documentPath.String()), refDirectory.String()) + if newDocumentPath, err = url.Parse(joinedDirectory + "/"); err != nil { + return nil, err + } + } + return newDocumentPath, nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go new file mode 100644 index 000000000..d5de72d59 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go @@ -0,0 +1,20 @@ +package openapi3 + +// Tags is specified by OpenAPI/Swagger 3.0 standard. +type Tags []*Tag + +func (tags Tags) Get(name string) *Tag { + for _, tag := range tags { + if tag.Name == name { + return tag + } + } + return nil +} + +// Tag is specified by OpenAPI/Swagger 3.0 standard. +type Tag struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} diff --git a/vendor/github.com/ghodss/yaml/.gitignore b/vendor/github.com/ghodss/yaml/.gitignore new file mode 100644 index 000000000..e256a31e0 --- /dev/null +++ b/vendor/github.com/ghodss/yaml/.gitignore @@ -0,0 +1,20 @@ +# OSX leaves these everywhere on SMB shares +._* + +# Eclipse files +.classpath +.project +.settings/** + +# Emacs save files +*~ + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +# Go test binaries +*.test diff --git a/vendor/github.com/ghodss/yaml/.travis.yml b/vendor/github.com/ghodss/yaml/.travis.yml new file mode 100644 index 000000000..0e9d6edc0 --- /dev/null +++ b/vendor/github.com/ghodss/yaml/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: + - 1.3 + - 1.4 +script: + - go test + - go build diff --git a/vendor/github.com/ghodss/yaml/LICENSE b/vendor/github.com/ghodss/yaml/LICENSE new file mode 100644 index 000000000..7805d36de --- /dev/null +++ b/vendor/github.com/ghodss/yaml/LICENSE @@ -0,0 +1,50 @@ +The MIT License (MIT) + +Copyright (c) 2014 Sam Ghods + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/ghodss/yaml/README.md b/vendor/github.com/ghodss/yaml/README.md new file mode 100644 index 000000000..0200f75b4 --- /dev/null +++ b/vendor/github.com/ghodss/yaml/README.md @@ -0,0 +1,121 @@ +# YAML marshaling and unmarshaling support for Go + +[![Build Status](https://travis-ci.org/ghodss/yaml.svg)](https://travis-ci.org/ghodss/yaml) + +## Introduction + +A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs. + +In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/). + +## Compatibility + +This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility). + +## Caveats + +**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example: + +``` +BAD: + exampleKey: !!binary gIGC + +GOOD: + exampleKey: gIGC +... and decode the base64 data in your code. +``` + +**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys. + +## Installation and usage + +To install, run: + +``` +$ go get github.com/ghodss/yaml +``` + +And import using: + +``` +import "github.com/ghodss/yaml" +``` + +Usage is very similar to the JSON library: + +```go +package main + +import ( + "fmt" + + "github.com/ghodss/yaml" +) + +type Person struct { + Name string `json:"name"` // Affects YAML field names too. + Age int `json:"age"` +} + +func main() { + // Marshal a Person struct to YAML. + p := Person{"John", 30} + y, err := yaml.Marshal(p) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + age: 30 + name: John + */ + + // Unmarshal the YAML back into a Person struct. + var p2 Person + err = yaml.Unmarshal(y, &p2) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(p2) + /* Output: + {John 30} + */ +} +``` + +`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available: + +```go +package main + +import ( + "fmt" + + "github.com/ghodss/yaml" +) + +func main() { + j := []byte(`{"name": "John", "age": 30}`) + y, err := yaml.JSONToYAML(j) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + name: John + age: 30 + */ + j2, err := yaml.YAMLToJSON(y) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(j2)) + /* Output: + {"age":30,"name":"John"} + */ +} +``` diff --git a/vendor/github.com/ghodss/yaml/fields.go b/vendor/github.com/ghodss/yaml/fields.go new file mode 100644 index 000000000..586007402 --- /dev/null +++ b/vendor/github.com/ghodss/yaml/fields.go @@ -0,0 +1,501 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package yaml + +import ( + "bytes" + "encoding" + "encoding/json" + "reflect" + "sort" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + if v.CanSet() { + v.Set(reflect.New(v.Type().Elem())) + } else { + v = reflect.New(v.Type().Elem()) + } + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(json.Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + v = v.Elem() + } + return nil, nil, v +} + +// A field represents a single field found in a struct. +type field struct { + name string + nameBytes []byte // []byte(name) + equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent + + tag bool + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +func fillField(f field) field { + f.nameBytes = []byte(f.name) + f.equalFold = foldFunc(f.nameBytes) + return f +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from json tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, fillField(field{ + name: name, + tag: tagged, + index: index, + typ: ft, + omitEmpty: opts.Contains("omitempty"), + quoted: opts.Contains("string"), + })) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// * S maps to s and to U+017F 'ſ' Latin small letter long s +// * k maps to K and to U+212A 'K' Kelvin sign +// See http://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + if len(t) > 0 { + return false + } + return true +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/vendor/github.com/ghodss/yaml/yaml.go b/vendor/github.com/ghodss/yaml/yaml.go new file mode 100644 index 000000000..4fb4054a8 --- /dev/null +++ b/vendor/github.com/ghodss/yaml/yaml.go @@ -0,0 +1,277 @@ +package yaml + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" + + "gopkg.in/yaml.v2" +) + +// Marshals the object into JSON then converts JSON to YAML and returns the +// YAML. +func Marshal(o interface{}) ([]byte, error) { + j, err := json.Marshal(o) + if err != nil { + return nil, fmt.Errorf("error marshaling into JSON: %v", err) + } + + y, err := JSONToYAML(j) + if err != nil { + return nil, fmt.Errorf("error converting JSON to YAML: %v", err) + } + + return y, nil +} + +// Converts YAML to JSON then uses JSON to unmarshal into an object. +func Unmarshal(y []byte, o interface{}) error { + vo := reflect.ValueOf(o) + j, err := yamlToJSON(y, &vo) + if err != nil { + return fmt.Errorf("error converting YAML to JSON: %v", err) + } + + err = json.Unmarshal(j, o) + if err != nil { + return fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return nil +} + +// Convert JSON to YAML. +func JSONToYAML(j []byte) ([]byte, error) { + // Convert the JSON to an object. + var jsonObj interface{} + // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the + // Go JSON library doesn't try to pick the right number type (int, float, + // etc.) when unmarshalling to interface{}, it just picks float64 + // universally. go-yaml does go through the effort of picking the right + // number type, so we can preserve number type throughout this process. + err := yaml.Unmarshal(j, &jsonObj) + if err != nil { + return nil, err + } + + // Marshal this object into YAML. + return yaml.Marshal(jsonObj) +} + +// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through +// this method should be a no-op. +// +// Things YAML can do that are not supported by JSON: +// * In YAML you can have binary and null keys in your maps. These are invalid +// in JSON. (int and float keys are converted to strings.) +// * Binary data in YAML with the !!binary tag is not supported. If you want to +// use binary data with this library, encode the data as base64 as usual but do +// not use the !!binary tag in your YAML. This will ensure the original base64 +// encoded data makes it all the way through to the JSON. +func YAMLToJSON(y []byte) ([]byte, error) { + return yamlToJSON(y, nil) +} + +func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) { + // Convert the YAML to an object. + var yamlObj interface{} + err := yaml.Unmarshal(y, &yamlObj) + if err != nil { + return nil, err + } + + // YAML objects are not completely compatible with JSON objects (e.g. you + // can have non-string keys in YAML). So, convert the YAML-compatible object + // to a JSON-compatible object, failing with an error if irrecoverable + // incompatibilties happen along the way. + jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget) + if err != nil { + return nil, err + } + + // Convert this object to JSON and return the data. + return json.Marshal(jsonObj) +} + +func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) { + var err error + + // Resolve jsonTarget to a concrete value (i.e. not a pointer or an + // interface). We pass decodingNull as false because we're not actually + // decoding into the value, we're just checking if the ultimate target is a + // string. + if jsonTarget != nil { + ju, tu, pv := indirect(*jsonTarget, false) + // We have a JSON or Text Umarshaler at this level, so we can't be trying + // to decode into a string. + if ju != nil || tu != nil { + jsonTarget = nil + } else { + jsonTarget = &pv + } + } + + // If yamlObj is a number or a boolean, check if jsonTarget is a string - + // if so, coerce. Else return normal. + // If yamlObj is a map or array, find the field that each key is + // unmarshaling to, and when you recurse pass the reflect.Value for that + // field back into this function. + switch typedYAMLObj := yamlObj.(type) { + case map[interface{}]interface{}: + // JSON does not support arbitrary keys in a map, so we must convert + // these keys to strings. + // + // From my reading of go-yaml v2 (specifically the resolve function), + // keys can only have the types string, int, int64, float64, binary + // (unsupported), or null (unsupported). + strMap := make(map[string]interface{}) + for k, v := range typedYAMLObj { + // Resolve the key to a string first. + var keyString string + switch typedKey := k.(type) { + case string: + keyString = typedKey + case int: + keyString = strconv.Itoa(typedKey) + case int64: + // go-yaml will only return an int64 as a key if the system + // architecture is 32-bit and the key's value is between 32-bit + // and 64-bit. Otherwise the key type will simply be int. + keyString = strconv.FormatInt(typedKey, 10) + case float64: + // Stolen from go-yaml to use the same conversion to string as + // the go-yaml library uses to convert float to string when + // Marshaling. + s := strconv.FormatFloat(typedKey, 'g', -1, 32) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + keyString = s + case bool: + if typedKey { + keyString = "true" + } else { + keyString = "false" + } + default: + return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v", + reflect.TypeOf(k), k, v) + } + + // jsonTarget should be a struct or a map. If it's a struct, find + // the field it's going to map to and pass its reflect.Value. If + // it's a map, find the element type of the map and pass the + // reflect.Value created from that type. If it's neither, just pass + // nil - JSON conversion will error for us if it's a real issue. + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Struct { + keyBytes := []byte(keyString) + // Find the field that the JSON library would use. + var f *field + fields := cachedTypeFields(t.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, keyBytes) { + f = ff + break + } + // Do case-insensitive comparison. + if f == nil && ff.equalFold(ff.nameBytes, keyBytes) { + f = ff + } + } + if f != nil { + // Find the reflect.Value of the most preferential + // struct field. + jtf := t.Field(f.index[0]) + strMap[keyString], err = convertToJSONableObject(v, &jtf) + if err != nil { + return nil, err + } + continue + } + } else if t.Kind() == reflect.Map { + // Create a zero value of the map's element type to use as + // the JSON target. + jtv := reflect.Zero(t.Type().Elem()) + strMap[keyString], err = convertToJSONableObject(v, &jtv) + if err != nil { + return nil, err + } + continue + } + } + strMap[keyString], err = convertToJSONableObject(v, nil) + if err != nil { + return nil, err + } + } + return strMap, nil + case []interface{}: + // We need to recurse into arrays in case there are any + // map[interface{}]interface{}'s inside and to convert any + // numbers to strings. + + // If jsonTarget is a slice (which it really should be), find the + // thing it's going to map to. If it's not a slice, just pass nil + // - JSON conversion will error for us if it's a real issue. + var jsonSliceElemValue *reflect.Value + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Slice { + // By default slices point to nil, but we need a reflect.Value + // pointing to a value of the slice type, so we create one here. + ev := reflect.Indirect(reflect.New(t.Type().Elem())) + jsonSliceElemValue = &ev + } + } + + // Make and use a new array. + arr := make([]interface{}, len(typedYAMLObj)) + for i, v := range typedYAMLObj { + arr[i], err = convertToJSONableObject(v, jsonSliceElemValue) + if err != nil { + return nil, err + } + } + return arr, nil + default: + // If the target type is a string and the YAML type is a number, + // convert the YAML type to a string. + if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String { + // Based on my reading of go-yaml, it may return int, int64, + // float64, or uint64. + var s string + switch typedVal := typedYAMLObj.(type) { + case int: + s = strconv.FormatInt(int64(typedVal), 10) + case int64: + s = strconv.FormatInt(typedVal, 10) + case float64: + s = strconv.FormatFloat(typedVal, 'g', -1, 32) + case uint64: + s = strconv.FormatUint(typedVal, 10) + case bool: + if typedVal { + s = "true" + } else { + s = "false" + } + } + if len(s) > 0 { + yamlObj = interface{}(s) + } + } + return yamlObj, nil + } + + return nil, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7ff658800..68027bc92 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -78,12 +78,21 @@ github.com/coreos/go-systemd/activation # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/deepmap/oapi-codegen v1.3.12 +github.com/deepmap/oapi-codegen/cmd/oapi-codegen +github.com/deepmap/oapi-codegen/pkg/codegen +github.com/deepmap/oapi-codegen/pkg/codegen/templates github.com/deepmap/oapi-codegen/pkg/runtime github.com/deepmap/oapi-codegen/pkg/types +github.com/deepmap/oapi-codegen/pkg/util # github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go # github.com/dimchansky/utfbom v1.1.0 github.com/dimchansky/utfbom +# github.com/getkin/kin-openapi v0.13.0 +github.com/getkin/kin-openapi/jsoninfo +github.com/getkin/kin-openapi/openapi3 +# github.com/ghodss/yaml v1.0.0 +github.com/ghodss/yaml # github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/chi # github.com/gobwas/glob v0.2.3