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

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