go: vendor the oapi-codegen cmd

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 <ondrej@budai.cz>
This commit is contained in:
Ondřej Budai 2021-01-29 09:56:22 +01:00 committed by Ondřej Budai
parent 1a3cbb282a
commit 2241a8d9ed
75 changed files with 11211 additions and 0 deletions

2
go.sum
View file

@ -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=

15
internal/tools.go Normal file
View file

@ -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"
)

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 &param
}
}
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
}

View file

@ -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
}
}
}

View file

@ -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)
}

View file

@ -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,
}

View file

@ -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}}

View file

@ -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
}

View file

@ -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}}
}

View file

@ -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(), &params.{{.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}}

View file

@ -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 */}}

View file

@ -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 */}}

View file

@ -0,0 +1,3 @@
package templates
//go:generate go run github.com/cyberdelia/templates -s . -o templates.gen.go

View file

@ -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}}

View file

@ -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
}

View file

@ -0,0 +1,6 @@
{{range .}}{{$opid := .OperationId}}
{{range .TypeDefinitions}}
// {{.TypeName}} defines parameters for {{$opid}}.
type {{.TypeName}} {{.Schema.TypeDecl}}
{{end}}
{{end}}

View file

@ -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}}
}

View file

@ -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}}

View file

@ -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}}
}

View file

@ -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(), &params.{{.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(), &params.{{.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
}

View file

@ -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}}

View file

@ -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(), &params.{{.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}}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}
}

21
vendor/github.com/getkin/kin-openapi/LICENSE generated vendored Normal file
View file

@ -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.

2
vendor/github.com/getkin/kin-openapi/jsoninfo/doc.go generated vendored Normal file
View file

@ -0,0 +1,2 @@
// Package jsoninfo provides information and functions for marshalling/unmarshalling JSON.
package jsoninfo

View file

@ -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
}

View file

@ -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
}

View file

@ -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"`
}

View file

@ -0,0 +1,6 @@
package jsoninfo
type StrictStruct interface {
EncodeWith(encoder *ObjectEncoder, value interface{}) error
DecodeWith(decoder *ObjectDecoder, value interface{}) error
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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, "', '"))
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

5
vendor/github.com/getkin/kin-openapi/openapi3/doc.go generated vendored Normal file
View file

@ -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

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

93
vendor/github.com/getkin/kin-openapi/openapi3/info.go generated vendored Normal file
View file

@ -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
}

38
vendor/github.com/getkin/kin-openapi/openapi3/link.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

122
vendor/github.com/getkin/kin-openapi/openapi3/paths.go generated vendored Normal file
View file

@ -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
}

199
vendor/github.com/getkin/kin-openapi/openapi3/refs.go generated vendored Normal file
View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

1235
vendor/github.com/getkin/kin-openapi/openapi3/schema.go generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -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})?$`)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

148
vendor/github.com/getkin/kin-openapi/openapi3/server.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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, &param); err != nil {
return err
}
component.Value = &param
} 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
}

20
vendor/github.com/getkin/kin-openapi/openapi3/tag.go generated vendored Normal file
View file

@ -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"`
}

20
vendor/github.com/ghodss/yaml/.gitignore generated vendored Normal file
View file

@ -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

7
vendor/github.com/ghodss/yaml/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,7 @@
language: go
go:
- 1.3
- 1.4
script:
- go test
- go build

50
vendor/github.com/ghodss/yaml/LICENSE generated vendored Normal file
View file

@ -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.

121
vendor/github.com/ghodss/yaml/README.md generated vendored Normal file
View file

@ -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"}
*/
}
```

501
vendor/github.com/ghodss/yaml/fields.go generated vendored Normal file
View file

@ -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 '' 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
}

277
vendor/github.com/ghodss/yaml/yaml.go generated vendored Normal file
View file

@ -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
}

9
vendor/modules.txt vendored
View file

@ -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