[chore] Bump go swagger (#2871)

* bump go swagger version

* bump swagger version
This commit is contained in:
tobi 2024-04-26 11:31:10 +02:00 committed by GitHub
commit fd8a724e77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
251 changed files with 10841 additions and 11896 deletions

View file

@ -2,13 +2,12 @@ package commands
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"errors"
"github.com/go-openapi/loads"
"github.com/go-swagger/go-swagger/cmd/swagger/commands/diff"
)

View file

@ -42,8 +42,8 @@ func CompareProperties(location DifferenceLocation, schema1 *spec.Schema, schema
schema1Props := propertiesFor(schema1, getRefFn1)
schema2Props := propertiesFor(schema2, getRefFn2)
// find deleted and changed properties
// find deleted and changed properties
for eachProp1Name, eachProp1 := range schema1Props {
eachProp1 := eachProp1
childLoc := addChildDiffNode(location, eachProp1Name, eachProp1.Schema)
@ -66,7 +66,13 @@ func CompareProperties(location DifferenceLocation, schema1 *spec.Schema, schema
eachProp2 := eachProp2
if _, ok := schema1.Properties[eachProp2Name]; !ok {
childLoc := addChildDiffNode(location, eachProp2Name, &eachProp2)
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedProperty})
analyzedProp2 := schema2Props[eachProp2Name]
if analyzedProp2.Required {
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedRequiredProperty})
} else {
propDiffs = append(propDiffs, SpecDifference{DifferenceLocation: childLoc, Code: AddedProperty})
}
}
}
return propDiffs

View file

@ -36,11 +36,12 @@ func init() {
AddedConstraint: NonBreaking,
DeletedExtension: Warning,
AddedExtension: Warning,
ChangedExtensionValue: Warning,
},
ForRequest: map[SpecChangeCode]Compatibility{
AddedRequiredProperty: Breaking,
DeletedProperty: Breaking,
AddedProperty: Breaking,
AddedProperty: NonBreaking,
AddedOptionalParam: NonBreaking,
AddedRequiredParam: Breaking,
DeletedOptionalParam: NonBreaking,
@ -70,6 +71,7 @@ func init() {
ChangedCollectionFormat: Breaking,
DeletedExtension: Warning,
AddedExtension: Warning,
ChangedExtensionValue: Warning,
},
ForChange: map[SpecChangeCode]Compatibility{
NoChangeDetected: NonBreaking,
@ -96,6 +98,7 @@ func init() {
DeletedDefinition: NonBreaking,
DeletedExtension: Warning,
AddedExtension: Warning,
ChangedExtensionValue: Warning,
},
}
}

View file

@ -117,6 +117,8 @@ const (
DeletedExtension
// AddedExtension added an extension
AddedExtension
// ChangedExtensionValue changed an extension value
ChangedExtensionValue
)
var toLongStringSpecChangeCode = map[SpecChangeCode]string{
@ -173,6 +175,7 @@ var toLongStringSpecChangeCode = map[SpecChangeCode]string{
ChangedCollectionFormat: "Changed collection format",
DeletedExtension: "Deleted Extension",
AddedExtension: "Added Extension",
ChangedExtensionValue: "Changed Extension Value",
}
var toStringSpecChangeCode = map[SpecChangeCode]string{
@ -229,6 +232,7 @@ var toStringSpecChangeCode = map[SpecChangeCode]string{
ChangedCollectionFormat: "ChangedCollectionFormat",
DeletedExtension: "DeletedExtension",
AddedExtension: "AddedExtension",
ChangedExtensionValue: "ChangedExtensionValue",
}
var toIDSpecChangeCode = map[string]SpecChangeCode{}

View file

@ -2,6 +2,7 @@ package diff
import (
"fmt"
"reflect"
"strings"
"github.com/go-openapi/spec"
@ -230,7 +231,15 @@ func (sd *SpecAnalyser) analyseResponseParams() {
// deleted responses
for code1 := range op1Responses {
if _, ok := op2Responses[code1]; !ok {
location := DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code1, Node: getSchemaDiffNode("Body", op1Responses[code1].Schema)}
location := DifferenceLocation{
URL: eachURLMethodFrom2.Path,
Method: eachURLMethodFrom2.Method,
Response: code1,
Node: getNameOnlyDiffNode("NoContent"),
}
if op1Responses[code1].Schema != nil {
location.Node = getSchemaDiffNode("Body", op1Responses[code1].Schema)
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: location, Code: DeletedResponse})
}
}
@ -272,11 +281,22 @@ func (sd *SpecAnalyser) analyseResponseParams() {
sd.compareDescripton(responseLocation, op1Response.Description, op2Response.Description)
if op1Response.Schema != nil {
sd.compareSchema(
DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op1Response.Schema)},
op1Response.Schema,
op2Response.Schema)
if op2Response.Schema == nil {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op1Response.Schema)},
Code: DeletedProperty})
} else {
sd.compareSchema(
DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op1Response.Schema)},
op1Response.Schema,
op2Response.Schema)
}
} else if op2Response.Schema != nil {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: DifferenceLocation{URL: eachURLMethodFrom2.Path, Method: eachURLMethodFrom2.Method, Response: code2, Node: getSchemaDiffNode("Body", op2Response.Schema)},
Code: AddedProperty})
}
} else {
// op2Response
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
@ -293,6 +313,7 @@ func (sd *SpecAnalyser) analyseExtensions(spec1, spec2 *spec.Swagger) {
specLoc := DifferenceLocation{Node: &Node{Field: "Spec"}}
sd.checkAddedExtensions(spec1.Extensions, spec2.Extensions, specLoc, "")
sd.checkDeletedExtensions(spec1.Extensions, spec2.Extensions, specLoc, "")
sd.checkChangedExtensions(spec1.Extensions, spec2.Extensions, specLoc, "")
sd.analyzeInfoExtensions()
sd.analyzeTagExtensions(spec1, spec2)
@ -302,19 +323,27 @@ func (sd *SpecAnalyser) analyseExtensions(spec1, spec2 *spec.Swagger) {
}
func (sd *SpecAnalyser) analyzeOperationExtensions() {
pathsIterated := make(map[string]struct{})
for urlMethod, op2 := range sd.urlMethods2 {
pathAndMethodLoc := DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method}
if op1, ok := sd.urlMethods1[urlMethod]; ok {
sd.checkAddedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
if _, ok := pathsIterated[urlMethod.Path]; !ok {
sd.checkAddedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
sd.checkChangedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
pathsIterated[urlMethod.Path] = struct{}{}
}
sd.checkAddedExtensions(op1.Operation.Responses.Extensions, op2.Operation.Responses.Extensions, pathAndMethodLoc, "Responses")
sd.checkChangedExtensions(op1.Operation.Responses.Extensions, op2.Operation.Responses.Extensions, pathAndMethodLoc, "Responses")
sd.checkAddedExtensions(op1.Operation.Extensions, op2.Operation.Extensions, pathAndMethodLoc, "")
sd.checkChangedExtensions(op1.Operation.Extensions, op2.Operation.Extensions, pathAndMethodLoc, "")
sd.checkParamExtensions(op1, op2, urlMethod)
for code, resp := range op1.Operation.Responses.StatusCodeResponses {
for hdr, h := range resp.Headers {
op2StatusCode, ok := op2.Operation.Responses.StatusCodeResponses[code]
if ok {
if _, ok = op2StatusCode.Headers[hdr]; ok {
sd.checkAddedExtensions(h.Extensions, op2StatusCode.Headers[hdr].Extensions, DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method, Node: getNameOnlyDiffNode("Headers")}, hdr)
sd.checkChangedExtensions(h.Extensions, op2StatusCode.Headers[hdr].Extensions, DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method, Node: getNameOnlyDiffNode("Headers")}, hdr)
}
}
}
@ -326,10 +355,14 @@ func (sd *SpecAnalyser) analyzeOperationExtensions() {
}
}
pathsIterated = make(map[string]struct{})
for urlMethod, op1 := range sd.urlMethods1 {
pathAndMethodLoc := DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method}
if op2, ok := sd.urlMethods2[urlMethod]; ok {
sd.checkDeletedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
if _, ok := pathsIterated[urlMethod.Path]; !ok {
sd.checkDeletedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
pathsIterated[urlMethod.Path] = struct{}{}
}
sd.checkDeletedExtensions(op1.Operation.Responses.Extensions, op2.Operation.Responses.Extensions, pathAndMethodLoc, "Responses")
sd.checkDeletedExtensions(op1.Operation.Extensions, op2.Operation.Extensions, pathAndMethodLoc, "")
for code, resp := range op1.Operation.Responses.StatusCodeResponses {
@ -346,11 +379,42 @@ func (sd *SpecAnalyser) analyzeOperationExtensions() {
}
}
func (sd *SpecAnalyser) checkParamExtensions(op1 *PathItemOp, op2 *PathItemOp, urlMethod URLMethod) {
locations := []string{"query", "path", "body", "header", "formData"}
titles := []string{"Query", "Path", "Body", "Header", "FormData"}
for i, paramLocation := range locations {
rootNode := getNameOnlyDiffNode(titles[i])
params1 := getParams(op1.ParentPathItem.Parameters, op1.Operation.Parameters, paramLocation)
params2 := getParams(op2.ParentPathItem.Parameters, op2.Operation.Parameters, paramLocation)
location := DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method, Node: rootNode}
// detect deleted param extensions
for paramName1, param1 := range params1 {
if param2, ok := params2[paramName1]; ok {
childLocation := location.AddNode(getSchemaDiffNode(paramName1, &param1.SimpleSchema))
sd.checkDeletedExtensions(param1.Extensions, param2.Extensions, childLocation, "")
}
}
// detect added changed params
for paramName2, param2 := range params2 {
// changed?
if param1, ok := params1[paramName2]; ok {
childLocation := location.AddNode(getSchemaDiffNode(paramName2, &param1.SimpleSchema))
sd.checkAddedExtensions(param1.Extensions, param2.Extensions, childLocation, "")
sd.checkChangedExtensions(param1.Extensions, param2.Extensions, childLocation, "")
}
}
}
}
func (sd *SpecAnalyser) analyzeSecurityDefinitionExtensions(spec1 *spec.Swagger, spec2 *spec.Swagger) {
securityDefLoc := DifferenceLocation{Node: &Node{Field: "Security Definitions"}}
for key, securityDef := range spec1.SecurityDefinitions {
for key, securityDef1 := range spec1.SecurityDefinitions {
if securityDef2, ok := spec2.SecurityDefinitions[key]; ok {
sd.checkAddedExtensions(securityDef.Extensions, securityDef2.Extensions, securityDefLoc, "")
sd.checkAddedExtensions(securityDef1.Extensions, securityDef2.Extensions, securityDefLoc, "")
sd.checkChangedExtensions(securityDef1.Extensions, securityDef2.Extensions, securityDefLoc, "")
}
}
@ -365,6 +429,7 @@ func (sd *SpecAnalyser) analyzeSchemaExtensions(schema1, schema2 *spec.Schema, c
if schema1 != nil && schema2 != nil {
diffLoc := DifferenceLocation{Response: code, URL: urlMethod.Path, Method: urlMethod.Method, Node: getSchemaDiffNode("Body", schema2)}
sd.checkAddedExtensions(schema1.Extensions, schema2.Extensions, diffLoc, "")
sd.checkChangedExtensions(schema1.Extensions, schema2.Extensions, diffLoc, "")
sd.checkDeletedExtensions(schema1.Extensions, schema2.Extensions, diffLoc, "")
if schema1.Items != nil && schema2.Items != nil {
sd.analyzeSchemaExtensions(schema1.Items.Schema, schema2.Items.Schema, code, urlMethod)
@ -384,15 +449,18 @@ func (sd *SpecAnalyser) analyzeInfoExtensions() {
diffLocation := DifferenceLocation{Node: &Node{Field: "Spec Info"}}
sd.checkAddedExtensions(sd.Info1.Extensions, sd.Info2.Extensions, diffLocation, "")
sd.checkDeletedExtensions(sd.Info1.Extensions, sd.Info2.Extensions, diffLocation, "")
sd.checkChangedExtensions(sd.Info1.Extensions, sd.Info2.Extensions, diffLocation, "")
if sd.Info1.Contact != nil && sd.Info2.Contact != nil {
diffLocation = DifferenceLocation{Node: &Node{Field: "Spec Info.Contact"}}
sd.checkAddedExtensions(sd.Info1.Contact.Extensions, sd.Info2.Contact.Extensions, diffLocation, "")
sd.checkDeletedExtensions(sd.Info1.Contact.Extensions, sd.Info2.Contact.Extensions, diffLocation, "")
sd.checkChangedExtensions(sd.Info1.Contact.Extensions, sd.Info2.Contact.Extensions, diffLocation, "")
}
if sd.Info1.License != nil && sd.Info2.License != nil {
diffLocation = DifferenceLocation{Node: &Node{Field: "Spec Info.License"}}
sd.checkAddedExtensions(sd.Info1.License.Extensions, sd.Info2.License.Extensions, diffLocation, "")
sd.checkDeletedExtensions(sd.Info1.License.Extensions, sd.Info2.License.Extensions, diffLocation, "")
sd.checkChangedExtensions(sd.Info1.License.Extensions, sd.Info2.License.Extensions, diffLocation, "")
}
}
}
@ -403,6 +471,7 @@ func (sd *SpecAnalyser) analyzeTagExtensions(spec1 *spec.Swagger, spec2 *spec.Sw
for _, spec1Tag := range spec1.Tags {
if spec2Tag.Name == spec1Tag.Name {
sd.checkAddedExtensions(spec1Tag.Extensions, spec2Tag.Extensions, diffLocation, "")
sd.checkChangedExtensions(spec1Tag.Extensions, spec2Tag.Extensions, diffLocation, "")
}
}
}
@ -430,6 +499,21 @@ func (sd *SpecAnalyser) checkAddedExtensions(extensions1 spec.Extensions, extens
}
}
func (sd *SpecAnalyser) checkChangedExtensions(extensions1 spec.Extensions, extensions2 spec.Extensions, diffLocation DifferenceLocation, fieldPrefix string) {
for extKey, ext2Val := range extensions2 {
if ext1Val, ok := extensions1[extKey]; ok && !reflect.DeepEqual(ext1Val, ext2Val) {
if fieldPrefix != "" {
extKey = fmt.Sprintf("%s.%s", fieldPrefix, extKey)
}
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
DifferenceLocation: diffLocation.AddNode(&Node{Field: extKey}),
Code: ChangedExtensionValue,
Compatibility: Warning, // this could potentially be a breaking change
})
}
}
}
func (sd *SpecAnalyser) checkDeletedExtensions(extensions1 spec.Extensions, extensions2 spec.Extensions, diffLocation DifferenceLocation, fieldPrefix string) {
for extKey := range extensions1 {
if _, ok := extensions2[extKey]; !ok {
@ -746,7 +830,11 @@ func (sd *SpecAnalyser) schemaFromRef(ref spec.Ref, defns *spec.Definitions) (ac
}
func schemaLocationKey(location DifferenceLocation) string {
return location.Method + location.URL + location.Node.Field + location.Node.TypeName
k := location.Method + location.URL + location.Node.Field + location.Node.TypeName
if location.Node.ChildNode != nil && location.Node.ChildNode.IsArray {
k += location.Node.ChildNode.Field + location.Node.ChildNode.TypeName
}
return k
}
// PropertyDefn combines a property with its required-ness

View file

@ -63,9 +63,10 @@ func writeToFile(swspec *spec.Swagger, pretty bool, format string, output string
}
var bb interface{}
bb, err = data.MarshalYAML()
b = bb.([]byte)
if err == nil {
b = bb.([]byte)
}
}
}
if err != nil {

View file

@ -29,6 +29,7 @@ type modelOptions struct {
KeepSpecOrder bool `long:"keep-spec-order" description:"keep schema properties order identical to spec file"`
AllDefinitions bool `long:"all-definitions" description:"generate all model definitions regardless of usage in operations" hidden:"deprecated"`
StructTags []string `long:"struct-tags" description:"the struct tags to generate, repeat for multiple (defaults to json)"`
RootedErrorPath bool `long:"rooted-error-path" description:"extends validation errors with the type name instead of an empty path, in the case of arrays and maps"`
}
func (mo modelOptions) apply(opts *generator.GenOpts) {
@ -39,6 +40,7 @@ func (mo modelOptions) apply(opts *generator.GenOpts) {
opts.PropertiesSpecOrder = mo.KeepSpecOrder
opts.IgnoreOperations = mo.AllDefinitions
opts.StructTags = mo.StructTags
opts.WantsRootedErrorPath = mo.RootedErrorPath
}
// WithModels adds the model options group.

View file

@ -17,7 +17,7 @@ import (
// FlattenCmdOptions determines options to the flatten spec preprocessing
type FlattenCmdOptions struct {
WithExpand bool `long:"with-expand" description:"expands all $ref's in spec prior to generation (shorthand to --with-flatten=expand)" group:"shared"`
WithFlatten []string `long:"with-flatten" description:"flattens all $ref's in spec prior to generation" choice:"minimal" choice:"full" choice:"expand" choice:"verbose" choice:"noverbose" choice:"remove-unused" default:"minimal" default:"verbose" group:"shared"` // nolint: staticcheck
WithFlatten []string `long:"with-flatten" description:"flattens all $ref's in spec prior to generation" choice:"minimal" choice:"full" choice:"expand" choice:"verbose" choice:"noverbose" choice:"remove-unused" choice:"keep-names" default:"minimal" default:"verbose" group:"shared"`
}
// SetFlattenOptions builds flatten options from command line args
@ -64,6 +64,8 @@ func (f *FlattenCmdOptions) SetFlattenOptions(dflt *analysis.FlattenOpts) (res *
res.Minimal = true
minimalIsSet = true
}
case "keep-names":
res.KeepNames = true
}
}
return

View file

@ -1,6 +1,3 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -23,16 +20,17 @@ import (
"os"
"strings"
"github.com/go-swagger/go-swagger/codescan"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/go-swagger/go-swagger/scan"
"github.com/jessevdk/go-flags"
"gopkg.in/yaml.v3"
)
// SpecFile command to generate a swagger spec from a go application
type SpecFile struct {
BasePath string `long:"base-path" short:"b" description:"the base path to use" default:"."`
WorkDir string `long:"work-dir" short:"w" description:"the base path to use" default:"."`
BuildTags string `long:"tags" short:"t" description:"build tags" default:""`
ScanModels bool `long:"scan-models" short:"m" description:"includes models that were annotated with 'swagger:model'"`
Compact bool `long:"compact" description:"when present, doesn't prettify the json"`
@ -42,25 +40,32 @@ type SpecFile struct {
Exclude []string `long:"exclude" short:"x" description:"exclude packages matching pattern"`
IncludeTags []string `long:"include-tag" short:"" description:"include routes having specified tags (can be specified many times)"`
ExcludeTags []string `long:"exclude-tag" short:"" description:"exclude routes having specified tags (can be specified many times)"`
ExcludeDeps bool `long:"exclude-deps" short:"" description:"exclude all dependencies of project"`
}
// Execute runs this command
func (s *SpecFile) Execute(args []string) error {
if len(args) == 0 { // by default consider all the paths under the working directory
args = []string{"./..."}
}
input, err := loadSpec(string(s.Input))
if err != nil {
return err
}
var opts scan.Opts
opts.BasePath = s.BasePath
opts.Input = input
var opts codescan.Options
opts.Packages = args
opts.WorkDir = s.WorkDir
opts.InputSpec = input
opts.ScanModels = s.ScanModels
opts.BuildTags = s.BuildTags
opts.Include = s.Include
opts.Exclude = s.Exclude
opts.IncludeTags = s.IncludeTags
opts.ExcludeTags = s.ExcludeTags
swspec, err := scan.Application(opts)
opts.ExcludeDeps = s.ExcludeDeps
swspec, err := codescan.Run(&opts)
if err != nil {
return err
}
@ -100,7 +105,7 @@ func writeToFile(swspec *spec.Swagger, pretty bool, output string) error {
fmt.Println(string(b))
return nil
}
return os.WriteFile(output, b, 0644)
return os.WriteFile(output, b, 0644) // #nosec
}
func marshalToJSONFormat(swspec *spec.Swagger, pretty bool) ([]byte, error) {

View file

@ -1,119 +0,0 @@
//go:build go1.11
// +build go1.11
package generate
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/go-swagger/go-swagger/codescan"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/jessevdk/go-flags"
"gopkg.in/yaml.v3"
)
// SpecFile command to generate a swagger spec from a go application
type SpecFile struct {
WorkDir string `long:"work-dir" short:"w" description:"the base path to use" default:"."`
BuildTags string `long:"tags" short:"t" description:"build tags" default:""`
ScanModels bool `long:"scan-models" short:"m" description:"includes models that were annotated with 'swagger:model'"`
Compact bool `long:"compact" description:"when present, doesn't prettify the json"`
Output flags.Filename `long:"output" short:"o" description:"the file to write to"`
Input flags.Filename `long:"input" short:"i" description:"an input swagger file with which to merge"`
Include []string `long:"include" short:"c" description:"include packages matching pattern"`
Exclude []string `long:"exclude" short:"x" description:"exclude packages matching pattern"`
IncludeTags []string `long:"include-tag" short:"" description:"include routes having specified tags (can be specified many times)"`
ExcludeTags []string `long:"exclude-tag" short:"" description:"exclude routes having specified tags (can be specified many times)"`
ExcludeDeps bool `long:"exclude-deps" short:"" description:"exclude all dependencies of project"`
}
// Execute runs this command
func (s *SpecFile) Execute(args []string) error {
if len(args) == 0 { // by default consider all the paths under the working directory
args = []string{"./..."}
}
input, err := loadSpec(string(s.Input))
if err != nil {
return err
}
var opts codescan.Options
opts.Packages = args
opts.WorkDir = s.WorkDir
opts.InputSpec = input
opts.ScanModels = s.ScanModels
opts.BuildTags = s.BuildTags
opts.Include = s.Include
opts.Exclude = s.Exclude
opts.IncludeTags = s.IncludeTags
opts.ExcludeTags = s.ExcludeTags
opts.ExcludeDeps = s.ExcludeDeps
swspec, err := codescan.Run(&opts)
if err != nil {
return err
}
return writeToFile(swspec, !s.Compact, string(s.Output))
}
func loadSpec(input string) (*spec.Swagger, error) {
if fi, err := os.Stat(input); err == nil {
if fi.IsDir() {
return nil, fmt.Errorf("expected %q to be a file not a directory", input)
}
sp, err := loads.Spec(input)
if err != nil {
return nil, err
}
return sp.Spec(), nil
}
return nil, nil
}
func writeToFile(swspec *spec.Swagger, pretty bool, output string) error {
var b []byte
var err error
if strings.HasSuffix(output, "yml") || strings.HasSuffix(output, "yaml") {
b, err = marshalToYAMLFormat(swspec)
} else {
b, err = marshalToJSONFormat(swspec, pretty)
}
if err != nil {
return err
}
if output == "" {
fmt.Println(string(b))
return nil
}
return os.WriteFile(output, b, 0644) // #nosec
}
func marshalToJSONFormat(swspec *spec.Swagger, pretty bool) ([]byte, error) {
if pretty {
return json.MarshalIndent(swspec, "", " ")
}
return json.Marshal(swspec)
}
func marshalToYAMLFormat(swspec *spec.Swagger) ([]byte, error) {
b, err := json.Marshal(swspec)
if err != nil {
return nil, err
}
var jsonObj interface{}
if err := yaml.Unmarshal(b, &jsonObj); err != nil {
return nil, err
}
return yaml.Marshal(jsonObj)
}

View file

@ -23,7 +23,7 @@ type ServeCmd struct {
BasePath string `long:"base-path" description:"the base path to serve the spec and UI at"`
Flavor string `short:"F" long:"flavor" description:"the flavor of docs, can be swagger or redoc" default:"redoc" choice:"redoc" choice:"swagger"`
DocURL string `long:"doc-url" description:"override the url which takes a url query param to render the doc ui"`
NoOpen bool `long:"no-open" description:"when present won't open the the browser to show the url"`
NoOpen bool `long:"no-open" description:"when present won't open the browser to show the url"`
NoUI bool `long:"no-ui" description:"when present, only the swagger spec will be served"`
Flatten bool `long:"flatten" description:"when present, flatten the swagger spec before serving it"`
Port int `long:"port" short:"p" description:"the port to serve this site" env:"PORT"`

View file

@ -163,7 +163,7 @@ DECLS:
return
}
func (d *entityDecl) OperationIDS() (result []string) {
func (d *entityDecl) OperationIDs() (result []string) {
if d == nil || d.Comments == nil {
return nil
}
@ -281,7 +281,6 @@ func (s *scanCtx) FindDecl(pkgPath, name string) (*entityDecl, bool) {
}
return decl, true
}
}
}
}
@ -399,7 +398,7 @@ func (s *scanCtx) FindEnumValues(pkg *packages.Package, enumName string) (list [
}
for i, doc := range vs.Doc.List {
if doc.Text != "" {
var text = strings.TrimPrefix(doc.Text, "//")
text := strings.TrimPrefix(doc.Text, "//")
desc.WriteString(text)
if i < docListLen-1 {
desc.WriteString(" ")
@ -419,10 +418,7 @@ func (s *scanCtx) FindEnumValues(pkg *packages.Package, enumName string) (list [
return list, descList, true
}
func newTypeIndex(pkgs []*packages.Package,
excludeDeps bool, includeTags, excludeTags map[string]bool,
includePkgs, excludePkgs []string) (*typeIndex, error) {
func newTypeIndex(pkgs []*packages.Package, excludeDeps bool, includeTags, excludeTags map[string]bool, includePkgs, excludePkgs []string) (*typeIndex, error) {
ac := &typeIndex{
AllPackages: make(map[string]*packages.Package),
Models: make(map[*ast.Ident]*entityDecl),

View file

@ -29,10 +29,10 @@ func (o *operationsBuilder) Build(tgt *spec.Paths) error {
sp.setDescription = func(lines []string) { op.Description = joinDropLast(lines) }
if err := sp.Parse(o.path.Remaining); err != nil {
return fmt.Errorf("operation (%s): %v", op.ID, err)
return fmt.Errorf("operation (%s): %w", op.ID, err)
}
if err := sp.UnmarshalSpec(op.UnmarshalJSON); err != nil {
return fmt.Errorf("operation (%s): %v", op.ID, err)
return fmt.Errorf("operation (%s): %w", op.ID, err)
}
if tgt.Paths == nil {

View file

@ -8,8 +8,6 @@ import (
"golang.org/x/tools/go/ast/astutil"
"github.com/pkg/errors"
"github.com/go-openapi/spec"
)
@ -117,6 +115,7 @@ func (sv paramValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv paramValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
@ -143,6 +142,7 @@ func (sv itemsValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv itemsValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
@ -168,12 +168,11 @@ type parameterBuilder struct {
}
func (p *parameterBuilder) Build(operations map[string]*spec.Operation) error {
// check if there is a swagger:parameters tag that is followed by one or more words,
// these words are the ids of the operations this parameter struct applies to
// once type name is found convert it to a schema, by looking up the schema in the
// parameters dictionary that got passed into this parse method
for _, opid := range p.decl.OperationIDS() {
for _, opid := range p.decl.OperationIDs() {
operation, ok := operations[opid]
if !ok {
operation = new(spec.Operation)
@ -210,10 +209,10 @@ func (p *parameterBuilder) buildFromType(otpe types.Type, op *spec.Operation, se
}
return p.buildFromStruct(p.decl, stpe, op, seen)
default:
return errors.Errorf("unhandled type (%T): %s", stpe, o.Type().Underlying().String())
return fmt.Errorf("unhandled type (%T): %s", stpe, o.Type().Underlying().String())
}
default:
return errors.Errorf("unhandled type (%T): %s", otpe, tpe.String())
return fmt.Errorf("unhandled type (%T): %s", otpe, tpe.String())
}
}
@ -279,9 +278,9 @@ func (p *parameterBuilder) buildFromField(fld *types.Var, tpe types.Type, typabl
p.postDecls = append(p.postDecls, sb.postDecls...)
return nil
}
return errors.Errorf("unable to find package and source file for: %s", ftpe.String())
return fmt.Errorf("unable to find package and source file for: %s", ftpe.String())
default:
return errors.Errorf("unknown type for %s: %T", fld.String(), fld.Type())
return fmt.Errorf("unknown type for %s: %T", fld.String(), fld.Type())
}
}

View file

@ -2,8 +2,10 @@ package codescan
import (
"encoding/json"
"errors"
"fmt"
"go/ast"
"log"
"reflect"
"regexp"
"strconv"
@ -11,7 +13,6 @@ import (
"github.com/go-openapi/loads/fmts"
"github.com/go-openapi/spec"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
@ -1466,7 +1467,7 @@ func (ss *setOpResponses) Parse(lines []string) error {
return nil
}
func parseEnum(val string, s *spec.SimpleSchema) []interface{} {
func parseEnumOld(val string, s *spec.SimpleSchema) []interface{} {
list := strings.Split(val, ",")
interfaceSlice := make([]interface{}, len(list))
for i, d := range list {
@ -1481,6 +1482,35 @@ func parseEnum(val string, s *spec.SimpleSchema) []interface{} {
return interfaceSlice
}
func parseEnum(val string, s *spec.SimpleSchema) []interface{} {
// obtain the raw elements of the list to latter process them with the parseValueFromSchema
var rawElements []json.RawMessage
if err := json.Unmarshal([]byte(val), &rawElements); err != nil {
log.Print("WARNING: item list for enum is not a valid JSON array, using the old deprecated format")
return parseEnumOld(val, s)
}
interfaceSlice := make([]interface{}, len(rawElements))
for i, d := range rawElements {
ds, err := strconv.Unquote(string(d))
if err != nil {
ds = string(d)
}
v, err := parseValueFromSchema(ds, s)
if err != nil {
interfaceSlice[i] = ds
continue
}
interfaceSlice[i] = v
}
return interfaceSlice
}
// AlphaChars used when parsing for Vendor Extensions
const AlphaChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

View file

@ -1,6 +1,3 @@
//go:build go1.19
// +build go1.19
package codescan
import (

View file

@ -1,42 +0,0 @@
//go:build !go1.19
// +build !go1.19
package codescan
import "strings"
// a shared function that can be used to split given headers
// into a title and description
func collectScannerTitleDescription(headers []string) (title, desc []string) {
hdrs := cleanupScannerLines(headers, rxUncommentHeaders, nil)
idx := -1
for i, line := range hdrs {
if strings.TrimSpace(line) == "" {
idx = i
break
}
}
if idx > -1 {
title = hdrs[:idx]
if len(hdrs) > idx+1 {
desc = hdrs[idx+1:]
} else {
desc = nil
}
return
}
if len(hdrs) > 0 {
line := hdrs[0]
if rxPunctuationEnd.MatchString(line) {
title = []string{line}
desc = hdrs[1:]
} else {
desc = hdrs
}
}
return
}

View file

@ -1,16 +1,14 @@
package codescan
import (
"errors"
"fmt"
"go/ast"
"go/types"
"strings"
"github.com/pkg/errors"
"golang.org/x/tools/go/ast/astutil"
"github.com/go-openapi/spec"
"golang.org/x/tools/go/ast/astutil"
)
type responseTypable struct {
@ -97,22 +95,50 @@ func (sv headerValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv headerValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
}
func (sv headerValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val }
func (sv headerValidations) SetMinItems(val int64) { sv.current.MinItems = &val }
func (sv headerValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val }
func (sv headerValidations) SetMinLength(val int64) { sv.current.MinLength = &val }
func (sv headerValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val }
func (sv headerValidations) SetPattern(val string) { sv.current.Pattern = val }
func (sv headerValidations) SetUnique(val bool) { sv.current.UniqueItems = val }
func (sv headerValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
func (sv headerValidations) SetMultipleOf(val float64) {
sv.current.MultipleOf = &val
}
func (sv headerValidations) SetMinItems(val int64) {
sv.current.MinItems = &val
}
func (sv headerValidations) SetMaxItems(val int64) {
sv.current.MaxItems = &val
}
func (sv headerValidations) SetMinLength(val int64) {
sv.current.MinLength = &val
}
func (sv headerValidations) SetMaxLength(val int64) {
sv.current.MaxLength = &val
}
func (sv headerValidations) SetPattern(val string) {
sv.current.Pattern = val
}
func (sv headerValidations) SetUnique(val bool) {
sv.current.UniqueItems = val
}
func (sv headerValidations) SetCollectionFormat(val string) {
sv.current.CollectionFormat = val
}
func (sv headerValidations) SetEnum(val string) {
sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
}
func (sv headerValidations) SetDefault(val interface{}) { sv.current.Default = val }
func (sv headerValidations) SetExample(val interface{}) { sv.current.Example = val }
type responseBuilder struct {
@ -215,9 +241,9 @@ func (r *responseBuilder) buildFromField(fld *types.Var, tpe types.Type, typable
r.postDecls = append(r.postDecls, sb.postDecls...)
return nil
}
return errors.Errorf("unable to find package and source file for: %s", ftpe.String())
return fmt.Errorf("unable to find package and source file for: %s", ftpe.String())
default:
return errors.Errorf("unknown type for %s: %T", fld.String(), fld.Type())
return fmt.Errorf("unknown type for %s: %T", fld.String(), fld.Type())
}
}
@ -256,7 +282,7 @@ func (r *responseBuilder) buildFromType(otpe types.Type, resp *spec.Response, se
r.postDecls = append(r.postDecls, sb.postDecls...)
return nil
}
return errors.Errorf("responses can only be structs, did you mean for %s to be the response body?", otpe.String())
return fmt.Errorf("responses can only be structs, did you mean for %s to be the response body?", otpe.String())
}
default:
return errors.New("anonymous types are currently not supported for responses")

View file

@ -58,7 +58,6 @@ type routesBuilder struct {
}
func (r *routesBuilder) Build(tgt *spec.Paths) error {
pthObj := tgt.Paths[r.route.Path]
op := setPathOperation(
r.route.Method, r.route.ID,
@ -82,7 +81,7 @@ func (r *routesBuilder) Build(tgt *spec.Paths) error {
newMultiLineTagParser("Extensions", newSetExtensions(opExtensionsSetter(op)), true),
}
if err := sp.Parse(r.route.Remaining); err != nil {
return fmt.Errorf("operation (%s): %v", op.ID, err)
return fmt.Errorf("operation (%s): %w", op.ID, err)
}
if tgt.Paths == nil {

View file

@ -2,6 +2,7 @@ package codescan
import (
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/importer"
@ -15,7 +16,6 @@ import (
"golang.org/x/tools/go/ast/astutil"
"github.com/go-openapi/spec"
"github.com/pkg/errors"
)
func addExtension(ve *spec.VendorExtensible, key string, value interface{}) {
@ -92,6 +92,7 @@ func (sv schemaValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv schemaValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
@ -358,9 +359,12 @@ func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error
return nil
}
if s.decl.Spec.Assign.IsValid() {
return s.buildFromType(titpe.Underlying(), tgt)
}
switch utitpe := tpe.Underlying().(type) {
case *types.Struct:
if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
if decl.Type.Obj().Pkg().Path() == "time" && decl.Type.Obj().Name() == "Time" {
tgt.Typed("string", "date-time")
@ -892,7 +896,7 @@ func (s *schemaBuilder) buildAllOf(tpe types.Type, schema *spec.Schema) error {
}
return s.buildFromStruct(decl, utpe, schema, make(map[string]string))
}
return errors.Errorf("can't find source file for struct: %s", ftpe.String())
return fmt.Errorf("can't find source file for struct: %s", ftpe.String())
case *types.Interface:
decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name())
if found {
@ -905,7 +909,7 @@ func (s *schemaBuilder) buildAllOf(tpe types.Type, schema *spec.Schema) error {
}
return s.buildFromInterface(decl, utpe, schema, make(map[string]string))
}
return errors.Errorf("can't find source file for interface: %s", ftpe.String())
return fmt.Errorf("can't find source file for interface: %s", ftpe.String())
default:
log.Printf("WARNING: can't figure out object type for allOf named type (%T): %v", ftpe, ftpe.Underlying())
return fmt.Errorf("unable to locate source file for allOf %s", utpe.String())
@ -929,13 +933,13 @@ func (s *schemaBuilder) buildEmbedded(tpe types.Type, schema *spec.Schema, seen
if found {
return s.buildFromStruct(decl, utpe, schema, seen)
}
return errors.Errorf("can't find source file for struct: %s", ftpe.String())
return fmt.Errorf("can't find source file for struct: %s", ftpe.String())
case *types.Interface:
decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name())
if found {
return s.buildFromInterface(decl, utpe, schema, seen)
}
return errors.Errorf("can't find source file for struct: %s", ftpe.String())
return fmt.Errorf("can't find source file for struct: %s", ftpe.String())
default:
log.Printf("WARNING: can't figure out object type for embedded named type (%T): %v", ftpe, ftpe.Underlying())
}

View file

@ -54,7 +54,7 @@ func (s *specBuilder) Build() (*spec.Swagger, error) {
return nil, err
}
if err := s.buildRespones(); err != nil {
if err := s.buildResponses(); err != nil {
return nil, err
}
@ -160,7 +160,7 @@ func (s *specBuilder) buildRoutes() error {
return nil
}
func (s *specBuilder) buildRespones() error {
func (s *specBuilder) buildResponses() error {
// build responses dictionary
for _, decl := range s.ctx.app.Responses {
rb := &responseBuilder{

View file

@ -11,7 +11,7 @@ var _bindata embed.FS
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0)
_ = fs.WalkDir(_bindata, "templates", func(path string, d fs.DirEntry, err error) error {
_ = fs.WalkDir(_bindata, "templates", func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}

View file

@ -1,6 +1,7 @@
package generator
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -53,7 +54,8 @@ func ReadConfig(fpath string) (*viper.Viper, error) {
v.SetConfigName(".swagger")
v.AddConfigPath(".")
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.UnsupportedConfigError); !ok && v.ConfigFileUsed() != "" {
var e viper.UnsupportedConfigError
if !errors.As(err, &e) && v.ConfigFileUsed() != "" {
return nil, err
}
}

View file

@ -57,6 +57,7 @@ var zeroes = map[string]string{
"strfmt.UUID3": "strfmt.UUID3(\"\")",
"strfmt.UUID4": "strfmt.UUID4(\"\")",
"strfmt.UUID5": "strfmt.UUID5(\"\")",
"strfmt.ULID": "strfmt.ULID(\"\")",
// "file": "runtime.File",
}
@ -165,6 +166,7 @@ var formatMapping = map[string]map[string]string{
"uuid3": "strfmt.UUID3",
"uuid4": "strfmt.UUID4",
"uuid5": "strfmt.UUID5",
"ulid": "strfmt.ULID",
// For file producers
"file": "runtime.File",
},

View file

@ -34,13 +34,11 @@ func (t *Repository) LoadPlugin(pluginPath string) error {
log.Printf("Attempting to load template plugin: %s", pluginPath)
p, err := plugin.Open(pluginPath)
if err != nil {
return err
}
f, err := p.Lookup("AddFuncs")
if err != nil {
return err
}

View file

@ -141,7 +141,7 @@ func (l *LanguageOpts) baseImport(tgt string) string {
// GoLangOpts for rendering items as golang code
func GoLangOpts() *LanguageOpts {
var goOtherReservedSuffixes = map[string]bool{
goOtherReservedSuffixes := map[string]bool{
// see:
// https://golang.org/src/go/build/syslist.go
// https://golang.org/doc/install/source#environment
@ -154,6 +154,7 @@ func GoLangOpts() *LanguageOpts {
"freebsd": true,
"hurd": true,
"illumos": true,
"ios": true,
"js": true,
"linux": true,
"nacl": true,
@ -172,6 +173,7 @@ func GoLangOpts() *LanguageOpts {
"armbe": true,
"arm64": true,
"arm64be": true,
"loong64": true,
"mips": true,
"mipsle": true,
"mips64": true,
@ -436,5 +438,4 @@ func checkPrefixAndFetchRelativePath(childpath string, parentpath string) (bool,
}
return false, ""
}

View file

@ -71,6 +71,10 @@ func mediaMime(orig string) string {
return strings.SplitN(orig, ";", 2)[0]
}
func mediaGoName(media string) string {
return pascalize(strings.ReplaceAll(media, "*", "Star"))
}
func mediaParameters(orig string) string {
parts := strings.SplitN(orig, ";", 2)
if len(parts) < 2 {

View file

@ -120,10 +120,9 @@ type definitionGenerator struct {
}
func (m *definitionGenerator) Generate() error {
mod, err := makeGenDefinition(m.Name, m.Target, m.Model, m.SpecDoc, m.opts)
if err != nil {
return fmt.Errorf("could not generate definitions for model %s on target %s: %v", m.Name, m.Target, err)
return fmt.Errorf("could not generate definitions for model %s on target %s: %w", m.Name, m.Target, err)
}
if m.opts.DumpData {
@ -133,7 +132,7 @@ func (m *definitionGenerator) Generate() error {
if m.opts.IncludeModel {
log.Println("including additional model")
if err := m.generateModel(mod); err != nil {
return fmt.Errorf("could not generate model: %v", err)
return fmt.Errorf("could not generate model: %w", err)
}
}
log.Println("generated model", m.Name)
@ -255,9 +254,10 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
StrictAdditionalProperties: opts.StrictAdditionalProperties,
WithXML: opts.WithXML,
StructTags: opts.StructTags,
WantsRootedErrorPath: opts.WantsRootedErrorPath,
}
if err := pg.makeGenSchema(); err != nil {
return nil, fmt.Errorf("could not generate schema for %s: %v", name, err)
return nil, fmt.Errorf("could not generate schema for %s: %w", name, err)
}
dsi, ok := di.Discriminators["#/definitions/"+name]
if ok {
@ -358,6 +358,7 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
"runtime": "github.com/go-openapi/runtime",
"swag": "github.com/go-openapi/swag",
"validate": "github.com/go-openapi/validate",
"strfmt": "github.com/go-openapi/strfmt",
}
return &GenDefinition{
@ -442,12 +443,12 @@ type schemaGenContext struct {
AdditionalProperty bool
Untyped bool
Named bool
RefHandled bool
IsVirtual bool
IsTuple bool
IncludeValidator bool
IncludeModel bool
StrictAdditionalProperties bool
WantsRootedErrorPath bool
WithXML bool
Index int
@ -473,6 +474,10 @@ type schemaGenContext struct {
// force to use container in inlined definitions (for deconflicting)
UseContainerInName bool
// indicates is the schema is part of a slice or a map
IsElem bool
// indicates is the schema is part of a struct
IsProperty bool
}
func (sg *schemaGenContext) NewSliceBranch(schema *spec.Schema) *schemaGenContext {
@ -500,6 +505,7 @@ func (sg *schemaGenContext) NewSliceBranch(schema *spec.Schema) *schemaGenContex
pg.ValueExpr = pg.ValueExpr + "[" + indexVar + "]"
pg.Schema = *schema
pg.Required = false
pg.IsElem = true
if sg.IsVirtual {
pg.TypeResolver = sg.TypeResolver.NewWithModelName(sg.TypeResolver.ModelName)
}
@ -566,6 +572,7 @@ func (sg *schemaGenContext) NewStructBranch(name string, schema spec.Schema) *sc
pg.Name = name
pg.ValueExpr = pg.ValueExpr + "." + pascalize(goName(&schema, name))
pg.Schema = schema
pg.IsProperty = true
for _, fn := range sg.Schema.Required {
if name == fn {
pg.Required = true
@ -621,6 +628,7 @@ func (sg *schemaGenContext) NewAdditionalProperty(schema spec.Schema) *schemaGen
if sg.Path != "" {
pg.Path = sg.Path + "+\".\"+" + pg.KeyVar
}
pg.IsElem = true
// propagates the special IsNullable override for maps of slices and
// maps of aliased types.
pg.GenSchema.IsMapNullOverride = sg.GenSchema.IsMapNullOverride
@ -680,7 +688,7 @@ func (sg *schemaGenContext) schemaValidations() sharedValidations {
// when readOnly or default is specified, this disables Required validation (Swagger-specific)
isRequired = false
if sg.Required {
log.Printf("warn: properties with a default value or readOnly should not be required [%s]", sg.Name)
log.Printf("warning: properties with a default value or readOnly should not be required [%s]", sg.Name)
}
}
@ -841,7 +849,7 @@ func (sg *schemaGenContext) buildProperties() error {
}
// set property name
var nm = filepath.Base(emprop.Schema.Ref.GetURL().Fragment)
nm := filepath.Base(emprop.Schema.Ref.GetURL().Fragment)
tr := sg.TypeResolver.NewWithModelName(goName(&emprop.Schema, swag.ToGoName(nm)))
ttpe, err := tr.ResolveSchema(sch, false, true)
@ -1228,7 +1236,13 @@ func (mt *mapStack) Dict() map[string]interface{} {
func (sg *schemaGenContext) buildAdditionalProperties() error {
if sg.Schema.AdditionalProperties == nil {
return nil
if sg.Schema.MinProperties == nil && sg.Schema.MaxProperties == nil {
return nil
}
// whenever there is a validation on min/max properties and no additionalProperties is defined,
// we imply additionalProperties: true (corresponds to jsonschema defaults).
sg.Schema.AdditionalProperties = &spec.SchemaOrBool{Allows: true}
}
addp := *sg.Schema.AdditionalProperties
@ -1256,7 +1270,9 @@ func (sg *schemaGenContext) buildAdditionalProperties() error {
sg.GenSchema.IsComplexObject = false
sg.GenSchema.IsMap = true
sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.Name+" additionalProperties")
if !sg.IsElem && !sg.IsProperty {
sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.Name+" additionalProperties")
}
cp := sg.NewAdditionalProperty(*addp.Schema)
cp.Name += "AdditionalProperties"
cp.Required = false
@ -1325,7 +1341,6 @@ func (sg *schemaGenContext) buildAdditionalProperties() error {
if err := comprop.makeGenSchema(); err != nil {
return err
}
sg.MergeResult(comprop, false)
sg.GenSchema.AdditionalProperties = &comprop.GenSchema
sg.GenSchema.AdditionalProperties.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]"
@ -1598,9 +1613,8 @@ func (sg *schemaGenContext) buildItems() error {
}
func (sg *schemaGenContext) buildAdditionalItems() error {
wantsAdditionalItems :=
sg.Schema.AdditionalItems != nil &&
(sg.Schema.AdditionalItems.Allows || sg.Schema.AdditionalItems.Schema != nil)
wantsAdditionalItems := sg.Schema.AdditionalItems != nil &&
(sg.Schema.AdditionalItems.Allows || sg.Schema.AdditionalItems.Schema != nil)
sg.GenSchema.HasAdditionalItems = wantsAdditionalItems
if wantsAdditionalItems {
@ -1672,8 +1686,7 @@ func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
// NOTE: this assumes that all $ref point to a definition,
// i.e. the spec is canonical, as guaranteed by minimal flattening.
//
// TODO: RefHandled is actually set nowhere
if sg.RefHandled || !sg.Named || sg.Schema.Ref.String() == "" {
if !sg.Named || sg.Schema.Ref.String() == "" {
return false, nil
}
debugLogAsJSON("short circuit named ref: %q", sg.Schema.Ref.String(), sg.Schema)
@ -1684,6 +1697,8 @@ func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
// check if the $ref points to a simple type or polymorphic (base) type.
//
// If this is the case, just realias this simple type, without creating a struct.
//
// In templates this case is identified by .IsSuperAlias = true
asch, era := analysis.Schema(analysis.SchemaOpts{
Root: sg.TypeResolver.Doc.Spec(),
BasePath: sg.TypeResolver.Doc.SpecFilePath(),
@ -1734,10 +1749,16 @@ func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
}
// Aliased object: use golang struct composition.
// Covers case of a type redefinition like:
// thistype:
// $ref: #/definitions/othertype
//
// This is rendered as a struct with type field, i.e. :
// Alias struct {
// AliasedType
// }
//
// In templates, the schema is composed like AllOf.
nullableOverride := sg.GenSchema.IsNullable
tpe := resolvedType{}
@ -1750,17 +1771,26 @@ func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
tpe.IsAnonymous = false
tpe.IsNullable = sg.TypeResolver.isNullable(&sg.Schema)
item := sg.NewCompositionBranch(sg.Schema, 0)
if err := item.makeGenSchema(); err != nil {
branch := sg.NewCompositionBranch(sg.Schema, 0)
if err := branch.makeGenSchema(); err != nil {
return true, err
}
sg.GenSchema.resolvedType = tpe
sg.GenSchema.IsNullable = sg.GenSchema.IsNullable || nullableOverride
// prevent format from bubbling up in composed type
item.GenSchema.IsCustomFormatter = false
branch.GenSchema.IsCustomFormatter = false
sg.MergeResult(branch, true)
tpx, ers := sg.TypeResolver.ResolveSchema(&sg.Schema, false, true)
if ers != nil {
return false, ers
}
// we don't know the actual validation status yet. So assume true,
// unless we can infer that no Validate() method will be present
branch.GenSchema.HasValidations = !tpx.IsInterface && !tpx.IsStream
sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, branch.GenSchema)
sg.MergeResult(item, true)
sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, item.GenSchema)
return true, nil
}
@ -1967,6 +1997,9 @@ func (sg *schemaGenContext) makeGenSchema() error {
sg.GenSchema.Default = sg.Schema.Default
sg.GenSchema.StructTags = sg.StructTags
sg.GenSchema.ExtraImports = make(map[string]string)
sg.GenSchema.WantsRootedErrorPath = sg.WantsRootedErrorPath
sg.GenSchema.IsElem = sg.IsElem
sg.GenSchema.IsProperty = sg.IsProperty
var err error
returns, err := sg.shortCircuitNamedRef()
@ -1974,6 +2007,7 @@ func (sg *schemaGenContext) makeGenSchema() error {
return err
}
if returns {
// short circuited on a resolved $ref
return nil
}
debugLogAsJSON("after short circuit named ref", sg.Schema)
@ -2035,6 +2069,8 @@ func (sg *schemaGenContext) makeGenSchema() error {
log.Printf("INFO: type %s is external, with inferred spec type %s, referred to as %s", sg.GenSchema.Name, sg.GenSchema.GoType, extType)
sg.GenSchema.GoType = extType
sg.GenSchema.AliasedType = extType
// short circuit schema building for external types
return nil
}
// TODO: case for embedded types as anonymous definitions
@ -2073,6 +2109,8 @@ func (sg *schemaGenContext) makeGenSchema() error {
sg.GenSchema.IsMap = prev.IsMap
sg.GenSchema.IsAdditionalProperties = prev.IsAdditionalProperties
sg.GenSchema.IsBaseType = sg.GenSchema.HasDiscriminator
sg.GenSchema.IsElem = prev.IsElem
sg.GenSchema.IsProperty = prev.IsProperty
debugLogAsJSON("gschema nnullable:IsNullable:%t,resolver.IsNullable:%t,nullableOverride:%t",
sg.GenSchema.IsNullable, otn, nullableOverride, sg.Schema)
@ -2114,5 +2152,6 @@ func (sg *schemaGenContext) makeGenSchema() error {
(gs.IsTuple || gs.IsComplexObject || gs.IsAdditionalProperties || (gs.IsPrimitive && gs.IsAliased && gs.IsCustomFormatter && !strings.Contains(gs.Zero(), `("`)))
debugLog("finished gen schema for %q", sg.Name)
return nil
}

View file

@ -18,7 +18,9 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"path/filepath"
"regexp"
"sort"
"strings"
@ -149,7 +151,6 @@ type operationGenerator struct {
// Generate a single operation
func (o *operationGenerator) Generate() error {
defaultImports := o.GenOpts.defaultImports()
apiPackage := o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.APIPackage, defaultOperationsTarget)
@ -164,6 +165,7 @@ func (o *operationGenerator) Generate() error {
Imports: imports,
DefaultScheme: o.DefaultScheme,
Doc: o.Doc,
PristineDefs: o.Doc.Pristine(),
Analyzed: o.Analyzed,
BasePath: o.BasePath,
GenOpts: o.GenOpts,
@ -223,7 +225,7 @@ type codeGenOpBuilder struct {
Target string
Operation spec.Operation
Doc *loads.Document
PristineDoc *loads.Document
PristineDefs *loads.Document
Analyzed *analysis.Spec
DefaultImports map[string]string
Imports map[string]string
@ -245,12 +247,24 @@ func paramMappings(params map[string]spec.Parameter) (map[string]map[string]stri
"header": make(map[string]string, len(params)),
"body": make(map[string]string, len(params)),
}
debugLog("paramMappings: map=%v", params)
// In order to avoid unstable generation, adopt same naming convention
// for all parameters with same name across locations.
seenIds := make(map[string]interface{}, len(params))
seenIDs := make(map[string]interface{}, len(params))
for id, p := range params {
if val, ok := seenIds[p.Name]; ok {
debugLog("paramMappings: params: id=%s, In=%q, Name=%q", id, p.In, p.Name)
// guard against possible validation failures and/or skipped issues
if _, found := idMapping[p.In]; !found {
log.Printf(`warning: parameter named %q has an invalid "in": %q. Skipped`, p.Name, p.In)
continue
}
if p.Name == "" {
log.Printf(`warning: unnamed parameter (%+v). Skipped`, p)
continue
}
if val, ok := seenIDs[p.Name]; ok {
previous := val.(struct{ id, in string })
idMapping[p.In][p.Name] = swag.ToGoName(id)
// rewrite the previously found one
@ -258,11 +272,11 @@ func paramMappings(params map[string]spec.Parameter) (map[string]map[string]stri
} else {
idMapping[p.In][p.Name] = swag.ToGoName(p.Name)
}
seenIds[strings.ToLower(idMapping[p.In][p.Name])] = struct{ id, in string }{id: id, in: p.In}
seenIDs[strings.ToLower(idMapping[p.In][p.Name])] = struct{ id, in string }{id: id, in: p.In}
}
// pick a deconflicted private name for timeout for this operation
timeoutName := renameTimeout(seenIds, "timeout")
timeoutName := renameTimeout(seenIDs, "timeout")
return idMapping, timeoutName
}
@ -272,12 +286,12 @@ func paramMappings(params map[string]spec.Parameter) (map[string]map[string]stri
//
// NOTE: this merely protects the timeout field in the client parameter struct,
// fields "Context" and "HTTPClient" remain exposed to name conflicts.
func renameTimeout(seenIds map[string]interface{}, timeoutName string) string {
if seenIds == nil {
func renameTimeout(seenIDs map[string]interface{}, timeoutName string) string {
if seenIDs == nil {
return timeoutName
}
current := strings.ToLower(timeoutName)
if _, ok := seenIds[current]; !ok {
if _, ok := seenIDs[current]; !ok {
return timeoutName
}
var next string
@ -297,7 +311,7 @@ func renameTimeout(seenIds map[string]interface{}, timeoutName string) string {
default:
next = timeoutName + "1"
}
return renameTimeout(seenIds, next)
return renameTimeout(seenIDs, next)
}
func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
@ -325,7 +339,6 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
for _, p := range paramsForOperation {
cp, err := b.MakeParameter(receiver, resolver, p, idMapping)
if err != nil {
return GenOperation{}, err
}
@ -417,10 +430,7 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
originalExtraSchemes := getExtraSchemes(operation.Extensions)
produces := producesOrDefault(operation.Produces, swsp.Produces, b.DefaultProduces)
sort.Strings(produces)
consumes := producesOrDefault(operation.Consumes, swsp.Consumes, b.DefaultConsumes)
sort.Strings(consumes)
var successResponse *GenResponse
for _, resp := range successResponses {
@ -718,7 +728,12 @@ func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver
b.Method, b.Path, param.Name, goName)
}
} else if len(idMapping) > 0 {
id = idMapping[param.In][param.Name]
id, ok = idMapping[param.In][param.Name]
if !ok {
// skipped parameter
return GenParameter{}, fmt.Errorf(`%s %s, %q has an invalid parameter definition`,
b.Method, b.Path, param.Name)
}
}
res := GenParameter{
@ -739,6 +754,16 @@ func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver
Extensions: param.Extensions,
}
if goCustomTag, ok := param.Extensions["x-go-custom-tag"]; ok {
customTag, ok := goCustomTag.(string)
if !ok {
return GenParameter{}, fmt.Errorf(`%s %s, parameter %q: "x-go-custom-tag" field must be a string, not a %T`,
b.Method, b.Path, param.Name, goCustomTag)
}
res.CustomTag = customTag
}
if param.In == "body" {
// Process parameters declared in body (i.e. have a Schema)
res.Required = param.Required
@ -964,7 +989,6 @@ func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) {
p.HasModelBodyMap = hasModelBodyMap
p.HasSimpleBodyMap = hasSimpleBodyMap
}
}
// makeSecuritySchemes produces a sorted list of security schemes for this operation
@ -1012,10 +1036,7 @@ func (b *codeGenOpBuilder) cloneSchema(schema *spec.Schema) *spec.Schema {
// This uses a deep clone the spec document to construct a type resolver which knows about definitions when the making of this operation started,
// and only these definitions. We are not interested in the "original spec", but in the already transformed spec.
func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *spec.Schema) (*typeResolver, *spec.Schema) {
if b.PristineDoc == nil {
b.PristineDoc = b.Doc.Pristine()
}
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.PristineDoc)
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.PristineDefs)
return rslv, b.cloneSchema(schema)
}
@ -1226,11 +1247,19 @@ func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) {
// conflict with "operations" package is handled separately
tag = renameOperationPackage(intersected, tag)
}
if matches := versionedPkgRex.FindStringSubmatch(tag); len(matches) > 2 {
// rename packages like "v1", "v2" ... as they hold a special meaning for go
tag = "version" + matches[2]
}
b.APIPackage = b.GenOpts.LanguageOpts.ManglePackageName(tag, b.APIPackage) // actual package name
b.APIPackageAlias = deconflictTag(intersected, b.APIPackage) // deconflicted import alias
return tag, intersected, len(filter) == 0 || len(filter) > 0 && len(intersected) > 0
}
var versionedPkgRex = regexp.MustCompile(`(?i)(v)([0-9]+)`)
func maxInt(a, b int) int {
if a > b {
return a
@ -1268,6 +1297,7 @@ func deconflictPkg(pkg string, renamer func(string) string) string {
case "tls", "http", "fmt", "strings", "log", "flags", "pflag", "json", "time":
return renamer(pkg)
}
return pkg
}

View file

@ -68,15 +68,21 @@ func DefaultSectionOpts(gen *GenOpts) {
FileName: "{{ (snakize (pascalize .Name)) }}.go",
},
}
if gen.IncludeCLi {
opts = append(opts, TemplateOpts{
sec.Models = opts
}
if len(sec.PostModels) == 0 && gen.IncludeCLi {
// For CLI, we need to postpone the generation of model-supporting source,
// in order for go imports to run properly in all cases.
opts := []TemplateOpts{
{
Name: "clidefinitionhook",
Source: "asset:cliModelcli",
Target: "{{ joinFilePath .Target (toPackagePath .CliPackage) }}",
FileName: "{{ (snakize (pascalize .Name)) }}_model.go",
})
},
}
sec.Models = opts
sec.PostModels = opts
}
if len(sec.Operations) == 0 {
@ -228,7 +234,6 @@ func DefaultSectionOpts(gen *GenOpts) {
Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
FileName: "auto_configure_{{ (snakize (pascalize .Name)) }}.go",
})
} else {
opts = append(opts, TemplateOpts{
Name: "configure",
@ -242,7 +247,6 @@ func DefaultSectionOpts(gen *GenOpts) {
}
}
gen.Sections = sec
}
// MarkdownOpts for rendering a spec as markdown
@ -255,6 +259,7 @@ func MarkdownOpts() *LanguageOpts {
// MarkdownSectionOpts for a given opts and output file.
func MarkdownSectionOpts(gen *GenOpts, output string) {
gen.Sections.Models = nil
gen.Sections.PostModels = nil
gen.Sections.OperationGroups = nil
gen.Sections.Operations = nil
gen.LanguageOpts = MarkdownOpts()
@ -284,6 +289,7 @@ type SectionOpts struct {
Operations []TemplateOpts `mapstructure:"operations"`
OperationGroups []TemplateOpts `mapstructure:"operation_groups"`
Models []TemplateOpts `mapstructure:"models"`
PostModels []TemplateOpts `mapstructure:"post_models"`
}
// GenOptsCommon the options for the generator
@ -344,6 +350,7 @@ type GenOptsCommon struct {
AllowEnumCI bool
StrictResponders bool
AcceptDefinitionsOnly bool
WantsRootedErrorPath bool
templates *Repository // a shallow clone of the global template repository
}
@ -356,7 +363,7 @@ func (g *GenOpts) CheckOpts() error {
if !filepath.IsAbs(g.Target) {
if _, err := filepath.Abs(g.Target); err != nil {
return fmt.Errorf("could not locate target %s: %v", g.Target, err)
return fmt.Errorf("could not locate target %s: %w", g.Target, err)
}
}
@ -602,11 +609,11 @@ func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
}
content, err := os.ReadFile(templateFile)
if err != nil {
return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err)
return nil, fmt.Errorf("error while opening %s template file: %w", templateFile, err)
}
tt, err := template.New(t.Source).Funcs(FuncMapFunc(g.LanguageOpts)).Parse(string(content))
if err != nil {
return nil, fmt.Errorf("template parsing failed on template %s: %v", t.Name, err)
return nil, fmt.Errorf("template parsing failed on template %s: %w", t.Name, err)
}
templ = tt
}
@ -617,7 +624,7 @@ func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
var tBuf bytes.Buffer
if err := templ.Execute(&tBuf, data); err != nil {
return nil, fmt.Errorf("template execution failed for template %s: %v", t.Name, err)
return nil, fmt.Errorf("template execution failed for template %s: %w", t.Name, err)
}
log.Printf("executed template %s", t.Source)
@ -631,7 +638,7 @@ func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
dir, fname, err := g.location(t, data)
if err != nil {
return fmt.Errorf("failed to resolve template location for template %s: %v", t.Name, err)
return fmt.Errorf("failed to resolve template location for template %s: %w", t.Name, err)
}
if t.SkipExists && fileExists(dir, fname) {
@ -643,7 +650,7 @@ func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
log.Printf("creating generated file %q in %q as %s", fname, dir, t.Name)
content, err := g.render(t, data)
if err != nil {
return fmt.Errorf("failed rendering template data for %s: %v", t.Name, err)
return fmt.Errorf("failed rendering template data for %s: %w", t.Name, err)
}
if dir != "" {
@ -652,7 +659,7 @@ func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
debugLog("creating directory %q for \"%s\"", dir, t.Name)
// Directory settings consistent with file privileges.
// Environment's umask may alter this setup
if e := os.MkdirAll(dir, 0755); e != nil {
if e := os.MkdirAll(dir, 0o755); e != nil {
return e
}
}
@ -666,18 +673,18 @@ func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
formatted, err = g.LanguageOpts.FormatContent(filepath.Join(dir, fname), content)
if err != nil {
log.Printf("source formatting failed on template-generated source (%q for %s). Check that your template produces valid code", filepath.Join(dir, fname), t.Name)
writeerr = os.WriteFile(filepath.Join(dir, fname), content, 0644) // #nosec
writeerr = os.WriteFile(filepath.Join(dir, fname), content, 0o644) // #nosec
if writeerr != nil {
return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr)
return fmt.Errorf("failed to write (unformatted) file %q in %q: %w", fname, dir, writeerr)
}
log.Printf("unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!", fname)
return fmt.Errorf("source formatting on generated source %q failed: %v", t.Name, err)
return fmt.Errorf("source formatting on generated source %q failed: %w", t.Name, err)
}
}
writeerr = os.WriteFile(filepath.Join(dir, fname), formatted, 0644) // #nosec
writeerr = os.WriteFile(filepath.Join(dir, fname), formatted, 0o644) // #nosec
if writeerr != nil {
return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr)
return fmt.Errorf("failed to write file %q in %q: %w", fname, dir, writeerr)
}
return err
}
@ -713,6 +720,20 @@ func (g *GenOpts) renderApplication(app *GenApp) error {
return err
}
}
if len(g.Sections.PostModels) > 0 {
log.Printf("post-rendering from %d models", len(app.Models))
for _, templateToPin := range g.Sections.PostModels {
templateConfig := templateToPin
for _, modelToPin := range app.Models {
modelData := modelToPin
if err := g.write(&templateConfig, modelData); err != nil {
return err
}
}
}
}
return nil
}
@ -1069,7 +1090,7 @@ func dumpData(data interface{}) error {
if err != nil {
return err
}
fmt.Fprintln(os.Stdout, string(bb))
fmt.Fprintln(os.Stdout, string(bb)) // TODO(fred): not testable
return nil
}

View file

@ -41,8 +41,11 @@ func (g *GenOpts) validateAndFlattenSpec() (*loads.Document, error) {
if validationErrors != nil {
str := fmt.Sprintf("The swagger spec at %q is invalid against swagger specification %s. see errors :\n",
g.Spec, specDoc.Version())
for _, desc := range validationErrors.(*swaggererrors.CompositeError).Errors {
str += fmt.Sprintf("- %s\n", desc)
var cerr *swaggererrors.CompositeError
if errors.As(validationErrors, &cerr) {
for _, desc := range cerr.Errors {
str += fmt.Sprintf("- %s\n", desc)
}
}
return nil, errors.New(str)
}
@ -84,6 +87,16 @@ func (g *GenOpts) validateAndFlattenSpec() (*loads.Document, error) {
return nil, err
}
if g.FlattenOpts.Expand {
// for a similar reason as the one mentioned above for validate,
// schema expansion alters the internal doc cache in the spec.
// This nasty bug (in spec expander) affects circular references.
// So we need to reload the spec from a clone.
// Notice that since the spec inside the document has been modified, we should
// ensure that Pristine refreshes its row root document.
specDoc = specDoc.Pristine()
}
// yields the preprocessed spec document
return specDoc, nil
}
@ -229,7 +242,7 @@ func WithAutoXOrder(specPath string) string {
}
tmpFile := filepath.Join(tmpDir, filepath.Base(specPath))
if err := os.WriteFile(tmpFile, out, 0600); err != nil {
if err := os.WriteFile(tmpFile, out, 0o600); err != nil {
panic(err)
}
return tmpFile

View file

@ -20,6 +20,7 @@ import (
type GenCommon struct {
Copyright string
TargetImportPath string
RootedErrorPath bool // wants array and map types to have a path corresponding to their type in reported errors
}
// GenDefinition contains all the properties to generate a
@ -85,6 +86,8 @@ type GenSchema struct {
HasBaseType bool
IsSubType bool
IsExported bool
IsElem bool // IsElem gives some context when the schema is part of an array or a map
IsProperty bool // IsProperty gives some context when the schema is a property of an object
DiscriminatorField string
DiscriminatorValue string
Discriminates map[string]string
@ -96,6 +99,7 @@ type GenSchema struct {
StructTags []string
ExtraImports map[string]string // non-standard imports detected when using external types
ExternalDocs *spec.ExternalDocumentation
WantsRootedErrorPath bool
}
func (g GenSchema) renderMarshalTag() string {
@ -361,6 +365,8 @@ type GenParameter struct {
CollectionFormat string
CustomTag string
Child *GenItems
Parent *GenItems
@ -514,6 +520,8 @@ type GenOperationGroup struct {
RootPackage string
GenOpts *GenOpts
PackageAlias string
ClientOptions *GenClientOptions
}
// GenOperationGroups is a sorted collection of operation groups
@ -801,3 +809,10 @@ type GenSecurityRequirements []GenSecurityRequirement
func (g GenSecurityRequirements) Len() int { return len(g) }
func (g GenSecurityRequirements) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
func (g GenSecurityRequirements) Less(i, j int) bool { return g[i].Name < g[j].Name }
// GenClientOptions holds extra pieces of information
// to generate a client.
type GenClientOptions struct {
ProducesMediaTypes []string // filled with all producers if any method as more than 1
ConsumesMediaTypes []string // filled with all consumers if any method as more than 1
}

View file

@ -58,6 +58,9 @@ func GenerateMarkdown(output string, modelNames, operationIDs []string, opts *Ge
if err := opts.EnsureDefaults(); err != nil {
return err
}
if opts.Target != "" && opts.Target != "." {
output = filepath.Join(opts.Target, output)
}
MarkdownSectionOpts(opts, output)
generator, err := newAppGenerator("", modelNames, operationIDs, opts)
@ -184,7 +187,7 @@ func (a *appGenerator) Generate() error {
}
// optional OperationGroups templates generation
if err := a.GenOpts.renderOperationGroup(&opg); err != nil {
return fmt.Errorf("error while rendering operation group: %v", err)
return fmt.Errorf("error while rendering operation group: %w", err)
}
}
}
@ -217,11 +220,13 @@ func (a *appGenerator) GenerateSupport(ap *GenApp) error {
app.DefaultImports[pkgAlias] = serverPath
app.ServerPackageAlias = pkgAlias
// add client import for cli generation
clientPath := path.Join(baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.ClientPackage, defaultClientTarget))
clientPkgAlias := importAlias(clientPath)
app.DefaultImports[clientPkgAlias] = clientPath
if a.GenOpts.IncludeCLi { // no need to add this import when there is no CLI
// add client import for cli generation
clientPath := path.Join(baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.ClientPackage, defaultClientTarget))
clientPkgAlias := importAlias(clientPath)
app.DefaultImports[clientPkgAlias] = clientPath
}
return a.GenOpts.renderApplication(app)
}
@ -262,9 +267,11 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
imports := make(map[string]string, 50)
alias := deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.OperationsPackage, defaultOperationsTarget), renameAPIPackage)
imports[alias] = path.Join(
baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget))
if !a.GenOpts.IsClient { // we don't want to inject this import for clients
imports[alias] = path.Join(
baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget))
}
implAlias := ""
if a.GenOpts.ImplementationPackage != "" {
@ -284,7 +291,7 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
a.GenOpts,
)
if err != nil {
return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %v", mn, err)
return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %w", mn, err)
}
if model != nil {
if !model.External {
@ -304,6 +311,10 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
log.Printf("planning operations (found: %d)", len(a.Operations))
genOps := make(GenOperations, 0, len(a.Operations))
consumesIndex := make(map[string][]string)
producesIndex := make(map[string][]string)
pristineDoc := a.SpecDoc.Pristine()
for operationName, opp := range a.Operations {
o := opp.Op
o.ID = operationName
@ -316,6 +327,7 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
Imports: imports,
DefaultScheme: a.DefaultScheme,
Doc: a.SpecDoc,
PristineDefs: pristineDoc,
Analyzed: a.Analyzed,
BasePath: a.SpecDoc.BasePath(),
GenOpts: a.GenOpts,
@ -355,7 +367,18 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
op.ReceiverName = receiver
op.Tags = tags // ordered tags for this operation, possibly filtered by CLI params
genOps = append(genOps, op)
allConsumes := pruneEmpty(op.ConsumesMediaTypes)
if bldr.DefaultConsumes != "" {
allConsumes = append(allConsumes, bldr.DefaultConsumes)
}
consumesIndex[bldr.Name] = allConsumes
allProduces := pruneEmpty(op.ProducesMediaTypes)
if bldr.DefaultProduces != "" {
allProduces = append(allProduces, bldr.DefaultProduces)
}
producesIndex[bldr.Name] = allProduces
if !a.GenOpts.SkipTagPackages && tag != "" {
importPath := filepath.ToSlash(
@ -364,8 +387,19 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget),
a.GenOpts.LanguageOpts.ManglePackageName(bldr.APIPackage, defaultOperationsTarget),
))
// check for possible conflicts that requires import aliasing
pth, aliasUsed := defaultImports[bldr.APIPackageAlias]
if (a.GenOpts.IsClient && bldr.APIPackageAlias == a.GenOpts.ClientPackage) || // we don't want import to shadow the current package
(a.GenOpts.IncludeCLi && bldr.APIPackageAlias == a.GenOpts.CliPackage) ||
(aliasUsed && pth != importPath) { // was already imported with a different target
op.PackageAlias = renameOperationPackage(tags, bldr.APIPackageAlias)
bldr.APIPackageAlias = op.PackageAlias
}
defaultImports[bldr.APIPackageAlias] = importPath
}
genOps = append(genOps, op)
}
sort.Sort(genOps)
@ -378,8 +412,12 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
opGroups := make(GenOperationGroups, 0, len(opsGroupedByPackage))
for k, v := range opsGroupedByPackage {
log.Printf("operations for package packages %q (found: %d)", k, len(v))
log.Printf("operations for package %q (found: %d)", k, len(v))
sort.Sort(v)
consumesInGroup := make([]string, 0, 2)
producesInGroup := make([]string, 0, 2)
// trim duplicate extra schemas within the same package
vv := make(GenOperations, 0, len(v))
seenExtraSchema := make(map[string]bool)
@ -393,6 +431,9 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
}
op.ExtraSchemas = uniqueExtraSchemas
vv = append(vv, op)
consumesInGroup = concatUnique(consumesInGroup, consumesIndex[op.Name])
producesInGroup = concatUnique(producesInGroup, producesIndex[op.Name])
}
var pkg string
if len(vv) > 0 {
@ -414,6 +455,19 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
RootPackage: a.APIPackage,
GenOpts: a.GenOpts,
}
if a.GenOpts.IsClient {
// generating extra options to switch media type in client
if len(consumesInGroup) > 1 || len(producesInGroup) > 1 {
sort.Strings(producesInGroup)
sort.Strings(consumesInGroup)
options := &GenClientOptions{
ProducesMediaTypes: producesInGroup,
ConsumesMediaTypes: consumesInGroup,
}
opGroup.ClientOptions = options
}
}
opGroups = append(opGroups, opGroup)
}
sort.Sort(opGroups)

View file

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"log"
"math"
"os"
"path"
@ -16,8 +17,6 @@ import (
"text/template/parse"
"unicode"
"log"
"github.com/Masterminds/sprig/v3"
"github.com/go-openapi/inflect"
"github.com/go-openapi/runtime"
@ -94,6 +93,7 @@ func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
"inspect": pretty.Sprint,
"cleanPath": path.Clean,
"mediaTypeName": mediaMime,
"mediaGoName": mediaGoName,
"arrayInitializer": lang.arrayInitializer,
"hasPrefix": strings.HasPrefix,
"stringContains": strings.Contains,
@ -134,9 +134,55 @@ func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
},
"docCollectionFormat": resolvedDocCollectionFormat,
"trimSpace": strings.TrimSpace,
"mdBlock": markdownBlock, // markdown block
"httpStatus": httpStatus,
"cleanupEnumVariant": cleanupEnumVariant,
"gt0": gt0,
"path": errorPath,
"cmdName": func(in interface{}) (string, error) {
// builds the name of a CLI command for a single operation
op, isOperation := in.(GenOperation)
if !isOperation {
ptr, ok := in.(*GenOperation)
if !ok {
return "", fmt.Errorf("cmdName should be called on a GenOperation, but got: %T", in)
}
op = *ptr
}
name := "Operation" + pascalize(op.Package) + pascalize(op.Name) + "Cmd"
return name, nil // TODO
},
"cmdGroupName": func(in interface{}) (string, error) {
// builds the name of a group of CLI commands
opGroup, ok := in.(GenOperationGroup)
if !ok {
return "", fmt.Errorf("cmdGroupName should be called on a GenOperationGroup, but got: %T", in)
}
name := "GroupOfOperations" + pascalize(opGroup.Name) + "Cmd"
return name, nil // TODO
},
"flagNameVar": func(in string) string {
// builds a flag name variable in CLI commands
return fmt.Sprintf("flag%sName", pascalize(in))
},
"flagValueVar": func(in string) string {
// builds a flag value variable in CLI commands
return fmt.Sprintf("flag%sValue", pascalize(in))
},
"flagDefaultVar": func(in string) string {
// builds a flag default value variable in CLI commands
return fmt.Sprintf("flag%sDefault", pascalize(in))
},
"flagModelVar": func(in string) string {
// builds a flag model variable in CLI commands
return fmt.Sprintf("flag%sModel", pascalize(in))
},
"flagDescriptionVar": func(in string) string {
// builds a flag description variable in CLI commands
return fmt.Sprintf("flag%sDescription", pascalize(in))
},
}
for k, v := range extra {
@ -327,7 +373,6 @@ func (t *Repository) ShallowClone() *Repository {
// LoadDefaults will load the embedded templates
func (t *Repository) LoadDefaults() {
for name, asset := range assets {
if err := t.addFile(name, string(asset), true); err != nil {
log.Fatal(err)
@ -337,26 +382,27 @@ func (t *Repository) LoadDefaults() {
// LoadDir will walk the specified path and add each .gotmpl file it finds to the repository
func (t *Repository) LoadDir(templatePath string) error {
err := filepath.Walk(templatePath, func(path string, info os.FileInfo, err error) error {
err := filepath.Walk(templatePath, func(path string, _ os.FileInfo, err error) error {
if strings.HasSuffix(path, ".gotmpl") {
if assetName, e := filepath.Rel(templatePath, path); e == nil {
if data, e := os.ReadFile(path); e == nil {
if ee := t.AddFile(assetName, string(data)); ee != nil {
return fmt.Errorf("could not add template: %v", ee)
return fmt.Errorf("could not add template: %w", ee)
}
}
// Non-readable files are skipped
}
}
if err != nil {
return err
}
// Non-template files are skipped
return nil
})
if err != nil {
return fmt.Errorf("could not complete template processing in directory \"%s\": %v", templatePath, err)
return fmt.Errorf("could not complete template processing in directory \"%s\": %w", templatePath, err)
}
return nil
}
@ -392,9 +438,8 @@ func (t *Repository) addFile(name, data string, allowOverride bool) error {
name = swag.ToJSONName(strings.TrimSuffix(name, ".gotmpl"))
templ, err := template.New(name).Funcs(t.funcs).Parse(data)
if err != nil {
return fmt.Errorf("failed to load template %s: %v", name, err)
return fmt.Errorf("failed to load template %s: %w", name, err)
}
// check if any protected templates are defined
@ -441,7 +486,6 @@ func (t *Repository) SetAllowOverride(value bool) {
}
func findDependencies(n parse.Node) []string {
var deps []string
depMap := make(map[string]bool)
@ -491,7 +535,6 @@ func findDependencies(n parse.Node) []string {
}
return deps
}
func (t *Repository) flattenDependencies(templ *template.Template, dependencies map[string]bool) map[string]bool {
@ -516,11 +559,9 @@ func (t *Repository) flattenDependencies(templ *template.Template, dependencies
}
return dependencies
}
func (t *Repository) addDependencies(templ *template.Template) (*template.Template, error) {
name := templ.Name()
deps := t.flattenDependencies(templ, nil)
@ -545,9 +586,8 @@ func (t *Repository) addDependencies(templ *template.Template) (*template.Templa
// Add it to the parse tree
templ, err = templ.AddParseTree(dep, tt.Tree)
if err != nil {
return templ, fmt.Errorf("dependency error: %v", err)
return templ, fmt.Errorf("dependency error: %w", err)
}
}
@ -576,7 +616,6 @@ func (t *Repository) DumpTemplates() {
fmt.Fprintf(buf, "Defined in `%s`\n", t.files[name])
if deps := findDependencies(templ.Tree.Root); len(deps) > 0 {
fmt.Fprintf(buf, "####requires \n - %v\n\n\n", strings.Join(deps, "\n - "))
}
fmt.Fprintln(buf, "\n---")
@ -853,3 +892,99 @@ func gt0(in *int64) bool {
// with a pointer
return in != nil && *in > 0
}
func errorPath(in interface{}) (string, error) {
// For schemas:
// errorPath returns an empty string litteral when the schema path is empty.
// It provides a shorthand for template statements such as:
// {{ if .Path }}{{ .Path }}{{ else }}" "{{ end }},
// which becomes {{ path . }}
//
// When called for a GenParameter, GenResponse or GenOperation object, it just
// returns Path.
//
// Extra behavior for schemas, when the generation option RootedErroPath is enabled:
// In the case of arrays with an empty path, it adds the type name as the path "root",
// so consumers of reported errors get an idea of the originator.
var pth string
rooted := func(schema GenSchema) string {
if schema.WantsRootedErrorPath && schema.Path == "" && (schema.IsArray || schema.IsMap) {
return `"[` + schema.Name + `]"`
}
return schema.Path
}
switch schema := in.(type) {
case GenSchema:
pth = rooted(schema)
case *GenSchema:
if schema == nil {
break
}
pth = rooted(*schema)
case GenDefinition:
pth = rooted(schema.GenSchema)
case *GenDefinition:
if schema == nil {
break
}
pth = rooted(schema.GenSchema)
case GenParameter:
pth = schema.Path
// unchanged Path if called with other types
case *GenParameter:
if schema == nil {
break
}
pth = schema.Path
case GenResponse:
pth = schema.Path
case *GenResponse:
if schema == nil {
break
}
pth = schema.Path
case GenOperation:
pth = schema.Path
case *GenOperation:
if schema == nil {
break
}
pth = schema.Path
case GenItems:
pth = schema.Path
case *GenItems:
if schema == nil {
break
}
pth = schema.Path
case GenHeader:
pth = schema.Path
case *GenHeader:
if schema == nil {
break
}
pth = schema.Path
default:
return "", fmt.Errorf("errorPath should be called with GenSchema or GenDefinition, but got %T", schema)
}
if pth == "" {
return `""`, nil
}
return pth, nil
}
const mdNewLine = "</br>"
var mdNewLineReplacer = strings.NewReplacer("\r\n", mdNewLine, "\n", mdNewLine, "\r", mdNewLine)
func markdownBlock(in string) string {
in = strings.TrimSpace(in)
return mdNewLineReplacer.Replace(in)
}

View file

@ -1,42 +1,45 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
package {{ .GenOpts.CliPackage }}
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
{{ imports .DefaultImports }}
{{ imports .Imports }}
"log"
"os"
"path"
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
httptransport "github.com/go-openapi/runtime/client"
homedir "github.com/mitchellh/go-homedir"
httptransport "github.com/go-openapi/runtime/client"
"github.com/spf13/cobra"
"github.com/spf13/viper"
{{ imports .DefaultImports }}
{{ imports .Imports }}
)
// debug flag indicating that cli should output debug logs
var debug bool
// config file location
var configFile string
// dry run flag
var dryRun bool
var (
// debug flag indicating that cli should output debug logs
debug bool
// name of the executable
var exeName string = filepath.Base(os.Args[0])
// config file location
configFile string
// dry run flag
dryRun bool
// name of the executable
exeName = filepath.Base(os.Args[0])
)
// logDebugf writes debug log to stdout
func logDebugf(format string, v ...interface{}) {
if !debug{
return
}
log.Printf(format, v...)
if !debug{
return
}
log.Printf(format, v...)
}
{{/*TODO: make this a swagger cli option*/}}
@ -44,199 +47,240 @@ func logDebugf(format string, v ...interface{}) {
var maxDepth int = 5
// makeClient constructs a client object
func makeClient(cmd *cobra.Command, args []string) (*client.{{ pascalize .Name }}, error) {
hostname := viper.GetString("hostname")
viper.SetDefault("base_path", client.DefaultBasePath)
basePath := viper.GetString("base_path")
scheme := viper.GetString("scheme")
func makeClient(cmd *cobra.Command, _ []string) (*client.{{ pascalize .Name }}, error) {
hostname := viper.GetString("hostname")
viper.SetDefault("base_path", client.DefaultBasePath)
basePath := viper.GetString("base_path")
scheme := viper.GetString("scheme")
r := httptransport.New(hostname, basePath, []string{scheme})
r.SetDebug(debug)
r := httptransport.New(hostname, basePath, []string{scheme})
r.SetDebug(debug)
{{- /* user might define custom mediatype xxx/json and there is no registered ones to handle. */}}
// set custom producer and consumer to use the default ones
{{ range .Consumes }}
{{ range .AllSerializers }}
{{- if stringContains .MediaType "json" }}
r.Consumers["{{ .MediaType }}"] = runtime.JSONConsumer()
{{- else }}
// warning: consumes {{ .MediaType }} is not supported by go-swagger cli yet
{{- end }}
{{- end }}
{{ end }}
{{ range .Produces }}
{{- range .AllSerializers }}
{{- if stringContains .MediaType "json" }}
r.Producers["{{ .MediaType }}"] = runtime.JSONProducer()
{{- else }}
// warning: produces {{ .MediaType }} is not supported by go-swagger cli yet
{{- end }}
{{- end }}
{{ end }}
{{- /* user might define custom mediatype xxx/json and there is no registered ones to handle. */}}
// set custom producer and consumer to use the default ones
{{ range .Consumes }}
{{ range .AllSerializers }}
{{- if stringContains .MediaType "json" }}
r.Consumers["{{ .MediaType }}"] = runtime.JSONConsumer()
{{- else }}
// warning: consumes {{ .MediaType }} is not supported by go-swagger cli yet
{{- end }}
{{- end }}
{{ end }}
{{ range .Produces }}
{{- range .AllSerializers }}
{{- if stringContains .MediaType "json" }}
r.Producers["{{ .MediaType }}"] = runtime.JSONProducer()
{{- else }}
// warning: produces {{ .MediaType }} is not supported by go-swagger cli yet
{{- end }}
{{- end }}
{{- end }}
{{- if .SecurityDefinitions }}
auth, err := makeAuthInfoWriter(cmd)
if err != nil {
return nil, err
}
r.DefaultAuthentication = auth
{{ end }}
appCli := client.New(r, strfmt.Default)
logDebugf("Server url: %v://%v", scheme, hostname)
return appCli, nil
{{- if .SecurityDefinitions }}
auth, err := makeAuthInfoWriter(cmd)
if err != nil {
return nil, err
}
r.DefaultAuthentication = auth
{{- end }}
appCli := client.New(r, strfmt.Default)
logDebugf("Server url: %v://%v", scheme, hostname)
return appCli, nil
}
// MakeRootCmd returns the root cmd
func MakeRootCmd() (*cobra.Command, error) {
cobra.OnInitialize(initViperConfigs)
// Use executable name as the command name
rootCmd := &cobra.Command{
Use: exeName,
}
{{/*note: viper binded flag value must be retrieved from viper rather than cmd*/}}
// register basic flags
rootCmd.PersistentFlags().String("hostname", client.DefaultHost, "hostname of the service")
viper.BindPFlag("hostname", rootCmd.PersistentFlags().Lookup("hostname"))
rootCmd.PersistentFlags().String("scheme", client.DefaultSchemes[0], fmt.Sprintf("Choose from: %v", client.DefaultSchemes))
viper.BindPFlag("scheme", rootCmd.PersistentFlags().Lookup("scheme"))
rootCmd.PersistentFlags().String("base-path", client.DefaultBasePath, fmt.Sprintf("For example: %v", client.DefaultBasePath))
viper.BindPFlag("base_path", rootCmd.PersistentFlags().Lookup("base-path"))
cobra.OnInitialize(initViperConfigs)
// configure debug flag
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "output debug logs")
// configure config location
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file path")
// configure dry run flag
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "do not send the request to server")
// Use executable name as the command name
rootCmd := &cobra.Command{
Use: exeName,
}
{{/*note: viper binded flag value must be retrieved from viper rather than cmd*/}}
// register basic flags
rootCmd.PersistentFlags().String("hostname", client.DefaultHost, "hostname of the service")
if err := viper.BindPFlag("hostname", rootCmd.PersistentFlags().Lookup("hostname")) ; err != nil {
return nil, err
}
rootCmd.PersistentFlags().String("scheme", client.DefaultSchemes[0], fmt.Sprintf("Choose from: %v", client.DefaultSchemes))
if err := viper.BindPFlag("scheme", rootCmd.PersistentFlags().Lookup("scheme")) ; err != nil {
return nil, err
}
rootCmd.PersistentFlags().String("base-path", client.DefaultBasePath, fmt.Sprintf("For example: %v", client.DefaultBasePath))
if err := viper.BindPFlag("base_path", rootCmd.PersistentFlags().Lookup("base-path")) ; err != nil {
return nil, err
}
// register security flags
{{- if .SecurityDefinitions }}
if err := registerAuthInoWriterFlags(rootCmd); err != nil{
return nil, err
}
{{- end }}
// add all operation groups
{{- range .OperationGroups -}}
{{- $operationGroupCmdVarName := printf "operationGroup%vCmd" (pascalize .Name) }}
{{ $operationGroupCmdVarName }}, err := makeOperationGroup{{ pascalize .Name }}Cmd()
if err != nil {
return nil, err
}
rootCmd.AddCommand({{ $operationGroupCmdVarName }})
{{ end }}
// configure debug flag
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "output debug logs")
// configure config location
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file path")
// configure dry run flag
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "do not send the request to server")
// add cobra completion
rootCmd.AddCommand(makeGenCompletionCmd())
// register security flags
{{- if .SecurityDefinitions }}
if err := registerAuthInoWriterFlags(rootCmd); err != nil{
return nil, err
}
{{- end }}
return rootCmd, nil
// add all operation groups
{{- range $index,$element := .OperationGroups }}
c{{ $index }}, err := make{{ cmdGroupName $element }}()
if err != nil {
return nil, err
}
rootCmd.AddCommand(c{{ $index}})
{{- end }}
// add cobra completion
rootCmd.AddCommand(makeGenCompletionCmd())
return rootCmd, nil
}
// initViperConfigs initialize viper config using config file in '$HOME/.config/<cli name>/config.<json|yaml...>'
// currently hostname, scheme and auth tokens can be specified in this config file.
func initViperConfigs() {
if configFile != "" {
// use user specified config file location
viper.SetConfigFile(configFile)
}else{
// look for default config
// Find home directory.
home, err := homedir.Dir()
cobra.CheckErr(err)
if configFile != "" {
// use user specified config file location
viper.SetConfigFile(configFile)
} else{
var (
configDir string
err error
)
// Search config in home directory with name ".cobra" (without extension).
viper.AddConfigPath(path.Join(home, ".config", exeName))
viper.SetConfigName("config")
}
// look for default config (OS-specific, e.g. ".config" on linux)
configDir, err = os.UserConfigDir()
if err != nil {
// fallback and try finding the home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
configDir = path.Join(home, ".config")
}
if err := viper.ReadInConfig(); err != nil {
logDebugf("Error: loading config file: %v", err)
return
}
logDebugf("Using config file: %v", viper.ConfigFileUsed())
// Search config in the config directory with name of the CLI binary (without extension).
configDir = path.Join(configDir, exeName)
viper.AddConfigPath(configDir)
viper.SetConfigName("config")
}
if err := viper.ReadInConfig(); err != nil {
logDebugf("Error: loading config file: %v", err)
return
}
logDebugf("Using config file: %v", viper.ConfigFileUsed())
}
{{- if .SecurityDefinitions }}
{{- /*youyuan: rework this since spec may define multiple auth schemes.
cli needs to detect which one user passed rather than add all of them.*/}}
{{- /*youyuan: rework this since spec may define multiple auth schemes.
cli needs to detect which one user passed rather than add all of them.*/}}
// registerAuthInoWriterFlags registers all flags needed to perform authentication
func registerAuthInoWriterFlags(cmd *cobra.Command) error {
{{- range .SecurityDefinitions }}
/*{{.Name}} {{.Description}}*/
{{- if .IsBasicAuth }}
cmd.PersistentFlags().String("username", "", "username for basic auth")
viper.BindPFlag("username", cmd.PersistentFlags().Lookup("username"))
cmd.PersistentFlags().String("password", "", "password for basic auth")
viper.BindPFlag("password", cmd.PersistentFlags().Lookup("password"))
{{- end }}
{{- if .IsAPIKeyAuth }}
cmd.PersistentFlags().String("{{.Name}}", "", `{{.Description}}`)
viper.BindPFlag("{{.Name}}", cmd.PersistentFlags().Lookup("{{.Name}}"))
{{- end }}
{{- if .IsOAuth2 }}
// oauth2: let user provide the token in a flag, rather than implement the logic to fetch the token.
cmd.PersistentFlags().String("oauth2-token", "", `{{.Description}}`)
viper.BindPFlag("oauth2-token", cmd.PersistentFlags().Lookup("oauth2-token"))
{{- end }}
{{- end }}
return nil
// {{.Name}}
{{- if .Description }}
{{- comment .Description }}
{{- end }}
{{- if .IsBasicAuth }}
cmd.PersistentFlags().String("username", "", "username for basic auth")
if err := viper.BindPFlag("username", cmd.PersistentFlags().Lookup("username")) ; err != nil {
return err
}
cmd.PersistentFlags().String("password", "", "password for basic auth")
if err := viper.BindPFlag("password", cmd.PersistentFlags().Lookup("password")) ; err != nil {
return err
}
{{- end }}
{{- if .IsAPIKeyAuth }}
cmd.PersistentFlags().String("{{.Name}}", "", `{{.Description}}`)
if err := viper.BindPFlag("{{.Name}}", cmd.PersistentFlags().Lookup("{{.Name}}")) ; err != nil {
return err
}
{{- end }}
{{- if .IsOAuth2 }}
// oauth2: let user provide the token in a flag, rather than implement the logic to fetch the token.
cmd.PersistentFlags().String("oauth2-token", "", `{{.Description}}`)
if err := viper.BindPFlag("oauth2-token", cmd.PersistentFlags().Lookup("oauth2-token")) ; err != nil {
return err
}
{{- end }}
{{ end }}
return nil
}
// makeAuthInfoWriter retrieves cmd flags and construct an auth info writer
func makeAuthInfoWriter(cmd *cobra.Command) (runtime.ClientAuthInfoWriter, error) {
auths := []runtime.ClientAuthInfoWriter{}
auths := []runtime.ClientAuthInfoWriter{}
{{- range .SecurityDefinitions }}
/*{{.Name}} {{.Description}}*/
{{- if .IsBasicAuth }}
if viper.IsSet("username") {
usr := viper.GetString("username")
if !viper.IsSet("password"){
return nil, fmt.Errorf("Basic Auth password for user [%v] is not provided.", usr)
}
pwd := viper.GetString("password")
auths = append(auths, httptransport.BasicAuth(usr,pwd))
// {{.Name}}
{{- if .Description }}
{{- comment .Description }}
{{- end }}
{{- if .IsBasicAuth }}
if viper.IsSet("username") {
usr := viper.GetString("username")
if !viper.IsSet("password"){
return nil, fmt.Errorf("Basic Auth password for user [%v] is not provided.", usr)
}
{{- end }}
{{- if .IsAPIKeyAuth }}
if viper.IsSet("{{.Name}}") {
{{ pascalize .Name }}Key := viper.GetString("{{.Name}}")
auths = append(auths, httptransport.APIKeyAuth("{{.Name}}", "{{.In}}", {{ pascalize .Name }}Key))
}
{{- end }}
{{- if .IsOAuth2 }}
if viper.IsSet("oauth2-token") {
// oauth2 workflow for generated CLI is not ideal.
// If you have suggestions on how to support it, raise an issue here: https://github.com/go-swagger/go-swagger/issues
// This will be added to header: "Authorization: Bearer {oauth2-token value}"
token := viper.GetString("oauth2-token")
auths = append(auths, httptransport.BearerToken(token))
}
{{- end }}
pwd := viper.GetString("password")
auths = append(auths, httptransport.BasicAuth(usr,pwd))
}
{{- end }}
{{- if .IsAPIKeyAuth }}
if viper.IsSet("{{.Name}}") {
{{ pascalize .Name }}Key := viper.GetString("{{.Name}}")
auths = append(auths, httptransport.APIKeyAuth("{{.Name}}", "{{.In}}", {{ pascalize .Name }}Key))
}
{{- end }}
{{- if .IsOAuth2 }}
if viper.IsSet("oauth2-token") {
// oauth2 workflow for generated CLI is not ideal.
// If you have suggestions on how to support it, raise an issue here: https://github.com/go-swagger/go-swagger/issues
// This will be added to header: "Authorization: Bearer {oauth2-token value}"
token := viper.GetString("oauth2-token")
auths = append(auths, httptransport.BearerToken(token))
}
{{- end }}
{{- end }}
if len(auths) == 0 {
logDebugf("Warning: No auth params detected.")
return nil, nil
}
// compose all auths together
return httptransport.Compose(auths...), nil
if len(auths) == 0 {
logDebugf("Warning: No auth params detected.")
return nil, nil
}
// compose all auths together
return httptransport.Compose(auths...), nil
}
{{- end }}
{{ range .OperationGroups -}}
func makeOperationGroup{{ pascalize .Name }}Cmd() (*cobra.Command, error) {
{{- $operationGroupCmdVarName := printf "operationGroup%vCmd" (pascalize .Name) }}
{{ $operationGroupCmdVarName }} := &cobra.Command{
Use: "{{ .Name }}",
Long: `{{ .Description }}`,
}
{{ range .Operations }}
{{- $operationCmdVarName := printf "operation%vCmd" (pascalize .Name) }}
{{ $operationCmdVarName }}, err := makeOperation{{pascalize .Package}}{{ pascalize .Name }}Cmd()
if err != nil {
return nil, err
}
{{ $operationGroupCmdVarName }}.AddCommand({{ $operationCmdVarName }})
{{ end }}
return {{ $operationGroupCmdVarName }}, nil
{{- range .OperationGroups -}}
// make{{ cmdGroupName . }} returns a parent command to handle all operations with tag {{ printf "%q" .Name }}
func make{{ cmdGroupName . }}() (*cobra.Command, error) {
parent := &cobra.Command{
Use: "{{ .Name }}",
Long: `{{ .Description }}`,
}
{{- range $index,$element := .Operations }}
sub{{ $index }}, err := make{{ cmdName $element }}()
if err != nil {
return nil, err
}
parent.AddCommand(sub{{ $index }})
{{- end }}
return parent, nil
}
{{ end }} {{/*operation group*/}}
{{- end }} {{/*operation group*/}}

View file

@ -1,28 +1,29 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
package main
import (
"encoding/json"
{{ imports .DefaultImports }}
{{ imports .Imports }}
"fmt"
"os"
{{ imports .DefaultImports }}
{{ imports .Imports }}
)
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
func main() {
rootCmd,err := cli.MakeRootCmd()
rootCmd, err := cli.MakeRootCmd()
if err != nil {
fmt.Println("Cmd construction error: ", err)
fmt.Println("cmd construction error: ", err)
os.Exit(1)
}
if err := rootCmd.Execute(); err != nil {
if err = rootCmd.Execute(); err != nil {
fmt.Println("cmd execute error: ", err)
os.Exit(1)
}
}
}

View file

@ -11,9 +11,12 @@ package cli
import (
{{ imports .DefaultImports }}
{{ imports .Imports }}
"github.com/spf13/cobra"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
{{ imports .DefaultImports }}
{{ imports .Imports }}
)
// Schema cli for {{.GoType}}
@ -22,4 +25,4 @@ import (
{{ range .ExtraSchemas }}
// Extra schema cli for {{.GoType}}
{{ template "modelschemacli" .}}
{{ end }}
{{ end }}

View file

@ -1,26 +1,26 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
{{- /*TODO: do not hardcode cli pkg*/}}
package cli
package cli {{/* TODO: do not hardcode cli pkg */}}
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
{{ imports .DefaultImports }}
{{ imports .Imports }}
"fmt"
"github.com/spf13/cobra"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
httptransport "github.com/go-openapi/runtime/client"
{{ imports .DefaultImports }}
{{ imports .Imports }}
"github.com/spf13/cobra"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
httptransport "github.com/go-openapi/runtime/client"
)
// makeOperation{{pascalize .Package}}{{ pascalize .Name }}Cmd returns a cmd to handle operation {{ camelize .Name }}
func makeOperation{{pascalize .Package}}{{ pascalize .Name }}Cmd() (*cobra.Command, error) {
// make{{ cmdName . }} returns a command to handle operation {{ camelize .Name }}
func make{{ cmdName . }}() (*cobra.Command, error) {
cmd := &cobra.Command{
Use: "{{ .Name }}",
Short: `{{ escapeBackticks .Description}}`,
@ -46,12 +46,11 @@ func runOperation{{pascalize $operationGroup }}{{ pascalize $operation }}(cmd *c
// retrieve flag values from cmd and fill params
params := {{ .PackageAlias }}.New{{ pascalize .Name}}Params()
{{- range .Params }}
if err, _ := retrieveOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{ pascalize .Name }}Flag(params, "", cmd); err != nil{
if err, _ = retrieveOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{ pascalize .Name }}Flag(params, "", cmd); err != nil{
return err
}
{{- end }} {{/*Params*/}}
if dryRun {
{{/* Note: dry run is not very useful for now, but useful when validation is added in future*/}}
if dryRun { {{/* Note: dry run is not very useful for now, but useful when validation is added in future*/}}
logDebugf("dry-run flag specified. Skip sending request.")
return nil
}
@ -61,10 +60,11 @@ func runOperation{{pascalize $operationGroup }}{{ pascalize $operation }}(cmd *c
if err != nil {
return err
}
if !debug{
{{/* In debug mode content should have been printed in transport layer, so do not print again*/}}
if !debug{ {{/* In debug mode content should have been printed in transport layer, so do not print again*/}}
fmt.Println(msgStr)
}
return nil
}
@ -77,9 +77,9 @@ func registerOperation{{pascalize $operationGroup }}{{ pascalize $operation }}Pa
{{- end }}
return nil
}
{{/*register functions for each fields in this operation*/}}
{{- range .Params }}
func registerOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{pascalize .Name }}ParamFlags(cmdPrefix string, cmd *cobra.Command) error{
{{- if .IsPrimitive }}
{{ template "primitiveregistrator" . }}
@ -96,12 +96,12 @@ func registerOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{
}
{{- end }}
{{/*functions to retreive each field of params*/}}
{{/*functions to retrieve each field of params*/}}
{{- range .Params }}
func retrieveOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{ pascalize .Name }}Flag(m *{{ $operationPkgAlias }}.{{ pascalize $operation }}Params, cmdPrefix string, cmd *cobra.Command) (error,bool){
retAdded := false
{{- $flagStr := .Name }}
{{- $flagValueVar := printf "%vValue" (camelize .Name) }}
{{- /*only set the param if user set the flag*/}}
if cmd.Flags().Changed("{{ $flagStr }}") {
{{- if .IsPrimitive }}
@ -113,16 +113,16 @@ func retrieveOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{
{{- else if and .IsBodyParam .Schema .IsComplexObject (not .IsStream) }}
{{- /*schema payload can be passed in cmd as a string and here is unmarshalled to model struct and attached in params*/}}
// Read {{ $flagStr }} string from cmd and unmarshal
{{ $flagValueVar }}Str, err := cmd.Flags().GetString("{{ $flagStr }}")
{{ flagValueVar .Name }}Str, err := cmd.Flags().GetString("{{ $flagStr }}")
if err != nil {
return err, false
}
{{/*Note anonymous body schema is not pointer*/}}
{{ $flagValueVar }} := {{if containsPkgStr .GoType}}{{ .GoType }}{{else}}{{ .Pkg }}.{{.GoType}}{{ end }}{}
if err := json.Unmarshal([]byte({{ $flagValueVar }}Str), &{{ $flagValueVar }}); err!= nil{
{{ flagValueVar .Name }} := {{if containsPkgStr .GoType}}{{ .GoType }}{{else}}{{ .Pkg }}.{{.GoType}}{{ end }}{}
if err := json.Unmarshal([]byte({{ flagValueVar .Name }}Str), &{{ flagValueVar .Name }}); err!= nil{
return fmt.Errorf("cannot unmarshal {{ $flagStr }} string in {{.GoType}}: %v", err), false
}
m.{{ .ID }} = {{- if .IsNullable }}&{{- end }}{{ $flagValueVar }}
m.{{ .ID }} = {{- if .IsNullable }}&{{- end }}{{ flagValueVar .Name }}
{{- else }}
// warning: {{.GoType}} is not supported by go-swagger cli yet
{{- end }} {{/*end go type case*/}}
@ -131,32 +131,32 @@ func retrieveOperation{{pascalize $operationGroup }}{{ pascalize $operation }}{{
{{- /* Add flags to capture fields in Body. If previously Body struct was constructed in unmarshalling body string,
then reuse the struct, otherwise construct an empty value struct to fill. Here body field flags overwrites
unmarshalled body string values. */}}
{{- $flagModelVar := printf "%vModel" (camelize $flagValueVar) }}
{{ $flagModelVar }} := m.{{ .ID }}
if swag.IsZero({{ $flagModelVar }}){
{{ $flagModelVar }} = {{- if .IsNullable }}&{{- end }}{{if containsPkgStr .GoType}}{{ .GoType }}{{else}}{{ .Pkg }}.{{.GoType}}{{ end }}{}
{{ flagModelVar .Name }} := m.{{ .ID }}
if swag.IsZero({{ flagModelVar .Name }}){
{{ flagModelVar .Name }} = {{- if .IsNullable }}&{{- end }}{{if containsPkgStr .GoType}}{{ .GoType }}{{else}}{{ .Pkg }}.{{.GoType}}{{ end }}{}
}
{{- /*Only attach the body struct in params if user passed some flag filling some body fields.*/}}
{{- /* add "&" to $flagModelVar when it is not nullable because the retrieve method always expects a pointer */}}
err, added := retrieveModel{{ pascalize (dropPackage .GoType) }}Flags(0, {{if not .IsNullable}}&{{end}}{{ $flagModelVar }}, "{{ camelize (dropPackage .GoType) }}", cmd)
{{- /* add "&" to flagModelVar .Name when it is not nullable because the retrieve method always expects a pointer */}}
err, added := retrieveModel{{ pascalize (dropPackage .GoType) }}Flags(0, {{if not .IsNullable}}&{{end}}{{ flagModelVar .Name }}, "{{ camelize (dropPackage .GoType) }}", cmd)
if err != nil{
return err, false
}
if added {
m.{{.ID}} = {{ $flagModelVar }}
m.{{.ID}} = {{ flagModelVar .Name }}
}
if dryRun && debug {
{{/* dry run we don't get trasnport debug strings, so print it here*/}}
{{- $bodyDebugVar := printf "%vDebugBytes" (camelize $flagValueVar) }}
if dryRun && debug { {{/* dry run we don't get trasnport debug strings, so print it here*/}}
{{- $bodyDebugVar := printf "%vDebugBytes" (flagValueVar .Name) }}
{{ $bodyDebugVar }}, err := json.Marshal(m.{{.ID}})
if err != nil{
return err, false
}
logDebugf("{{.ID }} dry-run payload: %v", string({{ $bodyDebugVar }}))
}
retAdded = retAdded || added
{{/*body debug string will be printed in transport layer*/}}
retAdded = retAdded || added {{/*body debug string will be printed in transport layer*/}}
{{- end }}
return nil, retAdded
}
{{- end }} {{/*Params*/}}

View file

@ -8,34 +8,32 @@
{{- if .Enum }}
{{- $fullDescription = printf "Enum: %v. %v" (json .Enum) $fullDescription}}
{{- end }}
{{ camelize .Name }}Description := `{{ $fullDescription }}`
{{ flagDescriptionVar .Name }} := `{{ $fullDescription }}`
{{ end }}
{{ define "flagnamevar" }}
{{- $flagNameVar := printf "%vFlagName" (camelize .Name) }}
var {{ $flagNameVar }} string
var {{ flagNameVar .Name }} string
if cmdPrefix == "" {
{{ $flagNameVar }} = "{{ .Name }}"
{{ flagNameVar .Name }} = "{{ .Name }}"
}else{
{{ $flagNameVar }} = fmt.Sprintf("%v.{{ .Name }}", cmdPrefix)
{{ flagNameVar .Name }} = fmt.Sprintf("%v.{{ .Name }}", cmdPrefix)
}
{{ end }}
{{ define "flagdefaultvar" }}
{{ $defaultVar := printf "%vFlagDefault" (camelize .Name) }}
var {{ $defaultVar}} {{ .GoType }} {{ if .Default }}= {{ printf "%#v" .Default }}{{ end }}
var {{ flagDefaultVar .Name }} {{ .GoType }} {{ if .Default }}= {{ printf "%#v" .Default }}{{ end }}
{{ end }}
{{/* Not used. CLI does not mark flag as required, and required will be checked by validation in future */}}
{{/* {{ define "requiredregistrator" }}
if err := cmd.MarkPersistentFlagRequired({{ camelize .Name }}FlagName); err != nil{
if err := cmd.MarkPersistentFlagRequired({{ flagNameVar .Name }}); err != nil{
return err
}
{{ end }} */}}
{{ define "enumcompletion" }} {{/*only used for primitive types. completion type is always string.*/}}
{{ if .Enum }}
if err := cmd.RegisterFlagCompletionFunc({{ camelize .Name }}FlagName,
if err := cmd.RegisterFlagCompletionFunc({{ flagNameVar .Name }},
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var res []string
if err := json.Unmarshal([]byte(`{{ json .Enum }}`), &res); err != nil {
@ -54,12 +52,12 @@ if err := cmd.RegisterFlagCompletionFunc({{ camelize .Name }}FlagName,
{{ template "flagdescriptionvar" . }}
{{ template "flagnamevar" . }}
{{ template "flagdefaultvar" . }}
_ = cmd.PersistentFlags().{{ pascalize .GoType }}({{ camelize .Name }}FlagName, {{ camelize .Name }}FlagDefault, {{ (camelize .Name) }}Description)
_ = cmd.PersistentFlags().{{ pascalize .GoType }}({{ flagNameVar .Name }}, {{ flagDefaultVar .Name }}, {{ flagDescriptionVar .Name }})
{{ template "enumcompletion" . }}
{{- else if or (eq .GoType "strfmt.DateTime") (eq .GoType "strfmt.UUID") (eq .GoType "strfmt.ObjectId") }} {{/* read as string */}}
{{- else if or (eq .GoType "strfmt.DateTime") (eq .GoType "strfmt.UUID") (eq .GoType "strfmt.ObjectId") (eq .GoType "strfmt.ULID") }} {{/* read as string */}}
{{ template "flagdescriptionvar" . }}
{{ template "flagnamevar" . }}
_ = cmd.PersistentFlags().String({{ camelize .Name }}FlagName, "", {{ (camelize .Name) }}Description)
_ = cmd.PersistentFlags().String({{ flagNameVar .Name }}, "", {{ flagDescriptionVar .Name }})
{{ template "enumcompletion" . }}
{{- else }}
// warning: primitive {{.Name}} {{.GoType }} is not supported by go-swagger cli yet
@ -71,12 +69,12 @@ if err := cmd.RegisterFlagCompletionFunc({{ camelize .Name }}FlagName,
{{ template "flagdescriptionvar" . }}
{{ template "flagnamevar" . }}
{{ template "flagdefaultvar" . }}
_ = cmd.PersistentFlags().{{ pascalize .GoType }}Slice({{ camelize .Name }}FlagName, {{ camelize .Name }}FlagDefault, {{ (camelize .Name) }}Description)
_ = cmd.PersistentFlags().{{ pascalize .GoType }}Slice({{ flagNameVar .Name }}, {{ flagDefaultVar .Name }}, {{ flagDescriptionVar .Name }})
{{ template "enumcompletion" . }}
{{- else if or (eq .GoType "[]strfmt.DateTime") (eq .GoType "[]strfmt.UUID") (eq .GoType "[]strfmt.ObjectId") }} {{/* read as string */}}
{{- else if or (eq .GoType "[]strfmt.DateTime") (eq .GoType "[]strfmt.UUID") (eq .GoType "[]strfmt.ObjectId") (eq .GoType "[]strfmt.ULID") }} {{/* read as string */}}
{{ template "flagdescriptionvar" . }}
{{ template "flagnamevar" . }}
_ = cmd.PersistentFlags().StringSlice({{ camelize .Name }}FlagName, []string{}, {{ (camelize .Name) }}Description)
_ = cmd.PersistentFlags().StringSlice({{ flagNameVar .Name }}, []string{}, {{ flagDescriptionVar .Name }})
{{- else }}
// warning: array {{.Name}} {{.GoType }} is not supported by go-swagger cli yet
{{- end }}
@ -86,7 +84,7 @@ if err := cmd.RegisterFlagCompletionFunc({{ camelize .Name }}FlagName,
{{/* each body parameter gets a string flag to input json raw string */}}
{{ define "modelparamstringregistrator" }}
{{ template "flagnamevar" . }}
_ = cmd.PersistentFlags().String({{ camelize .Name }}FlagName, "", "Optional json string for [{{ .Name }}]. {{ .Description }}")
_ = cmd.PersistentFlags().String({{ flagNameVar .Name }}, "", `Optional json string for [{{ .Name }}]. {{ escapeBackticks .Description }}`)
{{ end }}
{{ define "modelparamregistrator" }} {{/* register a param that has a schema */}}

View file

@ -1,59 +1,55 @@
{{/*util functions to retrieve flags*/}}
{{ define "primitiveretriever" }}
{{- $flagValueVar := printf "%vFlagValue" (camelize .Name) }}
{{- $flagNameVar := printf "%vFlagName" (camelize .Name )}}
{{- if or (eq .GoType "int64") (eq .GoType "int32") (eq .GoType "string") (eq .GoType "float64") (eq .GoType "float32") (eq .GoType "bool") }}
{{ template "flagnamevar" . }}
{{ $flagValueVar }}, err := cmd.Flags().Get{{pascalize .GoType}}({{ $flagNameVar }})
{{ flagValueVar .Name }}, err := cmd.Flags().Get{{pascalize .GoType}}({{ flagNameVar .Name }})
if err != nil{
return err, false
}
{{- /* reciever by convention is m for CLI */}}
m.{{ pascalize .Name }} = {{- if .IsNullable }}&{{- end }}{{ $flagValueVar }}
m.{{ pascalize .Name }} = {{- if .IsNullable }}&{{- end }}{{ flagValueVar .Name }}
{{- else if or (eq .GoType "strfmt.DateTime") (eq .GoType "strfmt.ObjectId") (eq .GoType "strfmt.UUID" ) }} {{/*Get flag value as string, then parse it*/}}
{{/*Many of the strfmt types can be added here*/}}
{{ template "flagnamevar" . }}
{{ $flagValueVar }}Str, err := cmd.Flags().GetString({{ $flagNameVar }})
{{ flagValueVar .Name }}Str, err := cmd.Flags().GetString({{ flagNameVar .Name }})
if err != nil{
return err, false
}
var {{ $flagValueVar }} {{ .GoType }}
if err := {{ $flagValueVar }}.UnmarshalText([]byte({{ $flagValueVar }}Str)); err != nil{
var {{ flagValueVar .Name }} {{ .GoType }}
if err := {{ flagValueVar .Name }}.UnmarshalText([]byte({{ flagValueVar .Name }}Str)); err != nil{
return err, false
}
m.{{ pascalize .Name }} = {{- if .IsNullable }}&{{- end }}{{ $flagValueVar }}
m.{{ pascalize .Name }} = {{- if .IsNullable }}&{{- end }}{{ flagValueVar .Name }}
{{- else }}
// warning: primitive {{.Name}} {{.GoType }} is not supported by go-swagger cli yet
{{- end }}
{{ end }}
{{ define "arrayretriever" }}
{{- $flagValueVar := printf "%vFlagValues" (camelize .Name) }}
{{- $flagNameVar := printf "%vFlagName" (camelize .Name )}}
{{- if or (eq .GoType "[]int64") (eq .GoType "[]int32") (eq .GoType "[]string") (eq .GoType "[]float64") (eq .GoType "[]float32") (eq .GoType "[]bool") }}
{{ template "flagnamevar" . }}
{{ $flagValueVar }}, err := cmd.Flags().Get{{pascalize .GoType}}Slice({{ $flagNameVar }})
{{ flagValueVar .Name }}, err := cmd.Flags().Get{{pascalize .GoType}}Slice({{ flagNameVar .Name }})
if err != nil{
return err, false
}
{{- /* reciever by convention is m for CLI */}}
m.{{ pascalize .Name }} = {{ $flagValueVar }}
{{- /* receiver by convention is m for CLI */}}
m.{{ pascalize .Name }} = {{ flagValueVar .Name }}
{{- else if or (eq .GoType "[]strfmt.DateTime") (eq .GoType "[]strfmt.ObjectId") (eq .GoType "[]strfmt.UUID") }} {{/*Get flag value as string, then parse it*/}}
{{ template "flagnamevar" . }}
{{ $flagValueVar }}Str, err := cmd.Flags().GetStringSlice({{ $flagNameVar }})
{{ flagValueVar .Name }}Str, err := cmd.Flags().GetStringSlice({{ flagNameVar .Name }})
if err != nil{
return err, false
}
{{ $flagValueVar }} := make({{ .GoType }}, len({{ $flagValueVar }}Str))
for i, v := range {{ $flagValueVar }}Str {
if err := {{ $flagValueVar }}[i].UnmarshalText([]byte(v)); err != nil{
{{ flagValueVar .Name }} := make({{ .GoType }}, len({{ flagValueVar .Name }}Str))
for i, v := range {{ flagValueVar .Name }}Str {
if err := {{ flagValueVar .Name }}[i].UnmarshalText([]byte(v)); err != nil{
return err, false
}
}
m.{{ pascalize .Name }} = {{- if .IsNullable }}&{{- end }}{{ $flagValueVar }}
m.{{ pascalize .Name }} = {{- if .IsNullable }}&{{- end }}{{ flagValueVar .Name }}
{{- else }}
// warning: array {{.Name}} {{.GoType }} is not supported by go-swagger cli yet
{{- end }}
{{ end }}
{{ end }}

View file

@ -14,12 +14,12 @@
{{- if .IsPrimitive }}
{{ template "primitiveregistrator" . }}
{{- else if .IsArray }}
// warning: {{.Name}} {{ .GoType }} array type is not supported by go-swagger cli yet
// warning: {{.Name}} {{ .GoType }} array type is not supported by go-swagger cli yet
{{- else if .IsMap }}
// warning: {{.Name}} {{ .GoType }} map type is not supported by go-swagger cli yet
{{- else if .IsComplexObject }} {{/* struct case */}}
{{ template "flagnamevar" . }}
if err := registerModel{{pascalize (dropPackage .GoType) }}Flags(depth + 1, {{ camelize .Name }}FlagName, cmd); err != nil{
if err := registerModel{{pascalize (dropPackage .GoType) }}Flags(depth + 1, {{ flagNameVar .Name }}, cmd); err != nil{
return err
}
{{- else }}
@ -28,10 +28,8 @@
{{ end }}
{{ define "propertyretriever" }}
{{- $flagNameVar := printf "%vFlagName" (camelize .Name) }}
{{- $flagValueVar := printf "%vFlagValue" (camelize .Name) }}
{{ $flagNameVar }} := fmt.Sprintf("%v.{{ .Name }}", cmdPrefix)
if cmd.Flags().Changed({{ $flagNameVar }}) {
{{ flagNameVar .Name }} := fmt.Sprintf("%v.{{ .Name }}", cmdPrefix)
if cmd.Flags().Changed({{ flagNameVar .Name }}) {
{{- if .IsPrimitive }}
{{ template "primitiveretriever" . }}
retAdded = true
@ -46,18 +44,18 @@
{{- end }}
}
{{- if and .IsComplexObject (not .IsArray) (not .IsMap) (not .IsStream) }}
{{ $flagValueVar }} := m.{{pascalize .Name}}
if swag.IsZero({{ $flagValueVar }}){
{{ $flagValueVar }} = {{if .IsNullable }}&{{end}}{{if containsPkgStr .GoType}}{{ .GoType }}{{else}}{{ .Pkg }}.{{.GoType}}{{ end }}{}
{{ flagValueVar .Name }} := m.{{pascalize .Name}}
if swag.IsZero({{ flagValueVar .Name }}){
{{ flagValueVar .Name }} = {{if .IsNullable }}&{{end}}{{if containsPkgStr .GoType}}{{ .GoType }}{{else}}{{ .Pkg }}.{{.GoType}}{{ end }}{}
}
{{/* always lift the payload to pointer and pass to model retrieve function. If .GoType has pkg str, use it, else use .Pkg+.GoType */}}
err, {{camelize .Name }}Added := retrieveModel{{pascalize (dropPackage .GoType) }}Flags(depth + 1, {{if not .IsNullable }}&{{end}}{{ $flagValueVar }}, {{ $flagNameVar }}, cmd)
err, {{pascalize .Name }}Added := retrieveModel{{pascalize (dropPackage .GoType) }}Flags(depth + 1, {{if not .IsNullable }}&{{end}}{{ flagValueVar .Name }}, {{ flagNameVar .Name }}, cmd)
if err != nil{
return err, false
}
retAdded = retAdded || {{camelize .Name }}Added
if {{camelize .Name }}Added {
m.{{pascalize .Name}} = {{ $flagValueVar }}
retAdded = retAdded || {{pascalize .Name }}Added
if {{pascalize .Name }}Added {
m.{{pascalize .Name}} = {{ flagValueVar .Name }}
}
{{- end }}
{{ end }}
@ -85,14 +83,14 @@ func registerModel{{pascalize .Name}}Flags(depth int, cmdPrefix string, cmd *cob
// register anonymous fields for {{.Name}}
{{ $anonName := .Name }}
{{ range .Properties }}
if err := register{{ pascalize $modelName }}Anon{{pascalize $anonName }}{{ pascalize .Name }}(depth, cmdPrefix, cmd); err != nil{
if err := register{{ pascalize $modelName }}PropAnon{{pascalize $anonName }}{{ pascalize .Name }}(depth, cmdPrefix, cmd); err != nil{
return err
}
{{ end }}
{{ end }}
{{ end }}
{{ range .Properties }}
if err := register{{ pascalize $modelName }}{{ pascalize .Name }}(depth, cmdPrefix, cmd); err != nil{
if err := register{{ pascalize $modelName }}Prop{{ pascalize .Name }}(depth, cmdPrefix, cmd); err != nil{
return err
}
{{ end }}
@ -104,7 +102,7 @@ func registerModel{{pascalize .Name}}Flags(depth int, cmdPrefix string, cmd *cob
// inline definition name {{ .Name }}, type {{.GoType}}
{{ $anonName := .Name }}
{{ range .Properties }}
func register{{ pascalize $modelName }}Anon{{pascalize $anonName }}{{ pascalize .Name }}(depth int, cmdPrefix string, cmd *cobra.Command) error {
func register{{ pascalize $modelName }}PropAnon{{pascalize $anonName }}{{ pascalize .Name }}(depth int, cmdPrefix string, cmd *cobra.Command) error {
if depth > maxDepth {
return nil
}
@ -117,7 +115,7 @@ func register{{ pascalize $modelName }}Anon{{pascalize $anonName }}{{ pascalize
{{/*register functions for each fields in this model */}}
{{ range .Properties }}
func register{{ pascalize $modelName }}{{ pascalize .Name }}(depth int, cmdPrefix string, cmd *cobra.Command) error{
func register{{ pascalize $modelName }}Prop{{ pascalize .Name }}(depth int, cmdPrefix string, cmd *cobra.Command) error{
if depth > maxDepth {
return nil
}
@ -133,11 +131,11 @@ func retrieveModel{{pascalize $modelName }}Flags(depth int, m *{{if containsPkgS
{{- if not .IsAnonymous }}{{/* named type composition */}}
{{ if or .IsPrimitive .IsComplexObject }}
// retrieve model {{.GoType}}
err, {{camelize .Name }}Added := retrieveModel{{ pascalize (dropPackage .GoType) }}Flags(depth, &m.{{pascalize (dropPackage .GoType) }}, cmdPrefix, cmd)
err, {{pascalize .Name }}Added := retrieveModel{{ pascalize (dropPackage .GoType) }}Flags(depth, &m.{{pascalize (dropPackage .GoType) }}, cmdPrefix, cmd)
if err != nil{
return err, false
}
retAdded = retAdded || {{camelize .Name }}Added
retAdded = retAdded || {{pascalize .Name }}Added
{{ else }} {{/*inline anonymous case*/}}
{{ end }}
@ -145,20 +143,20 @@ func retrieveModel{{pascalize $modelName }}Flags(depth int, m *{{if containsPkgS
// retrieve allOf {{.Name}} fields
{{ $anonName := .Name }}
{{ range .Properties }}
err, {{camelize .Name}}Added := retrieve{{ pascalize $modelName }}Anon{{pascalize $anonName }}{{ pascalize .Name }}Flags(depth, m, cmdPrefix, cmd)
err, {{pascalize .Name}}Added := retrieve{{ pascalize $modelName }}PropAnon{{pascalize $anonName }}{{ pascalize .Name }}Flags(depth, m, cmdPrefix, cmd)
if err != nil{
return err, false
}
retAdded = retAdded || {{ camelize .Name }}Added
retAdded = retAdded || {{ pascalize .Name }}Added
{{ end }}
{{- end }}
{{ end }}
{{ range .Properties }}
err, {{ camelize .Name }}Added := retrieve{{pascalize $modelName }}{{pascalize .Name }}Flags(depth, m, cmdPrefix, cmd)
err, {{ pascalize .Name }}Added := retrieve{{pascalize $modelName }}Prop{{pascalize .Name }}Flags(depth, m, cmdPrefix, cmd)
if err != nil{
return err, false
}
retAdded = retAdded || {{ camelize .Name }}Added
retAdded = retAdded || {{ pascalize .Name }}Added
{{ end }}
return nil, retAdded
}
@ -168,7 +166,7 @@ func retrieveModel{{pascalize $modelName }}Flags(depth int, m *{{if containsPkgS
// define retrieve functions for fields for inline definition name {{ .Name }}
{{ $anonName := .Name }}
{{ range .Properties }} {{/*anonymous fields will be registered directly on parent model*/}}
func retrieve{{ pascalize $modelName }}Anon{{pascalize $anonName }}{{ pascalize .Name }}Flags(depth int, m *{{if containsPkgStr $modelType}}{{ $modelType }}{{else}}{{ $modelPkg }}.{{$modelType}}{{ end }},cmdPrefix string, cmd *cobra.Command) (error,bool) {
func retrieve{{ pascalize $modelName }}PropAnon{{pascalize $anonName }}{{ pascalize .Name }}Flags(depth int, m *{{if containsPkgStr $modelType}}{{ $modelType }}{{else}}{{ $modelPkg }}.{{$modelType}}{{ end }},cmdPrefix string, cmd *cobra.Command) (error,bool) {
if depth > maxDepth {
return nil, false
}
@ -181,7 +179,7 @@ func retrieve{{ pascalize $modelName }}Anon{{pascalize $anonName }}{{ pascalize
{{ end }}
{{ range .Properties }}
func retrieve{{pascalize $modelName }}{{pascalize .Name }}Flags(depth int, m *{{if $modelPkg}}{{$modelPkg}}.{{ dropPackage $modelType }}{{else}}{{ $modelType }}{{end}}, cmdPrefix string, cmd *cobra.Command) (error, bool) {
func retrieve{{pascalize $modelName }}Prop{{pascalize .Name }}Flags(depth int, m *{{if $modelPkg}}{{$modelPkg}}.{{ dropPackage $modelType }}{{else}}{{ $modelType }}{{end}}, cmdPrefix string, cmd *cobra.Command) (error, bool) {
if depth > maxDepth {
return nil, false
}
@ -190,4 +188,4 @@ func retrieve{{pascalize $modelName }}{{pascalize .Name }}Flags(depth int, m *{{
return nil, retAdded
}
{{ end }} {{/*properties*/}}
{{ end }} {{/*define*/}}
{{ end }} {{/*define*/}}

View file

@ -16,6 +16,7 @@ import (
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
@ -29,6 +30,31 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientServi
return &Client{transport: transport, formats: formats}
}
// New creates a new {{ humanize .Name }} API client with basic auth credentials.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - user: user for basic authentication header.
// - password: password for basic authentication header.
func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
return &Client{transport: transport, formats: strfmt.Default}
}
// New creates a new {{ humanize .Name }} API client with a bearer token for authentication.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - bearerToken: bearer token for Bearer authentication header.
func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BearerToken(bearerToken)
return &Client{transport: transport, formats: strfmt.Default}
}
/*
Client {{ if .Summary }}{{ .Summary }}{{ if .Description }}
@ -39,9 +65,58 @@ type Client struct {
formats strfmt.Registry
}
// ClientOption is the option for Client methods
// ClientOption may be used to customize the behavior of Client methods.
type ClientOption func(*runtime.ClientOperation)
{{- with .ClientOptions }}{{/* use ad'hoc function mediaGoName rather than pascalize because of patterns with * */}}
// This client is generated with a few options you might find useful for your swagger spec.
//
// Feel free to add you own set of options.
{{- if gt (len .ConsumesMediaTypes) 1 }}
// WithContentType allows the client to force the Content-Type header
// to negotiate a specific Consumer from the server.
//
// You may use this option to set arbitrary extensions to your MIME media type.
func WithContentType(mime string) ClientOption {
return func(r *runtime.ClientOperation) {
r.ConsumesMediaTypes = []string{mime}
}
}
{{ range .ConsumesMediaTypes }}
{{- if not ( eq (mediaGoName .) "" ) }}{{/* guard: in case garbled input produces a (conflicting) empty name */}}
// WithContentType{{ mediaGoName . }} sets the Content-Type header to {{ printf "%q" . }}.
func WithContentType{{ mediaGoName . }}(r *runtime.ClientOperation) {
r.ConsumesMediaTypes = []string{ {{ printf "%q" . }} }
}
{{- end }}
{{- end }}
{{- end }}
{{- if gt (len .ProducesMediaTypes) 1 }}
// WithAccept allows the client to force the Accept header
// to negotiate a specific Producer from the server.
//
// You may use this option to set arbitrary extensions to your MIME media type.
func WithAccept(mime string) ClientOption {
return func(r *runtime.ClientOperation) {
r.ProducesMediaTypes = []string{mime}
}
}
{{ range .ProducesMediaTypes }}
{{- if not ( eq (mediaGoName .) "" ) }}{{/* guard: in case garbled input produces a (conflicting) empty name */}}
// WithAccept{{ mediaGoName . }} sets the Accept header to {{ printf "%q" . }}.
func WithAccept{{ mediaGoName . }}(r *runtime.ClientOperation) {
r.ProducesMediaTypes = []string{ {{ printf "%q" . }} }
}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
// ClientService is the interface for Client methods
type ClientService interface {
{{ range .Operations }}
@ -124,4 +199,4 @@ func (a *Client) {{ pascalize .Name }}(params *{{ pascalize .Name }}Params{{ if
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport
}
}

View file

@ -119,11 +119,17 @@ func ({{ .ReceiverName }} *{{ pascalize .Name }}) Code() int {
}
func ({{ .ReceiverName }} *{{ pascalize .Name }}) Error() string {
return fmt.Sprintf("[{{ upper .Method }} {{ .Path }}][%d] {{ if .Name }}{{ .Name }} {{ else }}unknown error {{ end }}{{ if .Schema }} %+v{{ end }}", {{ if eq .Code -1 }}{{ .ReceiverName }}._statusCode{{ else }}{{ .Code }}{{ end }}{{ if .Schema }}, o.Payload{{ end }})
{{- if .Schema }}{{ if (not .Schema.IsStream) }}
payload, _ := json.Marshal(o.Payload)
{{- end }}{{- end }}
return fmt.Sprintf("[{{ upper .Method }} {{ .Path }}][%d]{{ if .Name }} {{ .Name }}{{ else }} unknown error{{ end }}{{ if .Schema }}{{ if not .Schema.IsStream }} %s{{ end }}{{ end }}", {{ if eq .Code -1 }}{{ .ReceiverName }}._statusCode{{ else }}{{ .Code }}{{ end }}{{ if .Schema }}{{ if not .Schema.IsStream }}, payload{{ end }}{{ end }})
}
func ({{ .ReceiverName }} *{{ pascalize .Name }}) String() string {
return fmt.Sprintf("[{{ upper .Method }} {{ .Path }}][%d] {{ if .Name }}{{ .Name }} {{ else }}unknown response {{ end }}{{ if .Schema }} %+v{{ end }}", {{ if eq .Code -1 }}{{ .ReceiverName }}._statusCode{{ else }}{{ .Code }}{{ end }}{{ if .Schema }}, o.Payload{{ end }})
{{- if .Schema }}{{ if (not .Schema.IsStream) }}
payload, _ := json.Marshal(o.Payload)
{{- end }}{{- end }}
return fmt.Sprintf("[{{ upper .Method }} {{ .Path }}][%d]{{ if .Name }} {{ .Name }}{{ else }} unknown response{{ end }}{{ if .Schema }}{{ if not .Schema.IsStream }} %s{{ end }}{{ end }}", {{ if eq .Code -1 }}{{ .ReceiverName }}._statusCode{{ else }}{{ .Code }}{{ end }}{{ if .Schema }}{{ if not .Schema.IsStream }}, payload{{ end }}{{ end }})
}
{{ if .Schema }}

View file

@ -71,7 +71,7 @@ type PetAPI interface {
PetUpdate(ctx context.Context, params pet.PetUpdateParams) middleware.Responder
}
//go:generate mockery -name StoreAPI -inpkg
//go:generate mockery --name StoreAPI --inpackage
// StoreAPI
type StoreAPI interface {

View file

@ -29,7 +29,7 @@ type contextKey string
const AuthKey contextKey = "Auth"
{{ range .OperationGroups -}}
//go:generate mockery -name {{ pascalize .Name}}API -inpkg
//go:generate mockery --name {{ pascalize .Name}}API --inpackage
/* {{ pascalize .Name }}API {{ .Description }} */
type {{ pascalize .Name }}API interface {

View file

@ -10,13 +10,13 @@
{{- else }}
{{- humanize .Name }}
{{- end }}
{{- if or .MinProperties .MinProperties }}
{{- if or .MinProperties .MaxProperties }}
//
{{- if .MinProperties }}
// Min Properties: {{ .MinProperties }}
// MinProperties: {{ .MinProperties }}
{{- end }}
{{- if .MaxProperties }}
// Max Properties: {{ .MaxProperties }}
// MaxProperties: {{ .MaxProperties }}
{{- end }}
{{- end }}
{{- if .Example }}

View file

@ -2,18 +2,18 @@
{{- with .ExternalDocs }}
{{- if .URL }}
{{- if .Description }}
> [{{ trimSpace .Description }}]({{ .URL }})
> [{{ mdBlock .Description }}]({{ .URL }})
{{- else }}
> [Read more]({{ .URL }})
{{- end }}
{{- else }}
> {{ trimSpace .Description }}
> {{ mdBlock .Description }}
{{- end }}
{{- end }}
{{- end }}
{{- define "docParam" }}{{/* renders a parameter with simple schema */}}
| {{ .Name }} | `{{ .Location }}` | {{ paramDocType . }} | `{{ .GoType }}` | {{ if .CollectionFormat }}`{{ docCollectionFormat .CollectionFormat .Child }}`{{ end }} | {{ if .Required }}{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }} | {{ trimSpace .Description }} |
| {{ .Name }} | `{{ .Location }}` | {{ paramDocType . }} | `{{ .GoType }}` | {{ if .CollectionFormat }}`{{ docCollectionFormat .CollectionFormat .Child }}`{{ end }} | {{ if .Required }}{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }} | {{ mdBlock .Description }} |
{{- end }}
{{- define "docModelSchema" }}{{/* renders a schema */}}
@ -46,7 +46,7 @@
{{- else if and .IsAliased .IsPrimitive (not .IsSuperAlias) -}}
| Name | Type | Go type | Default | Description | Example |
|------|------|---------| ------- |-------------|---------|
| {{ .Name }} | {{ schemaDocType . }}| {{ .AliasedType }} | {{ if .Default }}`{{ json .Default }}`{{ end }}| {{ trimSpace .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
| {{ .Name }} | {{ schemaDocType . }}| {{ .AliasedType }} | {{ if .Default }}`{{ json .Default }}`{{ end }}| {{ mdBlock .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
{{ printf "\n" }}
{{- else if or (and .IsAliased (not (.IsAdditionalProperties))) (and .IsComplexObject (not .Properties) (not .AllOf)) -}}
[{{- dropPackage .GoType }}](#{{ dasherize (dropPackage .GoType) -}})
@ -71,7 +71,7 @@ any
| Name | Type | Go type | Required | Default | Description | Example |
|------|------|---------|:--------:| ------- |-------------|---------|
{{- range .Properties }}
| {{ .Name }} | {{ template "docSchemaSimple" . }}| `{{ .GoType }}` | {{ if .Required }}{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }}| {{ trimSpace .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
| {{ .Name }} | {{ template "docSchemaSimple" . }}| `{{ .GoType }}` | {{ if .Required }}{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }}| {{ mdBlock .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
{{- end }}
{{ printf "\n" }}
{{- end }}
@ -86,7 +86,7 @@ any
| Type | Go type | Default | Description | Example |
|------|---------| ------- |-------------|---------|
| {{ template "docSchemaSimple" . }} | `{{ .GoType }}` |{{ if .Default }}`{{ json .Default }}`{{ end }}| {{ trimSpace .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
| {{ template "docSchemaSimple" . }} | `{{ .GoType }}` |{{ if .Default }}`{{ json .Default }}`{{ end }}| {{ mdBlock .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
{{- else }}
{{ template "docModelSchema" . }}
@ -104,7 +104,7 @@ any
| Type | Go type | Default | Description | Example |
|------|---------| ------- |-------------|---------|
| {{ template "docSchemaSimple" . }} | `{{ .GoType }}` |{{ if .Default }}`{{ json .Default }}`{{ end }}| {{ trimSpace .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
| {{ template "docSchemaSimple" . }} | `{{ .GoType }}` |{{ if .Default }}`{{ json .Default }}`{{ end }}| {{ mdBlock .Description }} | {{ if .Example }}`{{ .Example }}`{{ end }} |
{{- else }}
{{ template "docModelSchema" . }}
@ -161,7 +161,7 @@ any
{{- end }}
{{- define "docModelBodyParam" }}{{/* layout for body param schema */}}
| {{ .Name }} | `body` | {{ template "docSchemaSimple" .Schema }} | `{{ .Schema.GoType }}` | | {{ if .Required }}{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }}| {{ trimSpace .Description }} |
| {{ .Name }} | `body` | {{ template "docSchemaSimple" .Schema }} | `{{ .Schema.GoType }}` | | {{ if .Required }}{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }}| {{ mdBlock .Description }} |
{{- end }}
{{- define "docHeaders" }}{{/* renders response headers */}}
@ -169,7 +169,7 @@ any
| Name | Type | Go type | Separator | Default | Description |
|------|------|---------|-----------|---------|-------------|
{{- range .Headers }}
| {{ .Name }} | {{ headerDocType . }} | `{{ .GoType }}` | {{ if .CollectionFormat }}`{{ docCollectionFormat .CollectionFormat .Child }}`{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }} | {{ trimSpace .Description }} |
| {{ .Name }} | {{ headerDocType . }} | `{{ .GoType }}` | {{ if .CollectionFormat }}`{{ docCollectionFormat .CollectionFormat .Child }}`{{ end }} | {{ if .Default }}`{{ json .Default }}`{{ end }} | {{ mdBlock .Description }} |
{{- end }}
{{- end }}
{{- end }}
@ -350,7 +350,7 @@ Name | Description
{{- range .Operations }}
{{- $opname := .Name }}
### <span id="{{ dasherize .Name }}"></span> {{ if .Summary }}{{ trimSpace .Summary }}{{ else }}{{ humanize .Name }}{{ end }} (*{{ .Name }}*)
### <span id="{{ dasherize .Name }}"></span> {{ if .Summary }}{{ mdBlock .Summary }}{{ else }}{{ humanize .Name }}{{ end }} (*{{ .Name }}*)
```
{{ upper .Method }} {{ joinPath .BasePath .Path }}
@ -424,16 +424,16 @@ Name | Description
| Code | Status | Description | Has headers | Schema |
|------|--------|-------------|:-----------:|--------|
{{- range .Responses }}
| [{{.Code}}](#{{ dasherize $opname }}-{{ .Code }}) | {{ httpStatus .Code }} | {{ trimSpace .Description }} | {{ if .Headers }}{{ end }} | [schema](#{{ dasherize $opname }}-{{ .Code }}-schema) |
| [{{.Code}}](#{{ dasherize $opname }}-{{ .Code }}) | {{ httpStatus .Code }} | {{ mdBlock .Description }} | {{ if .Headers }}{{ end }} | [schema](#{{ dasherize $opname }}-{{ .Code }}-schema) |
{{- end }}
{{- with .DefaultResponse }}
| [default](#{{ dasherize $opname }}-default) | | {{ trimSpace .Description }} | {{ if .Headers }}{{ end }} | [schema](#{{ dasherize $opname }}-default-schema) |
| [default](#{{ dasherize $opname }}-default) | | {{ mdBlock .Description }} | {{ if .Headers }}{{ end }} | [schema](#{{ dasherize $opname }}-default-schema) |
{{- end }}
#### Responses
{{ range .Responses }}
##### <span id="{{ dasherize $opname }}-{{ .Code }}"></span> {{.Code}}{{ if .Description }} - {{ trimSpace .Description }}{{ end }}
##### <span id="{{ dasherize $opname }}-{{ .Code }}"></span> {{.Code}}{{ if .Description }} - {{ mdBlock .Description }}{{ end }}
Status: {{ httpStatus .Code }}
###### <span id="{{ dasherize $opname }}-{{ .Code }}-schema"></span> Schema
@ -462,7 +462,7 @@ Status: {{ httpStatus .Code }}
{{- with .DefaultResponse }}
##### <span id="{{ dasherize $opname }}-default"></span> Default Response
{{ trimSpace .Description }}
{{ mdBlock .Description }}
###### <span id="{{ dasherize $opname }}-default-schema"></span> Schema
{{- if .Schema }}

View file

@ -1,6 +1,6 @@
{{ define "primitivefieldcontextvalidator" }}
{{ if .ReadOnly }}
if err := validate.ReadOnly(ctx, {{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil{
if err := validate.ReadOnly(ctx, {{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil{
return err
}
{{ end }}
@ -8,25 +8,25 @@
{{ define "primitivefieldvalidator" }}
{{ if .Required }}
{{- if and (eq .GoType "string") (not .IsNullable) }}
if err := validate.RequiredString({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsAliased }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if .IsAliased }}){{ end }}); err != nil {
if err := validate.RequiredString({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsAliased }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if .IsAliased }}){{ end }}); err != nil {
{{- else }}
if err := validate.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
{{- end }}
return err
}
{{- end }}
{{ if .MinLength }}
if err := validate.MinLength({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MinLength }}); err != nil {
if err := validate.MinLength({{ path . }}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MinLength }}); err != nil {
return err
}
{{- end }}
{{ if .MaxLength }}
if err := validate.MaxLength({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MaxLength }}); err != nil {
if err := validate.MaxLength({{ path . }}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MaxLength }}); err != nil {
return err
}
{{ end }}
{{ if .Pattern }}
if err := validate.Pattern({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ToString }}, `{{ escapeBackticks .Pattern }}`); err != nil {
if err := validate.Pattern({{ path . }}, {{ printf "%q" .Location }}, {{ .ToString }}, `{{ escapeBackticks .Pattern }}`); err != nil {
return err
}
{{- end }}
@ -41,7 +41,7 @@
{{ end }}
{{ if .Enum }}
// value enum
if err := {{.ReceiverName }}.validate{{ pascalize .Name }}{{ .Suffix }}Enum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}); err != nil {
if err := {{.ReceiverName }}.validate{{ pascalize .Name }}{{ .Suffix }}Enum({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}); err != nil {
return err
}
{{- end }}
@ -52,7 +52,7 @@
{{ define "slicecontextvalidator" }}
{{ if .ReadOnly }}
if err := validate.ReadOnly(ctx, {{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil{
if err := validate.ReadOnly(ctx, {{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil{
return err
}
{{ end }}
@ -71,9 +71,9 @@
{{- end }}
if err := {{.ValueExpression }}.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ve.ValidateName({{ path . }})
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ce.ValidateName({{ path . }})
}
return err
}
@ -86,7 +86,7 @@
{{define "slicevalidator" }}
{{ if .Required }}
if err := validate.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ValueExpression }}); err != nil {
if err := validate.Required({{ path . }}, {{ printf "%q" .Location }}, {{ .ValueExpression }}); err != nil {
return err
}
{{ end }}
@ -94,23 +94,23 @@
{{ .IndexVar }}{{ pascalize .Name }}Size := int64(len({{.ValueExpression }}))
{{ end }}
{{ if .MinItems }}
if err := validate.MinItems({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .IndexVar }}{{ pascalize .Name }}Size, {{.MinItems }}); err != nil {
if err := validate.MinItems({{ path . }}, {{ printf "%q" .Location }}, {{ .IndexVar }}{{ pascalize .Name }}Size, {{.MinItems }}); err != nil {
return err
}
{{ end }}
{{ if .MaxItems }}
if err := validate.MaxItems({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .IndexVar }}{{ pascalize .Name }}Size, {{.MaxItems }}); err != nil {
if err := validate.MaxItems({{ path . }}, {{ printf "%q" .Location }}, {{ .IndexVar }}{{ pascalize .Name }}Size, {{.MaxItems }}); err != nil {
return err
}
{{ end }}
{{ if .UniqueItems }}
if err := validate.UniqueItems({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{.ValueExpression }}); err != nil {
if err := validate.UniqueItems({{ path . }}, {{ printf "%q" .Location }}, {{.ValueExpression }}); err != nil {
return err
}
{{ end }}
{{ if .Enum }}
// for slice
if err := {{.ReceiverName }}.validate{{ pascalize .Name }}Enum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{.ValueExpression }}); err != nil {
if err := {{.ReceiverName }}.validate{{ pascalize .Name }}Enum({{ path . }}, {{ printf "%q" .Location }}, {{.ValueExpression }}); err != nil {
return err
}
{{ end }}
@ -138,9 +138,9 @@
{{- end }}
if err := {{.ValueExpression }}.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ve.ValidateName({{ path . }})
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ce.ValidateName({{ path . }})
}
return err
}
@ -154,10 +154,10 @@
{{- if and .Required }}
{{- if or .IsNullable .IsInterface }}
if {{ .ReceiverName }}.{{ pascalize .Name }} == nil {
return errors.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, nil)
return errors.Required({{ path . }}, {{ printf "%q" .Location }}, nil)
}
{{- else }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{ .ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{ .ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -213,7 +213,7 @@
{{ template "mapcontextvalidator" . }}
{{- else if and .IsMap .IsInterface }}
{{ if .Enum }}
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}ValueEnum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}ValueEnum({{ path . }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
return err
}
{{- end }}
@ -278,10 +278,10 @@
{{- if and .Required }}
{{- if or .IsNullable .IsInterface }}
if {{ .ReceiverName }}.{{ pascalize .Name }} == nil {
return errors.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, nil)
return errors.Required({{ path . }}, {{ printf "%q" .Location }}, nil)
}
{{- else }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{ .ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{ .ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -295,12 +295,12 @@
{{- if .IsInterface }}
if {{ $validatedValues }}[{{ $keyVar }}] == nil { // not required
{{- else }}
if swag.IsZero({{ $validatedValues }}[{{ $keyVar }}]) { // not required
if swag.IsZero({{ .ValueExpression }}) { // not required
{{- end }}
continue
}
{{- else if and (.Required) (not .IsArray) }}{{/* Required slice is processed below */}}
if err := validate.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
if err := validate.Required({{ path . }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
return err
}
{{- end }}
@ -313,9 +313,9 @@
{{- end }}
if err := val.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ve.ValidateName({{ path . }})
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ce.ValidateName({{ path . }})
}
return err
}
@ -342,7 +342,7 @@
{{- end }}
{{- else if and .IsCustomFormatter (or .HasValidations .Required) }}{{/* custom format not captured as primitive */}}
{{- if .Required }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -352,15 +352,16 @@
{{- else if .IsArray }}
{{ template "slicevalidator" . }}
{{- else if and .IsMap (not .IsInterface) }}
{{ template "minmaxProperties" .}}
{{ template "mapvalidator" . }}
{{ if .Enum }}
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}ValueEnum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}ValueEnum({{ path . }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
return err
}
{{- end }}
{{- else if and .IsMap .IsInterface }}
{{ if .Enum }}
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}ValueEnum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}ValueEnum({{ path . }}, {{ printf "%q" .Location }}, {{ $validatedValues }}[{{ $keyVar }}]); err != nil {
return err
}
{{- end }}
@ -372,9 +373,9 @@
{{- end }}
if err := val.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ve.ValidateName({{ path . }})
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ce.ValidateName({{ path . }})
}
return err
}
@ -402,7 +403,7 @@
{{ end }}
{{ if .Enum }}
// from map
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}Enum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ValueExpression }}); err != nil {
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}Enum({{ path . }}, {{ printf "%q" .Location }}, {{ .ValueExpression }}); err != nil {
return err
}
{{ end }}
@ -430,6 +431,11 @@
{{- end }}
{{- end }}
{{- end }}
{{- else if .Enum }}
// from map without additionalProperties
if err := {{ .ReceiverName }}.validate{{ pascalize .Name }}Enum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ValueExpression }}); err != nil {
return err
}
{{- end }}
{{ end }}
@ -462,9 +468,9 @@
{{ end }}
if err := {{.ValueExpression }}.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ve.ValidateName({{ path . }})
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ce.ValidateName({{ path . }})
}
return err
}
@ -490,14 +496,14 @@
{{ define "minmaxProperties" }}
{{- if and (or .IsMap (and .IsAdditionalProperties .HasAdditionalProperties)) (or .MinProperties .MaxProperties) }}
{{- if and (not .IsAdditionalProperties) (not .IsInterface) (eq (len .Properties) 0) }}{{/* map only */}}
nprops := len({{ if and (not .IsAliased) .HasAdditionalProperties }}{{ .ReceiverName }}{{ else }}{{ .ValueExpression }}{{ end }})
nprops := len({{ if and .IsMap (not .IsAliased) .HasAdditionalProperties (not .IsElem) (not .IsProperty) }}{{ .ReceiverName }}{{ else }}{{ .ValueExpression }}{{ end }})
{{- else }}{{/* object with properties */}}
{{- if and .IsNullable .MinProperties }}
{{- if gt0 .MinProperties }}
// short circuits minProperties > 0
if {{ .ReceiverName }} == nil {
return errors.TooFewProperties({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ .MinProperties }})
return errors.TooFewProperties({{ path . }}, {{ printf "%q" .Location }}, {{ .MinProperties }})
}
{{- end }}
{{- end }}
@ -517,13 +523,13 @@
{{ if .MinProperties }}
// minProperties: {{ .MinProperties }}
if nprops < {{ .MinProperties }} {
return errors.TooFewProperties({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ .MinProperties }})
return errors.TooFewProperties({{ path . }}, {{ printf "%q" .Location }}, {{ .MinProperties }})
}
{{- end }}
{{ if .MaxProperties }}
// maxProperties: {{ .MaxProperties }}
if nprops > {{ .MaxProperties }} {
return errors.TooManyProperties({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ .MaxProperties }})
return errors.TooManyProperties({{ path . }}, {{ printf "%q" .Location }}, {{ .MaxProperties }})
}
{{- end }}
{{- end }}
@ -548,7 +554,7 @@
*/}}
{{- if not .IsAnonymous }}
{{- if and .Required (or .IsNullable .IsBaseType .IsMap) }}
if err := validate.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{.ValueExpression }}); err != nil {
if err := validate.Required({{ path . }}, {{ printf "%q" .Location }}, {{.ValueExpression }}); err != nil {
return err
}
{{- if and (not .Required) .IsBaseType }}
@ -563,9 +569,9 @@
{{- end }}
if err := {{.ValueExpression }}.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ve.ValidateName({{ path . }})
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName({{ if .Path }}{{ .Path }}{{ else }}""{{ end }})
return ce.ValidateName({{ path . }})
}
return err
}
@ -602,7 +608,7 @@
// at https://github.com/go-swagger/go-swagger/issues
{{- if .ReadOnly }}
if err := validate.ReadOnly{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.ReadOnly{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -625,7 +631,7 @@
{{- if .IsPrimitive }}
{{- if .IsAliased }}
{{- if and .Required (not .IsAnonymous) }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -635,7 +641,7 @@
{{- end }}
{{- else if and .IsCustomFormatter (or .HasValidations .Required) }}{{/* custom format not captured as primitive */}}
{{- if .Required }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -651,10 +657,10 @@
{{- if and .IsAdditionalProperties .Required (not .IsAliased) }}
{{- if or .IsNullable .IsInterface }}
if {{ .ValueExpression }} == nil {
return errors.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ValueExpression }})
return errors.Required({{ path . }}, {{ printf "%q" .Location }}, {{ .ValueExpression }})
}
{{- else }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -663,10 +669,10 @@
{{- else if and .IsExternal .Required }}
{{- if or .IsNullable .IsInterface }}
if {{ .ValueExpression }} == nil {
return errors.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ValueExpression }})
return errors.Required({{ path . }}, {{ printf "%q" .Location }}, {{ .ValueExpression }})
}
{{- else }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -697,7 +703,7 @@
{{ template "primitivefieldvalidator" . }}
{{- else if and .IsCustomFormatter (or .HasValidations .Required) }}{{/* custom format not captured as primitive */}}
{{- if .Required }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}({{ path . }}, {{ printf "%q" .Location }}, {{ if not (or .IsAnonymous .IsNullable) }}{{ .GoType }}({{ end }}{{.ValueExpression }}{{ if not (or .IsAnonymous .IsNullable) }}){{ end }}); err != nil {
return err
}
{{- end }}
@ -1034,11 +1040,11 @@ func ({{.ReceiverName }} *{{ if $.Discriminates }}{{ camelize $.Name }}{{ else i
{{- if and $.IsTuple .IsMap .Required }}
{{- if .IsInterface }}
if {{ .ValueExpression }} == nil {
return errors.Required({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ .ValueExpression }})
return errors.Required({{ path . }}, {{ printf "%q" .Location }}, {{ .ValueExpression }})
}
{{- else }}
if err := validate.Required{{ if and (eq .GoType "string") (not .IsNullable) }}String{{ end }}(
{{- if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }},
{{ path . }}, {{ printf "%q" .Location }},
{{- if and (eq .GoType "string") (not (or .IsAnonymous .IsNullable)) }}{{ .GoType }}({{ end }}
{{- .ValueExpression }}
{{- if and (eq .GoType "string") (not (or .IsAnonymous .IsNullable)) }}){{ end }}); err != nil {

View file

@ -230,7 +230,7 @@ type Server struct {
ListenLimit int{{ if .UseGoStructFlags }} `long:"listen-limit" description:"limit the number of outstanding requests"`{{ end }}
KeepAlive time.Duration{{ if .UseGoStructFlags }} `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`{{ end }}
ReadTimeout time.Duration{{ if .UseGoStructFlags }} `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`{{ end }}
WriteTimeout time.Duration{{ if .UseGoStructFlags }} `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`{{ end }}
WriteTimeout time.Duration{{ if .UseGoStructFlags }} `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"30s"`{{ end }}
httpServerL net.Listener
TLSHost string{{ if .UseGoStructFlags }} `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`{{ end }}

View file

@ -1,3 +1,3 @@
if err := validate.FormatOf({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ printf "%q" .SwaggerFormat }}, {{ .ToString }}, formats); err != nil {
if err := validate.FormatOf({{ path . }}, {{ printf "%q" .Location }}, {{ printf "%q" .SwaggerFormat }}, {{ .ToString }}, formats); err != nil {
return err
}

View file

@ -1,21 +1,21 @@
{{- if or (hasPrefix .UnderlyingType "int") }}
{{- if and (hasPrefix .UnderlyingType "int64") (not .IsAliased) }}
if err := validate.MaximumInt({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
if err := validate.MaximumInt({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
{{- else }}
if err := validate.MaximumInt({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, int64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
if err := validate.MaximumInt({{ path . }}, {{ printf "%q" .Location }}, int64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
{{- end }}
{{- else }}
{{- if hasPrefix .UnderlyingType "uint" }}
{{- if and (hasPrefix .UnderlyingType "uint64") (not .IsAliased) }}
if err := validate.MaximumUint({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
if err := validate.MaximumUint({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
{{- else }}
if err := validate.MaximumUint({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, uint64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
if err := validate.MaximumUint({{ path . }}, {{ printf "%q" .Location }}, uint64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
{{- end }}
{{- else }}
{{- if and (eq .UnderlyingType "float64") (not .IsAliased) }}
if err := validate.Maximum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
if err := validate.Maximum({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
{{- else }}
if err := validate.Maximum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
if err := validate.Maximum({{ path . }}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Maximum }}, {{.ExclusiveMaximum }}); err != nil {
{{- end }}
{{- end }}
{{- end }}

View file

@ -1,21 +1,21 @@
{{- if hasPrefix .UnderlyingType "int" }}
{{- if and (hasPrefix .UnderlyingType "int64") (not .IsAliased) }}
if err := validate.MinimumInt({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
if err := validate.MinimumInt({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
{{- else }}
if err := validate.MinimumInt({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, int64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
if err := validate.MinimumInt({{ path . }}, {{ printf "%q" .Location }}, int64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
{{- end }}
{{- else }}
{{- if hasPrefix .UnderlyingType "uint" }}
{{- if and (hasPrefix .UnderlyingType "uint64") (not .IsAliased) }}
if err := validate.MinimumUint({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
if err := validate.MinimumUint({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
{{- else }}
if err := validate.MinimumUint({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, uint64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
if err := validate.MinimumUint({{ path . }}, {{ printf "%q" .Location }}, uint64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
{{- end }}
{{- else }}
{{- if and (eq .UnderlyingType "float64") (not .IsAliased) }}
if err := validate.Minimum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
if err := validate.Minimum({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
{{- else }}
if err := validate.Minimum({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
if err := validate.Minimum({{ path . }}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.Minimum }}, {{.ExclusiveMinimum }}); err != nil {
{{- end }}
{{- end }}
{{- end }}

View file

@ -1,21 +1,21 @@
{{- if and (hasPrefix .UnderlyingType "int") (isInteger .MultipleOf) }}{{/* if the type is an integer, but the multiple factor is not, fall back to the float64 version of the validator */}}
{{- if and (hasPrefix .UnderlyingType "int64") (not .IsAliased) }}
if err := validate.MultipleOfInt({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.MultipleOf }}); err != nil {
if err := validate.MultipleOfInt({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.MultipleOf }}); err != nil {
{{- else }}
if err := validate.MultipleOfInt({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, int64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.MultipleOf }}); err != nil {
if err := validate.MultipleOfInt({{ path . }}, {{ printf "%q" .Location }}, int64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.MultipleOf }}); err != nil {
{{- end }}
{{- else }}
{{- if and (hasPrefix .UnderlyingType "uint") (isInteger .MultipleOf) }}
{{- if and (hasPrefix .UnderlyingType "uint64") (not .IsAliased) }}
if err := validate.MultipleOfUint({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.MultipleOf }}); err != nil {
if err := validate.MultipleOfUint({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.MultipleOf }}); err != nil {
{{- else }}
if err := validate.MultipleOfUint({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, uint64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.MultipleOf }}); err != nil {
if err := validate.MultipleOfUint({{ path . }}, {{ printf "%q" .Location }}, uint64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.MultipleOf }}); err != nil {
{{- end }}
{{- else }}
{{- if and (eq .UnderlyingType "float64") (not .IsAliased) }}
if err := validate.MultipleOf({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.MultipleOf }}); err != nil {
if err := validate.MultipleOf({{ path . }}, {{ printf "%q" .Location }}, {{ if .IsNullable }}*{{ end }}{{.ValueExpression }}, {{.MultipleOf }}); err != nil {
{{- else }}
if err := validate.MultipleOf({{ if .Path }}{{ .Path }}{{ else }}""{{ end }}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.MultipleOf }}); err != nil {
if err := validate.MultipleOf({{ path . }}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression }}), {{.MultipleOf }}); err != nil {
{{- end }}
{{- end }}
{{- end }}

View file

@ -1,15 +1,15 @@
{{if .MinLength}}
if err := validate.MinLength({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MinLength}}); err != nil {
if err := validate.MinLength({{ path . }}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MinLength}}); err != nil {
return err
}
{{end}}
{{if .MaxLength}}
if err := validate.MaxLength({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MaxLength}}); err != nil {
if err := validate.MaxLength({{ path . }}, {{ printf "%q" .Location }}, {{ .ToString }}, {{.MaxLength}}); err != nil {
return err
}
{{end}}
{{if .Pattern}}
if err := validate.Pattern({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ .ToString }}, `{{escapeBackticks .Pattern}}`); err != nil {
if err := validate.Pattern({{ path . }}, {{ printf "%q" .Location }}, {{ .ToString }}, `{{escapeBackticks .Pattern}}`); err != nil {
return err
}
{{end}}
@ -23,7 +23,7 @@ if err := validate.Pattern({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf
{{ template "validationMultipleOf" . }}
{{end}}
{{if .Enum}}
if err := validate.EnumCase({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) .IsNullable }}*{{ end }}{{.ValueExpression}}{{ if .IsCustomFormatter }}.String(){{ end }}, {{ printf "%#v" .Enum}}, {{ if .IsEnumCI }}false{{ else }}true{{ end }}); err != nil {
if err := validate.EnumCase({{ path . }}, {{ printf "%q" .Location }}, {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) .IsNullable }}*{{ end }}{{.ValueExpression}}{{ if .IsCustomFormatter }}.String(){{ end }}, {{ printf "%#v" .Enum}}, {{ if .IsEnumCI }}false{{ else }}true{{ end }}); err != nil {
return err
}
{{end}}

View file

@ -40,14 +40,6 @@
// Min Items: {{ .MinItems }}
{{- end }}
{{- if .MinProperties }}
// Min Properties: {{ .MinProperties }}
{{- end }}
{{- if .MaxProperties }}
// Max Properties: {{ .MaxProperties }}
{{- end }}
{{- if .UniqueItems }}
// Unique: true
{{- end }}
@ -57,6 +49,6 @@
{{- end }}
{{- if .Enum }}
// Enum: {{ printf "%v" .Enum }}
// Enum: {{ json .Enum }}
{{- end }}
{{- end}}

View file

@ -345,7 +345,6 @@ func (t *typeResolver) inferAliasing(result *resolvedType, _ *spec.Schema, isAno
}
func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRequired bool) (returns bool, result resolvedType, err error) {
if schema.Format != "" {
// defaults to string
result.SwaggerType = str
@ -401,7 +400,6 @@ func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRe
//
// The interpretation of Required as a mean to make a type nullable is carried out elsewhere.
func (t *typeResolver) isNullable(schema *spec.Schema) bool {
if nullable, ok := t.isNullableOverride(schema); ok {
return nullable
}
@ -1000,8 +998,8 @@ func warnSkipValidation(types interface{}) func(string, interface{}) {
func guardValidations(tpe string, schema interface {
Validations() spec.SchemaValidations
SetValidations(spec.SchemaValidations)
}, types ...string) {
}, types ...string,
) {
v := schema.Validations()
if len(types) == 0 {
types = []string{tpe}
@ -1049,7 +1047,8 @@ func guardValidations(tpe string, schema interface {
func guardFormatConflicts(format string, schema interface {
Validations() spec.SchemaValidations
SetValidations(spec.SchemaValidations)
}) {
},
) {
v := schema.Validations()
msg := fmt.Sprintf("for format %q", format)

View file

@ -1,3 +0,0 @@
# scan
Pre go1.11 version of the go source parser, without support for go modules.

View file

@ -1,166 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"fmt"
"go/ast"
"log"
"regexp"
"golang.org/x/tools/go/loader"
)
type packageFilter struct {
Name string
}
func (pf *packageFilter) Matches(path string) bool {
matched, err := regexp.MatchString(pf.Name, path)
if err != nil {
log.Fatal(err)
}
return matched
}
type packageFilters []packageFilter
func (pf packageFilters) HasFilters() bool {
return len(pf) > 0
}
func (pf packageFilters) Matches(path string) bool {
for _, mod := range pf {
if mod.Matches(path) {
return true
}
}
return false
}
type classifiedProgram struct {
Meta []*ast.File
Models []*ast.File
Routes []*ast.File
Operations []*ast.File
Parameters []*ast.File
Responses []*ast.File
}
// programClassifier classifies the files of a program into buckets
// for processing by a swagger spec generator. This buckets files in
// 3 groups: Meta, Models and Operations.
//
// # Each of these buckets is then processed with an appropriate parsing strategy
//
// When there are Include or Exclude filters provide they are used to limit the
// candidates prior to parsing.
// The include filters take precedence over the excludes. So when something appears
// in both filters it will be included.
type programClassifier struct {
Includes packageFilters
Excludes packageFilters
}
func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram, error) {
var cp classifiedProgram
for pkg, pkgInfo := range prog.AllPackages {
if Debug {
log.Printf("analyzing: %s\n", pkg.Path())
}
if pc.Includes.HasFilters() {
if !pc.Includes.Matches(pkg.Path()) {
continue
}
} else if pc.Excludes.HasFilters() {
if pc.Excludes.Matches(pkg.Path()) {
continue
}
}
for _, file := range pkgInfo.Files {
var ro, op, mt, pm, rs, mm bool // only add a particular file once
for _, comments := range file.Comments {
var seenStruct string
for _, cline := range comments.List {
if cline != nil {
matches := rxSwaggerAnnotation.FindStringSubmatch(cline.Text)
if len(matches) > 1 {
switch matches[1] {
case "route":
if !ro {
cp.Routes = append(cp.Routes, file)
ro = true
}
case "operation":
if !op {
cp.Operations = append(cp.Operations, file)
op = true
}
case "model":
if !mm {
cp.Models = append(cp.Models, file)
mm = true
}
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "meta":
if !mt {
cp.Meta = append(cp.Meta, file)
mt = true
}
case "parameters":
if !pm {
cp.Parameters = append(cp.Parameters, file)
pm = true
}
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "response":
if !rs {
cp.Responses = append(cp.Responses, file)
rs = true
}
if seenStruct == "" || seenStruct == matches[1] {
seenStruct = matches[1]
} else {
return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q - %s", seenStruct, matches[1], cline.Text)
}
case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type":
// TODO: perhaps collect these and pass along to avoid lookups later on
case "allOf":
case "ignore":
default:
return nil, fmt.Errorf("classifier: unknown swagger annotation %q", matches[1])
}
}
}
}
}
}
}
return &cp, nil
}

View file

@ -1,89 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan provides a scanner for go files that produces a swagger spec document.
This package is intended for pre-go1.11 versions, and does not support go modules.
You give it a main file and it will parse all the files that are required by that main
package to produce a swagger specification.
To use you can add a go:generate comment to your main file for example:
//go:generate swagger generate spec
The following annotations exist:
swagger:meta
The swagger:meta annotation flags a file as source for metadata about the API.
This is typically a doc.go file with your package documentation.
You can specify a Consumes and Produces key which has a new content type on each line
Schemes is a tag that is required and allows for a comma separated string composed of:
http, https, ws or wss
Host and BasePath can be specified but those values will be defaults,
they should get substituted when serving the swagger spec.
Default parameters and responses are not supported at this stage, for those you can edit the template json.
swagger:strfmt [name]
A swagger:strfmt annotation names a type as a string formatter. The name is mandatory and that is
what will be used as format name for this particular string format.
String formats should only be used for very well known formats.
swagger:model [?model name]
A swagger:model annotation optionally gets a model name as extra data on the line.
when this appears anywhere in a comment for a struct, then that struct becomes a schema
in the definitions object of swagger.
The struct gets analyzed and all the collected models are added to the tree.
The refs are tracked separately so that they can be renamed later on.
When this annotation is found to be on an interface instead of a struct, the properties are provided
through exported nullary methods.
A property of an interface model can have a Discriminator: true annotation to mark that field as
the field that will contain the discriminator value.
swagger:route [method] [path pattern] [operation id] [?tag1 tag2 tag3]
A swagger:route annotation links a path to a method.
This operation gets a unique id, which is used in various places as method name.
One such usage is in method names for client generation for example.
Because there are many routers available, this tool does not try to parse the paths
you provided to your routing library of choice. So you have to specify your path pattern
yourself in valid swagger syntax.
swagger:params [operationid1 operationid2]
Links a struct to one or more operations. The params in the resulting swagger spec can be composed of several structs.
There are no guarantees given on how property name overlaps are resolved when several structs apply to the same operation.
This tag works very similarly to the swagger:model tag except that it produces valid parameter objects instead of schema
objects.
swagger:response [?response name]
Reads a struct decorated with swagger:response and uses that information to fill up the headers and the schema for a response.
A swagger:route can specify a response name for a status code and then the matching response will be used for that operation in the swagger definition.
*/
package scan

View file

@ -1,84 +0,0 @@
//go:build !go1.11
// +build !go1.11
package scan
import (
"go/ast"
"strconv"
"strings"
"unicode"
)
func upperSnakeCase(s string) string {
in := []rune(s)
isLower := func(idx int) bool {
return idx >= 0 && idx < len(in) && unicode.IsLower(in[idx])
}
out := make([]rune, 0, len(in)+len(in)/2)
for i, r := range in {
if unicode.IsUpper(r) {
r = unicode.ToLower(r)
if i > 0 && in[i-1] != '_' && (isLower(i-1) || isLower(i+1)) {
out = append(out, '_')
}
}
out = append(out, r)
}
return strings.ToUpper(string(out))
}
func getEnumBasicLitValue(basicLit *ast.BasicLit) interface{} {
switch basicLit.Kind.String() {
case "INT":
if result, err := strconv.ParseInt(basicLit.Value, 10, 64); err == nil {
return result
}
case "FLOAT":
if result, err := strconv.ParseFloat(basicLit.Value, 64); err == nil {
return result
}
default:
return strings.Trim(basicLit.Value, "\"")
}
return nil
}
func getEnumValues(file *ast.File, typeName string) (list []interface{}) {
for _, decl := range file.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if genDecl.Tok.String() == "const" {
for _, spec := range genDecl.Specs {
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
switch valueSpec.Type.(type) {
case *ast.Ident:
if valueSpec.Type.(*ast.Ident).Name == typeName {
if basicLit, ok := valueSpec.Values[0].(*ast.BasicLit); ok {
list = append(list, getEnumBasicLitValue(basicLit))
}
}
default:
var name = valueSpec.Names[0].Name
if strings.HasPrefix(name, upperSnakeCase(typeName)) {
var values = strings.SplitN(name, "__", 2)
if len(values) == 2 {
list = append(list, values[1])
}
}
}
}
}
}
}
return
}

View file

@ -1,246 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"encoding/json"
"fmt"
"net/mail"
"regexp"
"strings"
"github.com/go-openapi/spec"
)
func metaTOSSetter(meta *spec.Info) func([]string) {
return func(lines []string) {
meta.TermsOfService = joinDropLast(lines)
}
}
func metaConsumesSetter(meta *spec.Swagger) func([]string) {
return func(consumes []string) { meta.Consumes = consumes }
}
func metaProducesSetter(meta *spec.Swagger) func([]string) {
return func(produces []string) { meta.Produces = produces }
}
func metaSchemeSetter(meta *spec.Swagger) func([]string) {
return func(schemes []string) { meta.Schemes = schemes }
}
func metaSecuritySetter(meta *spec.Swagger) func([]map[string][]string) {
return func(secDefs []map[string][]string) { meta.Security = secDefs }
}
func metaSecurityDefinitionsSetter(meta *spec.Swagger) func(json.RawMessage) error {
return func(jsonValue json.RawMessage) error {
var jsonData spec.SecurityDefinitions
err := json.Unmarshal(jsonValue, &jsonData)
if err != nil {
return err
}
meta.SecurityDefinitions = jsonData
return nil
}
}
func metaVendorExtensibleSetter(meta *spec.Swagger) func(json.RawMessage) error {
return func(jsonValue json.RawMessage) error {
var jsonData spec.Extensions
err := json.Unmarshal(jsonValue, &jsonData)
if err != nil {
return err
}
for k := range jsonData {
if !rxAllowedExtensions.MatchString(k) {
return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k)
}
}
meta.Extensions = jsonData
return nil
}
}
func infoVendorExtensibleSetter(meta *spec.Swagger) func(json.RawMessage) error {
return func(jsonValue json.RawMessage) error {
var jsonData spec.Extensions
err := json.Unmarshal(jsonValue, &jsonData)
if err != nil {
return err
}
for k := range jsonData {
if !rxAllowedExtensions.MatchString(k) {
return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k)
}
}
meta.Info.Extensions = jsonData
return nil
}
}
func newMetaParser(swspec *spec.Swagger) *sectionedParser {
sp := new(sectionedParser)
if swspec.Info == nil {
swspec.Info = new(spec.Info)
}
info := swspec.Info
sp.setTitle = func(lines []string) {
tosave := joinDropLast(lines)
if len(tosave) > 0 {
tosave = rxStripTitleComments.ReplaceAllString(tosave, "")
}
info.Title = tosave
}
sp.setDescription = func(lines []string) { info.Description = joinDropLast(lines) }
sp.taggers = []tagParser{
newMultiLineTagParser("TOS", newMultilineDropEmptyParser(rxTOS, metaTOSSetter(info)), false),
newMultiLineTagParser("Consumes", newMultilineDropEmptyParser(rxConsumes, metaConsumesSetter(swspec)), false),
newMultiLineTagParser("Produces", newMultilineDropEmptyParser(rxProduces, metaProducesSetter(swspec)), false),
newSingleLineTagParser("Schemes", newSetSchemes(metaSchemeSetter(swspec))),
newMultiLineTagParser("Security", newSetSecurity(rxSecuritySchemes, metaSecuritySetter(swspec)), false),
newMultiLineTagParser("SecurityDefinitions", newYamlParser(rxSecurity, metaSecurityDefinitionsSetter(swspec)), true),
newSingleLineTagParser("Version", &setMetaSingle{swspec, rxVersion, setInfoVersion}),
newSingleLineTagParser("Host", &setMetaSingle{swspec, rxHost, setSwaggerHost}),
newSingleLineTagParser("BasePath", &setMetaSingle{swspec, rxBasePath, setSwaggerBasePath}),
newSingleLineTagParser("Contact", &setMetaSingle{swspec, rxContact, setInfoContact}),
newSingleLineTagParser("License", &setMetaSingle{swspec, rxLicense, setInfoLicense}),
newMultiLineTagParser("YAMLInfoExtensionsBlock", newYamlParser(rxInfoExtensions, infoVendorExtensibleSetter(swspec)), true),
newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, metaVendorExtensibleSetter(swspec)), true),
}
return sp
}
type setMetaSingle struct {
spec *spec.Swagger
rx *regexp.Regexp
set func(spec *spec.Swagger, lines []string) error
}
func (s *setMetaSingle) Matches(line string) bool {
return s.rx.MatchString(line)
}
func (s *setMetaSingle) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := s.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
return s.set(s.spec, []string{matches[1]})
}
return nil
}
func setSwaggerHost(swspec *spec.Swagger, lines []string) error {
lns := lines
if len(lns) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
lns = []string{"localhost"}
}
swspec.Host = lns[0]
return nil
}
func setSwaggerBasePath(swspec *spec.Swagger, lines []string) error {
var ln string
if len(lines) > 0 {
ln = lines[0]
}
swspec.BasePath = ln
return nil
}
func setInfoVersion(swspec *spec.Swagger, lines []string) error {
if len(lines) == 0 {
return nil
}
info := safeInfo(swspec)
info.Version = strings.TrimSpace(lines[0])
return nil
}
func setInfoContact(swspec *spec.Swagger, lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
contact, err := parseContactInfo(lines[0])
if err != nil {
return err
}
info := safeInfo(swspec)
info.Contact = contact
return nil
}
func parseContactInfo(line string) (*spec.ContactInfo, error) {
nameEmail, url := splitURL(line)
var name, email string
if len(nameEmail) > 0 {
addr, err := mail.ParseAddress(nameEmail)
if err != nil {
return nil, err
}
name, email = addr.Name, addr.Address
}
return &spec.ContactInfo{
URL: url,
Name: name,
Email: email,
}, nil
}
func setInfoLicense(swspec *spec.Swagger, lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
info := safeInfo(swspec)
line := lines[0]
name, url := splitURL(line)
info.License = &spec.License{
Name: name,
URL: url,
}
return nil
}
func safeInfo(swspec *spec.Swagger) *spec.Info {
if swspec.Info == nil {
swspec.Info = new(spec.Info)
}
return swspec.Info
}
// httpFTPScheme matches http://, https://, ws://, wss://
var httpFTPScheme = regexp.MustCompile("(?:(?:ht|f)tp|ws)s?://")
func splitURL(line string) (notURL, url string) {
str := strings.TrimSpace(line)
parts := httpFTPScheme.FindStringIndex(str)
if len(parts) == 0 {
if len(str) > 0 {
notURL = str
}
return
}
if len(parts) > 0 {
notURL = strings.TrimSpace(str[:parts[0]])
url = strings.TrimSpace(str[parts[0]:])
}
return
}

View file

@ -1,85 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"fmt"
"go/ast"
"github.com/go-openapi/spec"
"golang.org/x/tools/go/loader"
)
func newOperationsParser(prog *loader.Program) *operationsParser {
return &operationsParser{
program: prog,
}
}
type operationsParser struct {
program *loader.Program
definitions map[string]spec.Schema
operations map[string]*spec.Operation
responses map[string]spec.Response
}
func (op *operationsParser) Parse(gofile *ast.File, target interface{}, includeTags map[string]bool, excludeTags map[string]bool) error {
tgt := target.(*spec.Paths)
for _, comsec := range gofile.Comments {
content := parsePathAnnotation(rxOperation, comsec.List)
if content.Method == "" {
continue // it's not, next!
}
if !shouldAcceptTag(content.Tags, includeTags, excludeTags) {
if Debug {
fmt.Printf("operation %s %s is ignored due to tag rules\n", content.Method, content.Path)
}
continue
}
pthObj := tgt.Paths[content.Path]
op := setPathOperation(
content.Method, content.ID,
&pthObj, op.operations[content.ID])
op.Tags = content.Tags
sp := new(yamlSpecScanner)
sp.setTitle = func(lines []string) { op.Summary = joinDropLast(lines) }
sp.setDescription = func(lines []string) { op.Description = joinDropLast(lines) }
if err := sp.Parse(content.Remaining); err != nil {
return fmt.Errorf("operation (%s): %v", op.ID, err)
}
if err := sp.UnmarshalSpec(op.UnmarshalJSON); err != nil {
return fmt.Errorf("operation (%s): %v", op.ID, err)
}
if tgt.Paths == nil {
tgt.Paths = make(map[string]spec.PathItem)
}
tgt.Paths[content.Path] = pthObj
}
return nil
}

View file

@ -1,515 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"fmt"
"go/ast"
"strings"
"github.com/go-openapi/spec"
"golang.org/x/tools/go/loader"
)
type operationValidationBuilder interface {
validationBuilder
SetCollectionFormat(string)
}
type paramTypable struct {
param *spec.Parameter
}
func (pt paramTypable) Level() int { return 0 }
func (pt paramTypable) Typed(tpe, format string) {
pt.param.Typed(tpe, format)
}
func (pt paramTypable) WithEnum(values ...interface{}) {
pt.param.WithEnum(values...)
}
func (pt paramTypable) SetRef(ref spec.Ref) {
pt.param.Ref = ref
}
func (pt paramTypable) Items() swaggerTypable {
bdt, schema := bodyTypable(pt.param.In, pt.param.Schema)
if bdt != nil {
pt.param.Schema = schema
return bdt
}
if pt.param.Items == nil {
pt.param.Items = new(spec.Items)
}
pt.param.Type = "array"
return itemsTypable{pt.param.Items, 1}
}
func (pt paramTypable) Schema() *spec.Schema {
if pt.param.In != "body" {
return nil
}
if pt.param.Schema == nil {
pt.param.Schema = new(spec.Schema)
}
return pt.param.Schema
}
type itemsTypable struct {
items *spec.Items
level int
}
func (pt itemsTypable) Level() int { return pt.level }
func (pt itemsTypable) Typed(tpe, format string) {
pt.items.Typed(tpe, format)
}
func (pt itemsTypable) SetRef(ref spec.Ref) {
pt.items.Ref = ref
}
func (pt itemsTypable) WithEnum(values ...interface{}) {
pt.items.WithEnum(values...)
}
func (pt itemsTypable) Schema() *spec.Schema {
return nil
}
func (pt itemsTypable) Items() swaggerTypable {
if pt.items.Items == nil {
pt.items.Items = new(spec.Items)
}
pt.items.Type = "array"
return itemsTypable{pt.items.Items, pt.level + 1}
}
type paramValidations struct {
current *spec.Parameter
}
func (sv paramValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv paramValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
}
func (sv paramValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val }
func (sv paramValidations) SetMinItems(val int64) { sv.current.MinItems = &val }
func (sv paramValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val }
func (sv paramValidations) SetMinLength(val int64) { sv.current.MinLength = &val }
func (sv paramValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val }
func (sv paramValidations) SetPattern(val string) { sv.current.Pattern = val }
func (sv paramValidations) SetUnique(val bool) { sv.current.UniqueItems = val }
func (sv paramValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
func (sv paramValidations) SetEnum(val string) {
sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
}
func (sv paramValidations) SetDefault(val interface{}) { sv.current.Default = val }
func (sv paramValidations) SetExample(val interface{}) { sv.current.Example = val }
type itemsValidations struct {
current *spec.Items
}
func (sv itemsValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv itemsValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
}
func (sv itemsValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val }
func (sv itemsValidations) SetMinItems(val int64) { sv.current.MinItems = &val }
func (sv itemsValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val }
func (sv itemsValidations) SetMinLength(val int64) { sv.current.MinLength = &val }
func (sv itemsValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val }
func (sv itemsValidations) SetPattern(val string) { sv.current.Pattern = val }
func (sv itemsValidations) SetUnique(val bool) { sv.current.UniqueItems = val }
func (sv itemsValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
func (sv itemsValidations) SetEnum(val string) {
sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
}
func (sv itemsValidations) SetDefault(val interface{}) { sv.current.Default = val }
func (sv itemsValidations) SetExample(val interface{}) { sv.current.Example = val }
type paramDecl struct {
File *ast.File
Decl *ast.GenDecl
TypeSpec *ast.TypeSpec
OperationIDs []string
}
func (sd *paramDecl) inferOperationIDs() (opids []string) {
if len(sd.OperationIDs) > 0 {
opids = sd.OperationIDs
return
}
if sd.Decl.Doc != nil {
for _, cmt := range sd.Decl.Doc.List {
for _, ln := range strings.Split(cmt.Text, "\n") {
matches := rxParametersOverride.FindStringSubmatch(ln)
if len(matches) > 1 && len(matches[1]) > 0 {
for _, pt := range strings.Split(matches[1], " ") {
tr := strings.TrimSpace(pt)
if len(tr) > 0 {
opids = append(opids, tr)
}
}
}
}
}
}
sd.OperationIDs = append(sd.OperationIDs, opids...)
return
}
func newParameterParser(prog *loader.Program) *paramStructParser {
scp := new(paramStructParser)
scp.program = prog
scp.scp = newSchemaParser(prog)
return scp
}
type paramStructParser struct {
program *loader.Program
postDecls []schemaDecl
scp *schemaParser
}
// Parse will traverse a file and look for parameters.
func (pp *paramStructParser) Parse(gofile *ast.File, target interface{}) error {
tgt := target.(map[string]*spec.Operation)
for _, decl := range gofile.Decls {
switch x1 := decl.(type) {
// Check for parameters at the package level.
case *ast.GenDecl:
for _, spc := range x1.Specs {
switch x2 := spc.(type) {
case *ast.TypeSpec:
sd := paramDecl{gofile, x1, x2, nil}
sd.inferOperationIDs()
if err := pp.parseDecl(tgt, sd); err != nil {
return err
}
}
}
// Check for parameters inside functions.
case *ast.FuncDecl:
for _, b := range x1.Body.List {
switch x2 := b.(type) {
case *ast.DeclStmt:
switch x3 := x2.Decl.(type) {
case *ast.GenDecl:
for _, spc := range x3.Specs {
switch x4 := spc.(type) {
case *ast.TypeSpec:
sd := paramDecl{gofile, x3, x4, nil}
sd.inferOperationIDs()
if err := pp.parseDecl(tgt, sd); err != nil {
return err
}
}
}
}
}
}
}
}
return nil
}
func (pp *paramStructParser) parseDecl(operations map[string]*spec.Operation, decl paramDecl) error {
// check if there is a swagger:parameters tag that is followed by one or more words,
// these words are the ids of the operations this parameter struct applies to
// once type name is found convert it to a schema, by looking up the schema in the
// parameters dictionary that got passed into this parse method
for _, opid := range decl.inferOperationIDs() {
operation, ok := operations[opid]
if !ok {
operation = new(spec.Operation)
operations[opid] = operation
operation.ID = opid
}
// analyze struct body for fields etc
// each exported struct field:
// * gets a type mapped to a go primitive
// * perhaps gets a format
// * has to document the validations that apply for the type and the field
// * when the struct field points to a model it becomes a ref: #/definitions/ModelName
// * comments that aren't tags is used as the description
if tpe, ok := decl.TypeSpec.Type.(*ast.StructType); ok {
if err := pp.parseStructType(decl.File, operation, tpe, make(map[string]spec.Parameter)); err != nil {
return err
}
}
//operations[opid] = operation
}
return nil
}
func (pp *paramStructParser) parseEmbeddedStruct(gofile *ast.File, operation *spec.Operation, expr ast.Expr, seenPreviously map[string]spec.Parameter) error {
switch tpe := expr.(type) {
case *ast.Ident:
// do lookup of type
// take primitives into account, they should result in an error for swagger
pkg, err := pp.scp.packageForFile(gofile, tpe)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
file, _, ts, err := findSourceFile(pkg, tpe.Name)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
if st, ok := ts.Type.(*ast.StructType); ok {
return pp.parseStructType(file, operation, st, seenPreviously)
}
case *ast.SelectorExpr:
// look up package, file and then type
pkg, err := pp.scp.packageForSelector(gofile, tpe.X)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
file, _, ts, err := findSourceFile(pkg, tpe.Sel.Name)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
if st, ok := ts.Type.(*ast.StructType); ok {
return pp.parseStructType(file, operation, st, seenPreviously)
}
case *ast.StarExpr:
return pp.parseEmbeddedStruct(gofile, operation, tpe.X, seenPreviously)
}
fmt.Printf("3%#v\n", expr)
return fmt.Errorf("unable to resolve embedded struct for: %v", expr)
}
func (pp *paramStructParser) parseStructType(gofile *ast.File, operation *spec.Operation, tpe *ast.StructType, seenPreviously map[string]spec.Parameter) error {
if tpe.Fields != nil {
pt := seenPreviously
for _, fld := range tpe.Fields.List {
if len(fld.Names) == 0 {
// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
// otherwise the fields will just be included as normal properties
if err := pp.parseEmbeddedStruct(gofile, operation, fld.Type, pt); err != nil {
return err
}
}
}
// a slice used to keep track of the sequence of the map keys, as maps does not keep to any specific sequence (since Go-1.4)
sequence := []string{}
for _, fld := range tpe.Fields.List {
if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() {
gnm := fld.Names[0].Name
nm, ignore, _, err := parseJSONTag(fld)
if err != nil {
return err
}
if ignore {
continue
}
in := "query"
// scan for param location first, this changes some behavior down the line
if fld.Doc != nil {
for _, cmt := range fld.Doc.List {
for _, line := range strings.Split(cmt.Text, "\n") {
matches := rxIn.FindStringSubmatch(line)
if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 {
in = strings.TrimSpace(matches[1])
}
}
}
}
ps := pt[nm]
ps.In = in
var pty swaggerTypable = paramTypable{&ps}
if in == "body" {
pty = schemaTypable{pty.Schema(), 0}
}
if in == "formData" && fld.Doc != nil && fileParam(fld.Doc) {
pty.Typed("file", "")
} else {
if err := pp.scp.parseNamedType(gofile, fld.Type, pty); err != nil {
return err
}
}
if strfmtName, ok := strfmtName(fld.Doc); ok {
ps.Typed("string", strfmtName)
ps.Ref = spec.Ref{}
}
sp := new(sectionedParser)
sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) }
if ps.Ref.String() == "" {
sp.taggers = []tagParser{
newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}),
newSingleLineTagParser("maximum", &setMaximum{paramValidations{&ps}, rxf(rxMaximumFmt, "")}),
newSingleLineTagParser("minimum", &setMinimum{paramValidations{&ps}, rxf(rxMinimumFmt, "")}),
newSingleLineTagParser("multipleOf", &setMultipleOf{paramValidations{&ps}, rxf(rxMultipleOfFmt, "")}),
newSingleLineTagParser("minLength", &setMinLength{paramValidations{&ps}, rxf(rxMinLengthFmt, "")}),
newSingleLineTagParser("maxLength", &setMaxLength{paramValidations{&ps}, rxf(rxMaxLengthFmt, "")}),
newSingleLineTagParser("pattern", &setPattern{paramValidations{&ps}, rxf(rxPatternFmt, "")}),
newSingleLineTagParser("collectionFormat", &setCollectionFormat{paramValidations{&ps}, rxf(rxCollectionFormatFmt, "")}),
newSingleLineTagParser("minItems", &setMinItems{paramValidations{&ps}, rxf(rxMinItemsFmt, "")}),
newSingleLineTagParser("maxItems", &setMaxItems{paramValidations{&ps}, rxf(rxMaxItemsFmt, "")}),
newSingleLineTagParser("unique", &setUnique{paramValidations{&ps}, rxf(rxUniqueFmt, "")}),
newSingleLineTagParser("enum", &setEnum{paramValidations{&ps}, rxf(rxEnumFmt, "")}),
newSingleLineTagParser("default", &setDefault{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxDefaultFmt, "")}),
newSingleLineTagParser("example", &setExample{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxExampleFmt, "")}),
newSingleLineTagParser("required", &setRequiredParam{&ps}),
}
itemsTaggers := func(items *spec.Items, level int) []tagParser {
// the expression is 1-index based not 0-index
itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1)
return []tagParser{
newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{itemsValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{itemsValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{itemsValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{itemsValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{itemsValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{itemsValidations{items}, rxf(rxPatternFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dCollectionFormat", level), &setCollectionFormat{itemsValidations{items}, rxf(rxCollectionFormatFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{itemsValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{itemsValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{itemsValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{itemsValidations{items}, rxf(rxEnumFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&items.SimpleSchema, itemsValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&items.SimpleSchema, itemsValidations{items}, rxf(rxExampleFmt, itemsPrefix)}),
}
}
var parseArrayTypes func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error)
parseArrayTypes = func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) {
if items == nil {
return []tagParser{}, nil
}
switch iftpe := expr.(type) {
case *ast.ArrayType:
eleTaggers := itemsTaggers(items, level)
sp.taggers = append(eleTaggers, sp.taggers...)
otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Items, level+1)
if err != nil {
return nil, err
}
return otherTaggers, nil
case *ast.SelectorExpr:
otherTaggers, err := parseArrayTypes(iftpe.Sel, items.Items, level+1)
if err != nil {
return nil, err
}
return otherTaggers, nil
case *ast.Ident:
taggers := []tagParser{}
if iftpe.Obj == nil {
taggers = itemsTaggers(items, level)
}
otherTaggers, err := parseArrayTypes(expr, items.Items, level+1)
if err != nil {
return nil, err
}
return append(taggers, otherTaggers...), nil
case *ast.StarExpr:
otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
if err != nil {
return nil, err
}
return otherTaggers, nil
default:
return nil, fmt.Errorf("unknown field type ele for %q", nm)
}
}
// check if this is a primitive, if so parse the validations from the
// doc comments of the slice declaration.
if ftped, ok := fld.Type.(*ast.ArrayType); ok {
taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0)
if err != nil {
return err
}
sp.taggers = append(taggers, sp.taggers...)
}
} else {
sp.taggers = []tagParser{
newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}),
newSingleLineTagParser("required", &matchOnlyParam{&ps, rxRequired}),
}
}
if err := sp.Parse(fld.Doc); err != nil {
return err
}
if ps.In == "path" {
ps.Required = true
}
if ps.Name == "" {
ps.Name = nm
}
if nm != gnm {
addExtension(&ps.VendorExtensible, "x-go-name", gnm)
}
pt[nm] = ps
sequence = append(sequence, nm)
}
}
for _, k := range sequence {
p := pt[k]
for i, v := range operation.Parameters {
if v.Name == k {
operation.Parameters = append(operation.Parameters[:i], operation.Parameters[i+1:]...)
break
}
}
operation.Parameters = append(operation.Parameters, p)
}
}
return nil
}
func isAliasParam(prop swaggerTypable) bool {
var isParam bool
if param, ok := prop.(paramTypable); ok {
isParam = param.param.In == "query" ||
param.param.In == "path" ||
param.param.In == "formData"
}
return isParam
}

View file

@ -1,151 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"go/ast"
"regexp"
"strings"
"github.com/go-openapi/spec"
)
type parsedPathContent struct {
Method, Path, ID string
Tags []string
Remaining *ast.CommentGroup
}
func parsePathAnnotation(annotation *regexp.Regexp, lines []*ast.Comment) (cnt parsedPathContent) {
var justMatched bool
for _, cmt := range lines {
for _, line := range strings.Split(cmt.Text, "\n") {
matches := annotation.FindStringSubmatch(line)
if len(matches) > 3 {
cnt.Method, cnt.Path, cnt.ID = matches[1], matches[2], matches[len(matches)-1]
cnt.Tags = rxSpace.Split(matches[3], -1)
if len(matches[3]) == 0 {
cnt.Tags = nil
}
justMatched = true
} else if cnt.Method != "" {
if cnt.Remaining == nil {
cnt.Remaining = new(ast.CommentGroup)
}
if !justMatched || strings.TrimSpace(rxStripComments.ReplaceAllString(line, "")) != "" {
cc := new(ast.Comment)
cc.Slash = cmt.Slash
cc.Text = line
cnt.Remaining.List = append(cnt.Remaining.List, cc)
justMatched = false
}
}
}
}
return
}
func setPathOperation(method, id string, pthObj *spec.PathItem, op *spec.Operation) *spec.Operation {
if op == nil {
op = new(spec.Operation)
op.ID = id
}
switch strings.ToUpper(method) {
case "GET":
if pthObj.Get != nil {
if id == pthObj.Get.ID {
op = pthObj.Get
} else {
pthObj.Get = op
}
} else {
pthObj.Get = op
}
case "POST":
if pthObj.Post != nil {
if id == pthObj.Post.ID {
op = pthObj.Post
} else {
pthObj.Post = op
}
} else {
pthObj.Post = op
}
case "PUT":
if pthObj.Put != nil {
if id == pthObj.Put.ID {
op = pthObj.Put
} else {
pthObj.Put = op
}
} else {
pthObj.Put = op
}
case "PATCH":
if pthObj.Patch != nil {
if id == pthObj.Patch.ID {
op = pthObj.Patch
} else {
pthObj.Patch = op
}
} else {
pthObj.Patch = op
}
case "HEAD":
if pthObj.Head != nil {
if id == pthObj.Head.ID {
op = pthObj.Head
} else {
pthObj.Head = op
}
} else {
pthObj.Head = op
}
case "DELETE":
if pthObj.Delete != nil {
if id == pthObj.Delete.ID {
op = pthObj.Delete
} else {
pthObj.Delete = op
}
} else {
pthObj.Delete = op
}
case "OPTIONS":
if pthObj.Options != nil {
if id == pthObj.Options.ID {
op = pthObj.Options
} else {
pthObj.Options = op
}
} else {
pthObj.Options = op
}
}
return op
}

View file

@ -1,453 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"fmt"
"go/ast"
"strings"
"golang.org/x/tools/go/loader"
"github.com/go-openapi/spec"
)
type responseTypable struct {
in string
header *spec.Header
response *spec.Response
}
func (ht responseTypable) Level() int { return 0 }
func (ht responseTypable) Typed(tpe, format string) {
ht.header.Typed(tpe, format)
}
func (ht responseTypable) WithEnum(values ...interface{}) {
ht.header.WithEnum(values)
}
func bodyTypable(in string, schema *spec.Schema) (swaggerTypable, *spec.Schema) {
if in == "body" {
// get the schema for items on the schema property
if schema == nil {
schema = new(spec.Schema)
}
if schema.Items == nil {
schema.Items = new(spec.SchemaOrArray)
}
if schema.Items.Schema == nil {
schema.Items.Schema = new(spec.Schema)
}
schema.Typed("array", "")
return schemaTypable{schema.Items.Schema, 0}, schema
}
return nil, nil
}
func (ht responseTypable) Items() swaggerTypable {
bdt, schema := bodyTypable(ht.in, ht.response.Schema)
if bdt != nil {
ht.response.Schema = schema
return bdt
}
if ht.header.Items == nil {
ht.header.Items = new(spec.Items)
}
ht.header.Type = "array"
return itemsTypable{ht.header.Items, 1}
}
func (ht responseTypable) SetRef(ref spec.Ref) {
// having trouble seeing the usefulness of this one here
ht.Schema().Ref = ref
}
func (ht responseTypable) Schema() *spec.Schema {
if ht.response.Schema == nil {
ht.response.Schema = new(spec.Schema)
}
return ht.response.Schema
}
func (ht responseTypable) SetSchema(schema *spec.Schema) {
ht.response.Schema = schema
}
func (ht responseTypable) CollectionOf(items *spec.Items, format string) {
ht.header.CollectionOf(items, format)
}
type headerValidations struct {
current *spec.Header
}
func (sv headerValidations) SetMaximum(val float64, exclusive bool) {
sv.current.Maximum = &val
sv.current.ExclusiveMaximum = exclusive
}
func (sv headerValidations) SetMinimum(val float64, exclusive bool) {
sv.current.Minimum = &val
sv.current.ExclusiveMinimum = exclusive
}
func (sv headerValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val }
func (sv headerValidations) SetMinItems(val int64) { sv.current.MinItems = &val }
func (sv headerValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val }
func (sv headerValidations) SetMinLength(val int64) { sv.current.MinLength = &val }
func (sv headerValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val }
func (sv headerValidations) SetPattern(val string) { sv.current.Pattern = val }
func (sv headerValidations) SetUnique(val bool) { sv.current.UniqueItems = val }
func (sv headerValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
func (sv headerValidations) SetEnum(val string) {
sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
}
func (sv headerValidations) SetDefault(val interface{}) { sv.current.Default = val }
func (sv headerValidations) SetExample(val interface{}) { sv.current.Example = val }
func newResponseDecl(file *ast.File, decl *ast.GenDecl, ts *ast.TypeSpec) responseDecl {
var rd responseDecl
rd.File = file
rd.Decl = decl
rd.TypeSpec = ts
rd.inferNames()
return rd
}
type responseDecl struct {
File *ast.File
Decl *ast.GenDecl
TypeSpec *ast.TypeSpec
GoName string
Name string
annotated bool
}
func (sd *responseDecl) hasAnnotation() bool {
sd.inferNames()
return sd.annotated
}
func (sd *responseDecl) inferNames() (goName string, name string) {
if sd.GoName != "" {
goName, name = sd.GoName, sd.Name
return
}
goName = sd.TypeSpec.Name.Name
name = goName
if sd.Decl.Doc != nil {
DECLS:
for _, cmt := range sd.Decl.Doc.List {
for _, ln := range strings.Split(cmt.Text, "\n") {
matches := rxResponseOverride.FindStringSubmatch(ln)
if len(matches) > 0 {
sd.annotated = true
}
if len(matches) > 1 && len(matches[1]) > 0 {
name = matches[1]
break DECLS
}
}
}
}
sd.GoName = goName
sd.Name = name
return
}
func newResponseParser(prog *loader.Program) *responseParser {
return &responseParser{prog, nil, newSchemaParser(prog)}
}
type responseParser struct {
program *loader.Program
postDecls []schemaDecl
scp *schemaParser
}
func (rp *responseParser) Parse(gofile *ast.File, target interface{}) error {
tgt := target.(map[string]spec.Response)
for _, decl := range gofile.Decls {
switch x1 := decl.(type) {
// Check for parameters at the package level.
case *ast.GenDecl:
for _, spc := range x1.Specs {
switch x2 := spc.(type) {
case *ast.TypeSpec:
sd := newResponseDecl(gofile, x1, x2)
if sd.hasAnnotation() {
if err := rp.parseDecl(tgt, sd); err != nil {
return err
}
}
}
}
// Check for parameters inside functions.
case *ast.FuncDecl:
for _, b := range x1.Body.List {
switch x2 := b.(type) {
case *ast.DeclStmt:
switch x3 := x2.Decl.(type) {
case *ast.GenDecl:
for _, spc := range x3.Specs {
switch x4 := spc.(type) {
case *ast.TypeSpec:
sd := newResponseDecl(gofile, x3, x4)
if sd.hasAnnotation() {
if err := rp.parseDecl(tgt, sd); err != nil {
return err
}
}
}
}
}
}
}
}
}
return nil
}
func (rp *responseParser) parseDecl(responses map[string]spec.Response, decl responseDecl) error {
// check if there is a swagger:parameters tag that is followed by one or more words,
// these words are the ids of the operations this parameter struct applies to
// once type name is found convert it to a schema, by looking up the schema in the
// parameters dictionary that got passed into this parse method
response := responses[decl.Name]
resPtr := &response
// analyze doc comment for the model
sp := new(sectionedParser)
sp.setDescription = func(lines []string) { resPtr.Description = joinDropLast(lines) }
if err := sp.Parse(decl.Decl.Doc); err != nil {
return err
}
// analyze struct body for fields etc
// each exported struct field:
// * gets a type mapped to a go primitive
// * perhaps gets a format
// * has to document the validations that apply for the type and the field
// * when the struct field points to a model it becomes a ref: #/definitions/ModelName
// * comments that aren't tags is used as the description
if tpe, ok := decl.TypeSpec.Type.(*ast.StructType); ok {
if err := rp.parseStructType(decl.File, resPtr, tpe, make(map[string]struct{})); err != nil {
return err
}
}
responses[decl.Name] = response
return nil
}
func (rp *responseParser) parseEmbeddedStruct(gofile *ast.File, response *spec.Response, expr ast.Expr, seenPreviously map[string]struct{}) error {
switch tpe := expr.(type) {
case *ast.Ident:
// do lookup of type
// take primitives into account, they should result in an error for swagger
pkg, err := rp.scp.packageForFile(gofile, tpe)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
file, _, ts, err := findSourceFile(pkg, tpe.Name)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
if st, ok := ts.Type.(*ast.StructType); ok {
return rp.parseStructType(file, response, st, seenPreviously)
}
case *ast.SelectorExpr:
// look up package, file and then type
pkg, err := rp.scp.packageForSelector(gofile, tpe.X)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
file, _, ts, err := findSourceFile(pkg, tpe.Sel.Name)
if err != nil {
return fmt.Errorf("embedded struct: %v", err)
}
if st, ok := ts.Type.(*ast.StructType); ok {
return rp.parseStructType(file, response, st, seenPreviously)
}
case *ast.StarExpr:
return rp.parseEmbeddedStruct(gofile, response, tpe.X, seenPreviously)
}
fmt.Printf("1%#v\n", expr)
return fmt.Errorf("unable to resolve embedded struct for: %v", expr)
}
func (rp *responseParser) parseStructType(gofile *ast.File, response *spec.Response, tpe *ast.StructType, seenPreviously map[string]struct{}) error {
if tpe.Fields != nil {
seenProperties := seenPreviously
for _, fld := range tpe.Fields.List {
if len(fld.Names) == 0 {
// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
// otherwise the fields will just be included as normal properties
if err := rp.parseEmbeddedStruct(gofile, response, fld.Type, seenProperties); err != nil {
return err
}
}
}
for _, fld := range tpe.Fields.List {
if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() {
nm, ignore, _, err := parseJSONTag(fld)
if err != nil {
return err
}
if ignore {
continue
}
var in string
// scan for param location first, this changes some behavior down the line
if fld.Doc != nil {
for _, cmt := range fld.Doc.List {
for _, line := range strings.Split(cmt.Text, "\n") {
matches := rxIn.FindStringSubmatch(line)
if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 {
in = strings.TrimSpace(matches[1])
}
}
}
}
ps := response.Headers[nm]
// support swagger:file for response
// An API operation can return a file, such as an image or PDF. In this case,
// define the response schema with type: file and specify the appropriate MIME types in the produces section.
if fld.Doc != nil && fileParam(fld.Doc) {
response.Schema = &spec.Schema{}
response.Schema.Typed("file", "")
} else if err := rp.scp.parseNamedType(gofile, fld.Type, responseTypable{in, &ps, response}); err != nil {
return err
}
if strfmtName, ok := strfmtName(fld.Doc); ok {
ps.Typed("string", strfmtName)
}
sp := new(sectionedParser)
sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) }
sp.taggers = []tagParser{
newSingleLineTagParser("maximum", &setMaximum{headerValidations{&ps}, rxf(rxMaximumFmt, "")}),
newSingleLineTagParser("minimum", &setMinimum{headerValidations{&ps}, rxf(rxMinimumFmt, "")}),
newSingleLineTagParser("multipleOf", &setMultipleOf{headerValidations{&ps}, rxf(rxMultipleOfFmt, "")}),
newSingleLineTagParser("minLength", &setMinLength{headerValidations{&ps}, rxf(rxMinLengthFmt, "")}),
newSingleLineTagParser("maxLength", &setMaxLength{headerValidations{&ps}, rxf(rxMaxLengthFmt, "")}),
newSingleLineTagParser("pattern", &setPattern{headerValidations{&ps}, rxf(rxPatternFmt, "")}),
newSingleLineTagParser("collectionFormat", &setCollectionFormat{headerValidations{&ps}, rxf(rxCollectionFormatFmt, "")}),
newSingleLineTagParser("minItems", &setMinItems{headerValidations{&ps}, rxf(rxMinItemsFmt, "")}),
newSingleLineTagParser("maxItems", &setMaxItems{headerValidations{&ps}, rxf(rxMaxItemsFmt, "")}),
newSingleLineTagParser("unique", &setUnique{headerValidations{&ps}, rxf(rxUniqueFmt, "")}),
newSingleLineTagParser("enum", &setEnum{headerValidations{&ps}, rxf(rxEnumFmt, "")}),
newSingleLineTagParser("default", &setDefault{&ps.SimpleSchema, headerValidations{&ps}, rxf(rxDefaultFmt, "")}),
newSingleLineTagParser("example", &setExample{&ps.SimpleSchema, headerValidations{&ps}, rxf(rxExampleFmt, "")}),
}
itemsTaggers := func(items *spec.Items, level int) []tagParser {
// the expression is 1-index based not 0-index
itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1)
return []tagParser{
newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{itemsValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{itemsValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{itemsValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{itemsValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{itemsValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{itemsValidations{items}, rxf(rxPatternFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dCollectionFormat", level), &setCollectionFormat{itemsValidations{items}, rxf(rxCollectionFormatFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{itemsValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{itemsValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{itemsValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{itemsValidations{items}, rxf(rxEnumFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&items.SimpleSchema, itemsValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}),
newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&items.SimpleSchema, itemsValidations{items}, rxf(rxExampleFmt, itemsPrefix)}),
}
}
var parseArrayTypes func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error)
parseArrayTypes = func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) {
if items == nil {
return []tagParser{}, nil
}
switch iftpe := expr.(type) {
case *ast.ArrayType:
eleTaggers := itemsTaggers(items, level)
sp.taggers = append(eleTaggers, sp.taggers...)
otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Items, level+1)
if err != nil {
return nil, err
}
return otherTaggers, nil
case *ast.Ident:
taggers := []tagParser{}
if iftpe.Obj == nil {
taggers = itemsTaggers(items, level)
}
otherTaggers, err := parseArrayTypes(expr, items.Items, level+1)
if err != nil {
return nil, err
}
return append(taggers, otherTaggers...), nil
case *ast.StarExpr:
otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
if err != nil {
return nil, err
}
return otherTaggers, nil
default:
return nil, fmt.Errorf("unknown field type ele for %q", nm)
}
}
// check if this is a primitive, if so parse the validations from the
// doc comments of the slice declaration.
if ftped, ok := fld.Type.(*ast.ArrayType); ok {
taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0)
if err != nil {
return err
}
sp.taggers = append(taggers, sp.taggers...)
}
if err := sp.Parse(fld.Doc); err != nil {
return err
}
if in != "body" {
seenProperties[nm] = struct{}{}
if response.Headers == nil {
response.Headers = make(map[string]spec.Header)
}
response.Headers[nm] = ps
}
}
}
for k := range response.Headers {
if _, ok := seenProperties[k]; !ok {
delete(response.Headers, k)
}
}
}
return nil
}

View file

@ -1,253 +0,0 @@
//go:build !go1.11
// +build !go1.11
package scan
import (
"errors"
"strconv"
"strings"
"github.com/go-openapi/spec"
)
const (
// ParamDescriptionKey indicates the tag used to define a parameter description in swagger:route
ParamDescriptionKey = "description"
// ParamNameKey indicates the tag used to define a parameter name in swagger:route
ParamNameKey = "name"
// ParamInKey indicates the tag used to define a parameter location in swagger:route
ParamInKey = "in"
// ParamRequiredKey indicates the tag used to declare whether a parameter is required in swagger:route
ParamRequiredKey = "required"
// ParamTypeKey indicates the tag used to define the parameter type in swagger:route
ParamTypeKey = "type"
// ParamAllowEmptyKey indicates the tag used to indicate whether a parameter allows empty values in swagger:route
ParamAllowEmptyKey = "allowempty"
// SchemaMinKey indicates the tag used to indicate the minimum value allowed for this type in swagger:route
SchemaMinKey = "min"
// SchemaMaxKey indicates the tag used to indicate the maximum value allowed for this type in swagger:route
SchemaMaxKey = "max"
// SchemaEnumKey indicates the tag used to specify the allowed values for this type in swagger:route
SchemaEnumKey = "enum"
// SchemaFormatKey indicates the expected format for this field in swagger:route
SchemaFormatKey = "format"
// SchemaDefaultKey indicates the default value for this field in swagger:route
SchemaDefaultKey = "default"
// SchemaMinLenKey indicates the minimum length this field in swagger:route
SchemaMinLenKey = "minlength"
// SchemaMaxLenKey indicates the minimum length this field in swagger:route
SchemaMaxLenKey = "maxlength"
// TypeArray is the identifier for an array type in swagger:route
TypeArray = "array"
// TypeNumber is the identifier for a number type in swagger:route
TypeNumber = "number"
// TypeInteger is the identifier for an integer type in swagger:route
TypeInteger = "integer"
// TypeBoolean is the identifier for a boolean type in swagger:route
TypeBoolean = "boolean"
// TypeBool is the identifier for a boolean type in swagger:route
TypeBool = "bool"
// TypeObject is the identifier for an object type in swagger:route
TypeObject = "object"
// TypeString is the identifier for a string type in swagger:route
TypeString = "string"
)
var (
validIn = []string{"path", "query", "header", "body", "form"}
basicTypes = []string{TypeInteger, TypeNumber, TypeString, TypeBoolean, TypeBool, TypeArray}
)
func newSetParams(params []*spec.Parameter, setter func([]*spec.Parameter)) *setOpParams {
return &setOpParams{
set: setter,
parameters: params,
}
}
type setOpParams struct {
set func([]*spec.Parameter)
parameters []*spec.Parameter
}
func (s *setOpParams) Matches(line string) bool {
return rxParameters.MatchString(line)
}
func (s *setOpParams) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
var current *spec.Parameter
var extraData map[string]string
for _, line := range lines {
l := strings.TrimSpace(line)
if strings.HasPrefix(l, "+") {
s.finalizeParam(current, extraData)
current = new(spec.Parameter)
extraData = make(map[string]string)
l = strings.TrimPrefix(l, "+")
}
kv := strings.SplitN(l, ":", 2)
if len(kv) <= 1 {
continue
}
key := strings.ToLower(strings.TrimSpace(kv[0]))
value := strings.TrimSpace(kv[1])
if current == nil {
return errors.New("invalid route/operation schema provided")
}
switch key {
case ParamDescriptionKey:
current.Description = value
case ParamNameKey:
current.Name = value
case ParamInKey:
v := strings.ToLower(value)
if contains(validIn, v) {
current.In = v
}
case ParamRequiredKey:
if v, err := strconv.ParseBool(value); err == nil {
current.Required = v
}
case ParamTypeKey:
if current.Schema == nil {
current.Schema = new(spec.Schema)
}
if contains(basicTypes, value) {
current.Type = strings.ToLower(value)
if current.Type == TypeBool {
current.Type = TypeBoolean
}
} else {
if ref, err := spec.NewRef("#/definitions/" + value); err == nil {
current.Type = TypeObject
current.Schema.Ref = ref
}
}
current.Schema.Type = spec.StringOrArray{current.Type}
case ParamAllowEmptyKey:
if v, err := strconv.ParseBool(value); err == nil {
current.AllowEmptyValue = v
}
default:
extraData[key] = value
}
}
s.finalizeParam(current, extraData)
s.set(s.parameters)
return nil
}
func (s *setOpParams) finalizeParam(param *spec.Parameter, data map[string]string) {
if param == nil {
return
}
processSchema(data, param)
s.parameters = append(s.parameters, param)
}
func processSchema(data map[string]string, param *spec.Parameter) {
if param.Schema == nil {
return
}
var enumValues []string
for key, value := range data {
switch key {
case SchemaMinKey:
if t := getType(param.Schema); t == TypeNumber || t == TypeInteger {
v, _ := strconv.ParseFloat(value, 64)
param.Schema.Minimum = &v
}
case SchemaMaxKey:
if t := getType(param.Schema); t == TypeNumber || t == TypeInteger {
v, _ := strconv.ParseFloat(value, 64)
param.Schema.Maximum = &v
}
case SchemaMinLenKey:
if getType(param.Schema) == TypeArray {
v, _ := strconv.ParseInt(value, 10, 64)
param.Schema.MinLength = &v
}
case SchemaMaxLenKey:
if getType(param.Schema) == TypeArray {
v, _ := strconv.ParseInt(value, 10, 64)
param.Schema.MaxLength = &v
}
case SchemaEnumKey:
enumValues = strings.Split(value, ",")
case SchemaFormatKey:
param.Schema.Format = value
case SchemaDefaultKey:
param.Schema.Default = convert(param.Type, value)
}
}
if param.Description != "" {
param.Schema.Description = param.Description
}
convertEnum(param.Schema, enumValues)
}
func convertEnum(schema *spec.Schema, enumValues []string) {
if len(enumValues) == 0 {
return
}
var finalEnum []interface{}
for _, v := range enumValues {
finalEnum = append(finalEnum, convert(schema.Type[0], strings.TrimSpace(v)))
}
schema.Enum = finalEnum
}
func convert(typeStr, valueStr string) interface{} {
switch typeStr {
case TypeInteger:
fallthrough
case TypeNumber:
if num, err := strconv.ParseFloat(valueStr, 64); err == nil {
return num
}
case TypeBoolean:
fallthrough
case TypeBool:
if b, err := strconv.ParseBool(valueStr); err == nil {
return b
}
}
return valueStr
}
func getType(schema *spec.Schema) string {
if len(schema.Type) == 0 {
return ""
}
return schema.Type[0]
}
func contains(arr []string, obj string) bool {
for _, v := range arr {
if v == obj {
return true
}
}
return false
}

View file

@ -1,146 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"fmt"
"go/ast"
"github.com/go-openapi/spec"
"golang.org/x/tools/go/loader"
)
func opConsumesSetter(op *spec.Operation) func([]string) {
return func(consumes []string) { op.Consumes = consumes }
}
func opProducesSetter(op *spec.Operation) func([]string) {
return func(produces []string) { op.Produces = produces }
}
func opSchemeSetter(op *spec.Operation) func([]string) {
return func(schemes []string) { op.Schemes = schemes }
}
func opSecurityDefsSetter(op *spec.Operation) func([]map[string][]string) {
return func(securityDefs []map[string][]string) { op.Security = securityDefs }
}
func opResponsesSetter(op *spec.Operation) func(*spec.Response, map[int]spec.Response) {
return func(def *spec.Response, scr map[int]spec.Response) {
if op.Responses == nil {
op.Responses = new(spec.Responses)
}
op.Responses.Default = def
op.Responses.StatusCodeResponses = scr
}
}
func opParamSetter(op *spec.Operation) func([]*spec.Parameter) {
return func(params []*spec.Parameter) {
for _, v := range params {
op.AddParam(v)
}
}
}
func newRoutesParser(prog *loader.Program) *routesParser {
return &routesParser{
program: prog,
}
}
type routesParser struct {
program *loader.Program
definitions map[string]spec.Schema
operations map[string]*spec.Operation
responses map[string]spec.Response
parameters []*spec.Parameter
}
var routeVendorExtensibleParser = vendorExtensibleParser{
setExtensions: func(ext spec.Extensions, dest interface{}) {
dest.(*spec.Operation).Extensions = ext
},
}
func (rp *routesParser) Parse(gofile *ast.File, target interface{}, includeTags map[string]bool, excludeTags map[string]bool) error {
tgt := target.(*spec.Paths)
for _, comsec := range gofile.Comments {
content := parsePathAnnotation(rxRoute, comsec.List)
if content.Method == "" {
continue // it's not, next!
}
if !shouldAcceptTag(content.Tags, includeTags, excludeTags) {
if Debug {
fmt.Printf("route %s %s is ignored due to tag rules\n", content.Method, content.Path)
}
continue
}
pthObj := tgt.Paths[content.Path]
op := setPathOperation(
content.Method, content.ID,
&pthObj, rp.operations[content.ID])
op.Tags = content.Tags
sp := new(sectionedParser)
sp.setTitle = func(lines []string) { op.Summary = joinDropLast(lines) }
sp.setDescription = func(lines []string) { op.Description = joinDropLast(lines) }
sr := newSetResponses(rp.definitions, rp.responses, opResponsesSetter(op))
spa := newSetParams(rp.parameters, opParamSetter(op))
sp.taggers = []tagParser{
newMultiLineTagParser("Consumes", newMultilineDropEmptyParser(rxConsumes, opConsumesSetter(op)), false),
newMultiLineTagParser("Produces", newMultilineDropEmptyParser(rxProduces, opProducesSetter(op)), false),
newSingleLineTagParser("Schemes", newSetSchemes(opSchemeSetter(op))),
newMultiLineTagParser("Security", newSetSecurity(rxSecuritySchemes, opSecurityDefsSetter(op)), false),
newMultiLineTagParser("Parameters", spa, false),
newMultiLineTagParser("Responses", sr, false),
newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, routeVendorExtensibleParser.ParseInto(op)), true),
}
if err := sp.Parse(content.Remaining); err != nil {
return fmt.Errorf("operation (%s): %v", op.ID, err)
}
if tgt.Paths == nil {
tgt.Paths = make(map[string]spec.PathItem)
}
tgt.Paths[content.Path] = pthObj
}
return nil
}
func shouldAcceptTag(tags []string, includeTags map[string]bool, excludeTags map[string]bool) bool {
for _, tag := range tags {
if len(includeTags) > 0 {
if includeTags[tag] {
return true
}
} else if len(excludeTags) > 0 {
if excludeTags[tag] {
return false
}
}
}
return len(includeTags) <= 0
}

View file

@ -1,974 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/build"
goparser "go/parser"
"go/types"
"log"
"os"
"regexp"
"strings"
"github.com/go-openapi/loads/fmts"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
"golang.org/x/tools/go/loader"
yaml "gopkg.in/yaml.v3"
)
const (
rxMethod = "(\\p{L}+)"
rxPath = "((?:/[\\p{L}\\p{N}\\p{Pd}\\p{Pc}{}\\-\\.\\?_~%!$&'()*+,;=:@/]*)+/?)"
rxOpTags = "(\\p{L}[\\p{L}\\p{N}\\p{Pd}\\.\\p{Pc}\\p{Zs}]+)"
rxOpID = "((?:\\p{L}[\\p{L}\\p{N}\\p{Pd}\\p{Pc}]+)+)"
rxMaximumFmt = "%s[Mm]ax(?:imum)?\\p{Zs}*:\\p{Zs}*([\\<=])?\\p{Zs}*([\\+-]?(?:\\p{N}+\\.)?\\p{N}+)$"
rxMinimumFmt = "%s[Mm]in(?:imum)?\\p{Zs}*:\\p{Zs}*([\\>=])?\\p{Zs}*([\\+-]?(?:\\p{N}+\\.)?\\p{N}+)$"
rxMultipleOfFmt = "%s[Mm]ultiple\\p{Zs}*[Oo]f\\p{Zs}*:\\p{Zs}*([\\+-]?(?:\\p{N}+\\.)?\\p{N}+)$"
rxMaxLengthFmt = "%s[Mm]ax(?:imum)?(?:\\p{Zs}*[\\p{Pd}\\p{Pc}]?[Ll]en(?:gth)?)\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
rxMinLengthFmt = "%s[Mm]in(?:imum)?(?:\\p{Zs}*[\\p{Pd}\\p{Pc}]?[Ll]en(?:gth)?)\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
rxPatternFmt = "%s[Pp]attern\\p{Zs}*:\\p{Zs}*(.*)$"
rxCollectionFormatFmt = "%s[Cc]ollection(?:\\p{Zs}*[\\p{Pd}\\p{Pc}]?[Ff]ormat)\\p{Zs}*:\\p{Zs}*(.*)$"
rxEnumFmt = "%s[Ee]num\\p{Zs}*:\\p{Zs}*(.*)$"
rxDefaultFmt = "%s[Dd]efault\\p{Zs}*:\\p{Zs}*(.*)$"
rxExampleFmt = "%s[Ee]xample\\p{Zs}*:\\p{Zs}*(.*)$"
rxMaxItemsFmt = "%s[Mm]ax(?:imum)?(?:\\p{Zs}*|[\\p{Pd}\\p{Pc}]|\\.)?[Ii]tems\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
rxMinItemsFmt = "%s[Mm]in(?:imum)?(?:\\p{Zs}*|[\\p{Pd}\\p{Pc}]|\\.)?[Ii]tems\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
rxUniqueFmt = "%s[Uu]nique\\p{Zs}*:\\p{Zs}*(true|false)$"
rxItemsPrefixFmt = "(?:[Ii]tems[\\.\\p{Zs}]*){%d}"
)
var (
rxSwaggerAnnotation = regexp.MustCompile(`swagger:([\p{L}\p{N}\p{Pd}\p{Pc}]+)`)
rxFileUpload = regexp.MustCompile(`swagger:file`)
rxStrFmt = regexp.MustCompile(`swagger:strfmt\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
rxAlias = regexp.MustCompile(`swagger:alias`)
rxName = regexp.MustCompile(`swagger:name\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}\.]+)$`)
rxAllOf = regexp.MustCompile(`swagger:allOf\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}\.]+)?$`)
rxModelOverride = regexp.MustCompile(`swagger:model\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)?$`)
rxResponseOverride = regexp.MustCompile(`swagger:response\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)?$`)
rxParametersOverride = regexp.MustCompile(`swagger:parameters\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}\p{Zs}]+)$`)
rxEnum = regexp.MustCompile(`swagger:enum\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
rxIgnoreOverride = regexp.MustCompile(`swagger:ignore\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)?$`)
rxDefault = regexp.MustCompile(`swagger:default\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
rxType = regexp.MustCompile(`swagger:type\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
rxRoute = regexp.MustCompile(
"swagger:route\\p{Zs}*" +
rxMethod +
"\\p{Zs}*" +
rxPath +
"(?:\\p{Zs}+" +
rxOpTags +
")?\\p{Zs}+" +
rxOpID + "\\p{Zs}*$")
rxBeginYAMLSpec = regexp.MustCompile(`---\p{Zs}*$`)
rxUncommentHeaders = regexp.MustCompile(`^[\p{Zs}\t/\*-]*\|?`)
rxUncommentYAML = regexp.MustCompile(`^[\p{Zs}\t]*/*`)
rxOperation = regexp.MustCompile(
"swagger:operation\\p{Zs}*" +
rxMethod +
"\\p{Zs}*" +
rxPath +
"(?:\\p{Zs}+" +
rxOpTags +
")?\\p{Zs}+" +
rxOpID + "\\p{Zs}*$")
rxSpace = regexp.MustCompile(`\p{Zs}+`)
rxIndent = regexp.MustCompile(`\p{Zs}*/*\p{Zs}*[^\p{Zs}]`)
rxPunctuationEnd = regexp.MustCompile(`\p{Po}$`)
rxStripComments = regexp.MustCompile(`^[^\p{L}\p{N}\p{Pd}\p{Pc}\+]*`)
rxStripTitleComments = regexp.MustCompile(`^[^\p{L}]*[Pp]ackage\p{Zs}+[^\p{Zs}]+\p{Zs}*`)
rxAllowedExtensions = regexp.MustCompile(`^[Xx]-`)
rxIn = regexp.MustCompile(`[Ii]n\p{Zs}*:\p{Zs}*(query|path|header|body|formData)$`)
rxRequired = regexp.MustCompile(`[Rr]equired\p{Zs}*:\p{Zs}*(true|false)$`)
rxDiscriminator = regexp.MustCompile(`[Dd]iscriminator\p{Zs}*:\p{Zs}*(true|false)$`)
rxReadOnly = regexp.MustCompile(`[Rr]ead(?:\p{Zs}*|[\p{Pd}\p{Pc}])?[Oo]nly\p{Zs}*:\p{Zs}*(true|false)$`)
rxConsumes = regexp.MustCompile(`[Cc]onsumes\p{Zs}*:`)
rxProduces = regexp.MustCompile(`[Pp]roduces\p{Zs}*:`)
rxSecuritySchemes = regexp.MustCompile(`[Ss]ecurity\p{Zs}*:`)
rxSecurity = regexp.MustCompile(`[Ss]ecurity\p{Zs}*[Dd]efinitions:`)
rxResponses = regexp.MustCompile(`[Rr]esponses\p{Zs}*:`)
rxParameters = regexp.MustCompile(`[Pp]arameters\p{Zs}*:`)
rxSchemes = regexp.MustCompile(`[Ss]chemes\p{Zs}*:\p{Zs}*((?:(?:https?|HTTPS?|wss?|WSS?)[\p{Zs},]*)+)$`)
rxVersion = regexp.MustCompile(`[Vv]ersion\p{Zs}*:\p{Zs}*(.+)$`)
rxHost = regexp.MustCompile(`[Hh]ost\p{Zs}*:\p{Zs}*(.+)$`)
rxBasePath = regexp.MustCompile(`[Bb]ase\p{Zs}*-*[Pp]ath\p{Zs}*:\p{Zs}*` + rxPath + "$")
rxLicense = regexp.MustCompile(`[Ll]icense\p{Zs}*:\p{Zs}*(.+)$`)
rxContact = regexp.MustCompile(`[Cc]ontact\p{Zs}*-?(?:[Ii]info\p{Zs}*)?:\p{Zs}*(.+)$`)
rxTOS = regexp.MustCompile(`[Tt](:?erms)?\p{Zs}*-?[Oo]f?\p{Zs}*-?[Ss](?:ervice)?\p{Zs}*:`)
rxExtensions = regexp.MustCompile(`[Ee]xtensions\p{Zs}*:`)
rxInfoExtensions = regexp.MustCompile(`[In]nfo\p{Zs}*[Ee]xtensions:`)
// currently unused: rxExample = regexp.MustCompile(`[Ex]ample\p{Zs}*:\p{Zs}*(.*)$`)
)
// Many thanks go to https://github.com/yvasiyarov/swagger
// this is loosely based on that implementation but for swagger 2.0
func joinDropLast(lines []string) string {
l := len(lines)
lns := lines
if l > 0 && len(strings.TrimSpace(lines[l-1])) == 0 {
lns = lines[:l-1]
}
return strings.Join(lns, "\n")
}
func removeEmptyLines(lines []string) (notEmpty []string) {
for _, l := range lines {
if len(strings.TrimSpace(l)) > 0 {
notEmpty = append(notEmpty, l)
}
}
return
}
func rxf(rxp, ar string) *regexp.Regexp {
return regexp.MustCompile(fmt.Sprintf(rxp, ar))
}
// The Opts for the application scanner.
type Opts struct {
BasePath string
Input *spec.Swagger
ScanModels bool
BuildTags string
Include []string
Exclude []string
IncludeTags []string
ExcludeTags []string
}
func safeConvert(str string) bool {
b, err := swag.ConvertBool(str)
if err != nil {
return false
}
return b
}
// Debug is true when process is run with DEBUG=1 env var
var Debug = safeConvert(os.Getenv("DEBUG"))
// Application scans the application and builds a swagger spec based on the information from the code files.
// When there are includes provided, only those files are considered for the initial discovery.
// Similarly the excludes will exclude an item from initial discovery through scanning for annotations.
// When something in the discovered items requires a type that is contained in the includes or excludes it will still be
// in the spec.
func Application(opts Opts) (*spec.Swagger, error) {
parser, err := newAppScanner(&opts)
if err != nil {
return nil, err
}
return parser.Parse()
}
// appScanner the global context for scanning a go application
// into a swagger specification
type appScanner struct {
loader *loader.Config
prog *loader.Program
classifier *programClassifier
discovered []schemaDecl
input *spec.Swagger
definitions map[string]spec.Schema
responses map[string]spec.Response
operations map[string]*spec.Operation
scanModels bool
includeTags map[string]bool
excludeTas map[string]bool
// MainPackage the path to find the main class in
MainPackage string
}
// newAppScanner creates a new api parser
func newAppScanner(opts *Opts) (*appScanner, error) {
if Debug {
log.Println("scanning packages discovered through entrypoint @ ", opts.BasePath)
}
var ldr loader.Config
ldr.ParserMode = goparser.ParseComments
ldr.Import(opts.BasePath)
if opts.BuildTags != "" {
ldr.Build = &build.Default
ldr.Build.BuildTags = strings.Split(opts.BuildTags, ",")
}
ldr.TypeChecker = types.Config{FakeImportC: true}
prog, err := ldr.Load()
if err != nil {
return nil, err
}
var includes, excludes packageFilters
if len(opts.Include) > 0 {
for _, include := range opts.Include {
includes = append(includes, packageFilter{Name: include})
}
}
if len(opts.Exclude) > 0 {
for _, exclude := range opts.Exclude {
excludes = append(excludes, packageFilter{Name: exclude})
}
}
includeTags := make(map[string]bool)
for _, includeTag := range opts.IncludeTags {
includeTags[includeTag] = true
}
excludeTags := make(map[string]bool)
for _, excludeTag := range opts.ExcludeTags {
excludeTags[excludeTag] = true
}
input := opts.Input
if input == nil {
input = new(spec.Swagger)
input.Swagger = "2.0"
}
if input.Paths == nil {
input.Paths = new(spec.Paths)
}
if input.Definitions == nil {
input.Definitions = make(map[string]spec.Schema)
}
if input.Responses == nil {
input.Responses = make(map[string]spec.Response)
}
if input.Extensions == nil {
input.Extensions = make(spec.Extensions)
}
return &appScanner{
MainPackage: opts.BasePath,
prog: prog,
input: input,
loader: &ldr,
operations: collectOperationsFromInput(input),
definitions: input.Definitions,
responses: input.Responses,
scanModels: opts.ScanModels,
classifier: &programClassifier{
Includes: includes,
Excludes: excludes,
},
includeTags: includeTags,
excludeTas: excludeTags,
}, nil
}
func collectOperationsFromInput(input *spec.Swagger) map[string]*spec.Operation {
operations := make(map[string]*spec.Operation)
if input != nil && input.Paths != nil {
for _, pth := range input.Paths.Paths {
if pth.Get != nil {
operations[pth.Get.ID] = pth.Get
}
if pth.Post != nil {
operations[pth.Post.ID] = pth.Post
}
if pth.Put != nil {
operations[pth.Put.ID] = pth.Put
}
if pth.Patch != nil {
operations[pth.Patch.ID] = pth.Patch
}
if pth.Delete != nil {
operations[pth.Delete.ID] = pth.Delete
}
if pth.Head != nil {
operations[pth.Head.ID] = pth.Head
}
if pth.Options != nil {
operations[pth.Options.ID] = pth.Options
}
}
}
return operations
}
// Parse produces a swagger object for an application
func (a *appScanner) Parse() (*spec.Swagger, error) {
// classification still includes files that are completely commented out
cp, err := a.classifier.Classify(a.prog)
if err != nil {
return nil, err
}
// build models dictionary
if a.scanModels {
for _, modelsFile := range cp.Models {
if err := a.parseSchema(modelsFile); err != nil {
return nil, err
}
}
}
// build parameters dictionary
for _, paramsFile := range cp.Parameters {
if err := a.parseParameters(paramsFile); err != nil {
return nil, err
}
}
// build responses dictionary
for _, responseFile := range cp.Responses {
if err := a.parseResponses(responseFile); err != nil {
return nil, err
}
}
// build definitions dictionary
if err := a.processDiscovered(); err != nil {
return nil, err
}
// build paths dictionary
for _, routeFile := range cp.Routes {
if err := a.parseRoutes(routeFile); err != nil {
return nil, err
}
}
for _, operationFile := range cp.Operations {
if err := a.parseOperations(operationFile); err != nil {
return nil, err
}
}
// build swagger object
for _, metaFile := range cp.Meta {
if err := a.parseMeta(metaFile); err != nil {
return nil, err
}
}
if a.input.Swagger == "" {
a.input.Swagger = "2.0"
}
return a.input, nil
}
func (a *appScanner) processDiscovered() error {
// loop over discovered until all the items are in definitions
keepGoing := len(a.discovered) > 0
for keepGoing {
var queue []schemaDecl
for _, d := range a.discovered {
if _, ok := a.definitions[d.Name]; !ok {
queue = append(queue, d)
}
}
a.discovered = nil
for _, sd := range queue {
if err := a.parseDiscoveredSchema(sd); err != nil {
return err
}
}
keepGoing = len(a.discovered) > 0
}
return nil
}
func (a *appScanner) parseSchema(file *ast.File) error {
sp := newSchemaParser(a.prog)
if err := sp.Parse(file, a.definitions); err != nil {
return err
}
a.discovered = append(a.discovered, sp.postDecls...)
return nil
}
func (a *appScanner) parseDiscoveredSchema(sd schemaDecl) error {
sp := newSchemaParser(a.prog)
sp.discovered = &sd
if err := sp.Parse(sd.File, a.definitions); err != nil {
return err
}
a.discovered = append(a.discovered, sp.postDecls...)
return nil
}
func (a *appScanner) parseRoutes(file *ast.File) error {
rp := newRoutesParser(a.prog)
rp.operations = a.operations
rp.definitions = a.definitions
rp.responses = a.responses
return rp.Parse(file, a.input.Paths, a.includeTags, a.excludeTas)
}
func (a *appScanner) parseOperations(file *ast.File) error {
op := newOperationsParser(a.prog)
op.operations = a.operations
op.definitions = a.definitions
op.responses = a.responses
return op.Parse(file, a.input.Paths, a.includeTags, a.excludeTas)
}
func (a *appScanner) parseParameters(file *ast.File) error {
rp := newParameterParser(a.prog)
if err := rp.Parse(file, a.operations); err != nil {
return err
}
a.discovered = append(a.discovered, rp.postDecls...)
a.discovered = append(a.discovered, rp.scp.postDecls...)
return nil
}
func (a *appScanner) parseResponses(file *ast.File) error {
rp := newResponseParser(a.prog)
if err := rp.Parse(file, a.responses); err != nil {
return err
}
a.discovered = append(a.discovered, rp.postDecls...)
a.discovered = append(a.discovered, rp.scp.postDecls...)
return nil
}
func (a *appScanner) parseMeta(file *ast.File) error {
return newMetaParser(a.input).Parse(file.Doc)
}
// MustExpandPackagePath gets the real package path on disk
func (a *appScanner) MustExpandPackagePath(packagePath string) string {
pkgRealpath := swag.FindInGoSearchPath(packagePath)
if pkgRealpath == "" {
log.Fatalf("Can't find package %s \n", packagePath)
}
return pkgRealpath
}
type swaggerTypable interface {
Typed(string, string)
SetRef(spec.Ref)
Items() swaggerTypable
WithEnum(...interface{})
Schema() *spec.Schema
Level() int
}
// Map all Go builtin types that have Json representation to Swagger/Json types.
// See https://golang.org/pkg/builtin/ and http://swagger.io/specification/
func swaggerSchemaForType(typeName string, prop swaggerTypable) error {
switch typeName {
case "bool":
prop.Typed("boolean", "")
case "byte":
prop.Typed("integer", "uint8")
case "complex128", "complex64":
return fmt.Errorf("unsupported builtin %q (no JSON marshaller)", typeName)
case "error":
// TODO: error is often marshalled into a string but not always (e.g. errors package creates
// errors that are marshalled into an empty object), this could be handled the same way
// custom JSON marshallers are handled (in future)
prop.Typed("string", "")
case "float32":
prop.Typed("number", "float")
case "float64":
prop.Typed("number", "double")
case "int":
prop.Typed("integer", "int64")
case "int16":
prop.Typed("integer", "int16")
case "int32":
prop.Typed("integer", "int32")
case "int64":
prop.Typed("integer", "int64")
case "int8":
prop.Typed("integer", "int8")
case "rune":
prop.Typed("integer", "int32")
case "string":
prop.Typed("string", "")
case "uint":
prop.Typed("integer", "uint64")
case "uint16":
prop.Typed("integer", "uint16")
case "uint32":
prop.Typed("integer", "uint32")
case "uint64":
prop.Typed("integer", "uint64")
case "uint8":
prop.Typed("integer", "uint8")
case "uintptr":
prop.Typed("integer", "uint64")
default:
return fmt.Errorf("unsupported type %q", typeName)
}
return nil
}
func newMultiLineTagParser(name string, parser valueParser, skipCleanUp bool) tagParser {
return tagParser{
Name: name,
MultiLine: true,
SkipCleanUp: skipCleanUp,
Parser: parser,
}
}
func newSingleLineTagParser(name string, parser valueParser) tagParser {
return tagParser{
Name: name,
MultiLine: false,
SkipCleanUp: false,
Parser: parser,
}
}
type tagParser struct {
Name string
MultiLine bool
SkipCleanUp bool
Lines []string
Parser valueParser
}
func (st *tagParser) Matches(line string) bool {
return st.Parser.Matches(line)
}
func (st *tagParser) Parse(lines []string) error {
return st.Parser.Parse(lines)
}
func newYamlParser(rx *regexp.Regexp, setter func(json.RawMessage) error) valueParser {
return &yamlParser{
set: setter,
rx: rx,
}
}
type yamlParser struct {
set func(json.RawMessage) error
rx *regexp.Regexp
}
func (y *yamlParser) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
var uncommented []string
uncommented = append(uncommented, removeYamlIndent(lines)...)
yamlContent := strings.Join(uncommented, "\n")
var yamlValue interface{}
err := yaml.Unmarshal([]byte(yamlContent), &yamlValue)
if err != nil {
return err
}
var jsonValue json.RawMessage
jsonValue, err = fmts.YAMLToJSON(yamlValue)
if err != nil {
return err
}
return y.set(jsonValue)
}
func (y *yamlParser) Matches(line string) bool {
return y.rx.MatchString(line)
}
// aggregates lines in header until it sees `---`,
// the beginning of a YAML spec
type yamlSpecScanner struct {
header []string
yamlSpec []string
setTitle func([]string)
setDescription func([]string)
workedOutTitle bool
title []string
skipHeader bool
}
func cleanupScannerLines(lines []string, ur *regexp.Regexp, yamlBlock *regexp.Regexp) []string {
// bail early when there is nothing to parse
if len(lines) == 0 {
return lines
}
seenLine := -1
var lastContent int
var uncommented []string
var startBlock bool
var yaml []string
for i, v := range lines {
if yamlBlock != nil && yamlBlock.MatchString(v) && !startBlock {
startBlock = true
if seenLine < 0 {
seenLine = i
}
continue
}
if startBlock {
if yamlBlock.MatchString(v) {
startBlock = false
uncommented = append(uncommented, removeIndent(yaml)...)
continue
}
yaml = append(yaml, v)
if v != "" {
if seenLine < 0 {
seenLine = i
}
lastContent = i
}
continue
}
str := ur.ReplaceAllString(v, "")
uncommented = append(uncommented, str)
if str != "" {
if seenLine < 0 {
seenLine = i
}
lastContent = i
}
}
// fixes issue #50
if seenLine == -1 {
return nil
}
return uncommented[seenLine : lastContent+1]
}
// a shared function that can be used to split given headers
// into a title and description
func collectScannerTitleDescription(headers []string) (title, desc []string) {
hdrs := cleanupScannerLines(headers, rxUncommentHeaders, nil)
idx := -1
for i, line := range hdrs {
if strings.TrimSpace(line) == "" {
idx = i
break
}
}
if idx > -1 {
title = hdrs[:idx]
if len(hdrs) > idx+1 {
desc = hdrs[idx+1:]
} else {
desc = nil
}
return
}
if len(hdrs) > 0 {
line := hdrs[0]
if rxPunctuationEnd.MatchString(line) {
title = []string{line}
desc = hdrs[1:]
} else {
desc = hdrs
}
}
return
}
func (sp *yamlSpecScanner) collectTitleDescription() {
if sp.workedOutTitle {
return
}
if sp.setTitle == nil {
sp.header = cleanupScannerLines(sp.header, rxUncommentHeaders, nil)
return
}
sp.workedOutTitle = true
sp.title, sp.header = collectScannerTitleDescription(sp.header)
}
func (sp *yamlSpecScanner) Title() []string {
sp.collectTitleDescription()
return sp.title
}
func (sp *yamlSpecScanner) Description() []string {
sp.collectTitleDescription()
return sp.header
}
func (sp *yamlSpecScanner) Parse(doc *ast.CommentGroup) error {
if doc == nil {
return nil
}
var startedYAMLSpec bool
COMMENTS:
for _, c := range doc.List {
for _, line := range strings.Split(c.Text, "\n") {
if rxSwaggerAnnotation.MatchString(line) {
break COMMENTS // a new swagger: annotation terminates this parser
}
if !startedYAMLSpec {
if rxBeginYAMLSpec.MatchString(line) {
startedYAMLSpec = true
sp.yamlSpec = append(sp.yamlSpec, line)
continue
}
if !sp.skipHeader {
sp.header = append(sp.header, line)
}
// no YAML spec yet, moving on
continue
}
sp.yamlSpec = append(sp.yamlSpec, line)
}
}
if sp.setTitle != nil {
sp.setTitle(sp.Title())
}
if sp.setDescription != nil {
sp.setDescription(sp.Description())
}
return nil
}
func (sp *yamlSpecScanner) UnmarshalSpec(u func([]byte) error) (err error) {
spec := cleanupScannerLines(sp.yamlSpec, rxUncommentYAML, nil)
if len(spec) == 0 {
return errors.New("no spec available to unmarshal")
}
if !strings.Contains(spec[0], "---") {
return errors.New("yaml spec has to start with `---`")
}
// remove indentation
spec = removeIndent(spec)
// 1. parse yaml lines
yamlValue := make(map[interface{}]interface{})
yamlContent := strings.Join(spec, "\n")
err = yaml.Unmarshal([]byte(yamlContent), &yamlValue)
if err != nil {
return
}
// 2. convert to json
var jsonValue json.RawMessage
jsonValue, err = fmts.YAMLToJSON(yamlValue)
if err != nil {
return
}
// 3. unmarshal the json into an interface
var data []byte
data, err = jsonValue.MarshalJSON()
if err != nil {
return
}
err = u(data)
if err != nil {
return
}
// all parsed, returning...
sp.yamlSpec = nil // spec is now consumed, so let's erase the parsed lines
return
}
// removes indent base on the first line
func removeIndent(spec []string) []string {
loc := rxIndent.FindStringIndex(spec[0])
if loc[1] > 0 {
for i := range spec {
if len(spec[i]) >= loc[1] {
spec[i] = spec[i][loc[1]-1:]
}
}
}
return spec
}
// removes indent base on the first line
func removeYamlIndent(spec []string) []string {
loc := rxIndent.FindStringIndex(spec[0])
var s []string
if loc[1] > 0 {
for i := range spec {
if len(spec[i]) >= loc[1] {
s = append(s, spec[i][loc[1]-1:])
}
}
}
return s
}
// aggregates lines in header until it sees a tag.
type sectionedParser struct {
header []string
matched map[string]tagParser
annotation valueParser
seenTag bool
skipHeader bool
setTitle func([]string)
setDescription func([]string)
workedOutTitle bool
taggers []tagParser
currentTagger *tagParser
title []string
ignored bool
}
func (st *sectionedParser) collectTitleDescription() {
if st.workedOutTitle {
return
}
if st.setTitle == nil {
st.header = cleanupScannerLines(st.header, rxUncommentHeaders, nil)
return
}
st.workedOutTitle = true
st.title, st.header = collectScannerTitleDescription(st.header)
}
func (st *sectionedParser) Title() []string {
st.collectTitleDescription()
return st.title
}
func (st *sectionedParser) Description() []string {
st.collectTitleDescription()
return st.header
}
func (st *sectionedParser) Parse(doc *ast.CommentGroup) error {
if doc == nil {
return nil
}
COMMENTS:
for _, c := range doc.List {
for _, line := range strings.Split(c.Text, "\n") {
if rxSwaggerAnnotation.MatchString(line) {
if rxIgnoreOverride.MatchString(line) {
st.ignored = true
break COMMENTS // an explicit ignore terminates this parser
}
if st.annotation == nil || !st.annotation.Matches(line) {
break COMMENTS // a new swagger: annotation terminates this parser
}
_ = st.annotation.Parse([]string{line})
if len(st.header) > 0 {
st.seenTag = true
}
continue
}
var matched bool
for _, tagger := range st.taggers {
if tagger.Matches(line) {
st.seenTag = true
st.currentTagger = &tagger
matched = true
break
}
}
if st.currentTagger == nil {
if !st.skipHeader && !st.seenTag {
st.header = append(st.header, line)
}
// didn't match a tag, moving on
continue
}
if st.currentTagger.MultiLine && matched {
// the first line of a multiline tagger doesn't count
continue
}
ts, ok := st.matched[st.currentTagger.Name]
if !ok {
ts = *st.currentTagger
}
ts.Lines = append(ts.Lines, line)
if st.matched == nil {
st.matched = make(map[string]tagParser)
}
st.matched[st.currentTagger.Name] = ts
if !st.currentTagger.MultiLine {
st.currentTagger = nil
}
}
}
if st.setTitle != nil {
st.setTitle(st.Title())
}
if st.setDescription != nil {
st.setDescription(st.Description())
}
for _, mt := range st.matched {
if !mt.SkipCleanUp {
mt.Lines = cleanupScannerLines(mt.Lines, rxUncommentHeaders, nil)
}
if err := mt.Parse(mt.Lines); err != nil {
return err
}
}
return nil
}
type vendorExtensibleParser struct {
setExtensions func(ext spec.Extensions, dest interface{})
}
func (extParser vendorExtensibleParser) ParseInto(dest interface{}) func(json.RawMessage) error {
return func(jsonValue json.RawMessage) error {
var jsonData spec.Extensions
err := json.Unmarshal(jsonValue, &jsonData)
if err != nil {
return err
}
for k := range jsonData {
if !rxAllowedExtensions.MatchString(k) {
return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k)
}
}
extParser.setExtensions(jsonData, dest)
return nil
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,829 +0,0 @@
//go:build !go1.11
// +build !go1.11
// Copyright 2015 go-swagger maintainers
//
// 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 scan
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/go-openapi/spec"
)
type validationBuilder interface {
SetMaximum(float64, bool)
SetMinimum(float64, bool)
SetMultipleOf(float64)
SetMinItems(int64)
SetMaxItems(int64)
SetMinLength(int64)
SetMaxLength(int64)
SetPattern(string)
SetUnique(bool)
SetEnum(string)
SetDefault(interface{})
SetExample(interface{})
}
type valueParser interface {
Parse([]string) error
Matches(string) bool
}
type setMaximum struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMaximum) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 2 && len(matches[2]) > 0 {
max, err := strconv.ParseFloat(matches[2], 64)
if err != nil {
return err
}
sm.builder.SetMaximum(max, matches[1] == "<")
}
return nil
}
func (sm *setMaximum) Matches(line string) bool {
return sm.rx.MatchString(line)
}
type setMinimum struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMinimum) Matches(line string) bool {
return sm.rx.MatchString(line)
}
func (sm *setMinimum) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 2 && len(matches[2]) > 0 {
min, err := strconv.ParseFloat(matches[2], 64)
if err != nil {
return err
}
sm.builder.SetMinimum(min, matches[1] == ">")
}
return nil
}
type setMultipleOf struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMultipleOf) Matches(line string) bool {
return sm.rx.MatchString(line)
}
func (sm *setMultipleOf) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 2 && len(matches[1]) > 0 {
multipleOf, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return err
}
sm.builder.SetMultipleOf(multipleOf)
}
return nil
}
type setMaxItems struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMaxItems) Matches(line string) bool {
return sm.rx.MatchString(line)
}
func (sm *setMaxItems) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
maxItems, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return err
}
sm.builder.SetMaxItems(maxItems)
}
return nil
}
type setMinItems struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMinItems) Matches(line string) bool {
return sm.rx.MatchString(line)
}
func (sm *setMinItems) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
minItems, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return err
}
sm.builder.SetMinItems(minItems)
}
return nil
}
type setMaxLength struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMaxLength) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
maxLength, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return err
}
sm.builder.SetMaxLength(maxLength)
}
return nil
}
func (sm *setMaxLength) Matches(line string) bool {
return sm.rx.MatchString(line)
}
type setMinLength struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setMinLength) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
minLength, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return err
}
sm.builder.SetMinLength(minLength)
}
return nil
}
func (sm *setMinLength) Matches(line string) bool {
return sm.rx.MatchString(line)
}
type setPattern struct {
builder validationBuilder
rx *regexp.Regexp
}
func (sm *setPattern) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
sm.builder.SetPattern(matches[1])
}
return nil
}
func (sm *setPattern) Matches(line string) bool {
return sm.rx.MatchString(line)
}
type setCollectionFormat struct {
builder operationValidationBuilder
rx *regexp.Regexp
}
func (sm *setCollectionFormat) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sm.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
sm.builder.SetCollectionFormat(matches[1])
}
return nil
}
func (sm *setCollectionFormat) Matches(line string) bool {
return sm.rx.MatchString(line)
}
type setUnique struct {
builder validationBuilder
rx *regexp.Regexp
}
func (su *setUnique) Matches(line string) bool {
return su.rx.MatchString(line)
}
func (su *setUnique) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := su.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
req, err := strconv.ParseBool(matches[1])
if err != nil {
return err
}
su.builder.SetUnique(req)
}
return nil
}
type setEnum struct {
builder validationBuilder
rx *regexp.Regexp
}
func (se *setEnum) Matches(line string) bool {
return se.rx.MatchString(line)
}
func (se *setEnum) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := se.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
se.builder.SetEnum(matches[1])
}
return nil
}
func parseValueFromSchema(s string, schema *spec.SimpleSchema) (interface{}, error) {
if schema != nil {
switch strings.Trim(schema.TypeName(), "\"") {
case "integer", "int", "int64", "int32", "int16":
return strconv.Atoi(s)
case "bool", "boolean":
return strconv.ParseBool(s)
case "number", "float64", "float32":
return strconv.ParseFloat(s, 64)
case "object":
var obj map[string]interface{}
if err := json.Unmarshal([]byte(s), &obj); err != nil {
// If we can't parse it, just return the string.
return s, nil
}
return obj, nil
case "array":
var slice []interface{}
if err := json.Unmarshal([]byte(s), &slice); err != nil {
// If we can't parse it, just return the string.
return s, nil
}
return slice, nil
default:
return s, nil
}
} else {
return s, nil
}
}
type setDefault struct {
scheme *spec.SimpleSchema
builder validationBuilder
rx *regexp.Regexp
}
func (sd *setDefault) Matches(line string) bool {
return sd.rx.MatchString(line)
}
func (sd *setDefault) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := sd.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
d, err := parseValueFromSchema(matches[1], sd.scheme)
if err != nil {
return err
}
sd.builder.SetDefault(d)
}
return nil
}
type setExample struct {
scheme *spec.SimpleSchema
builder validationBuilder
rx *regexp.Regexp
}
func (se *setExample) Matches(line string) bool {
return se.rx.MatchString(line)
}
func (se *setExample) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := se.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
d, err := parseValueFromSchema(matches[1], se.scheme)
if err != nil {
return err
}
se.builder.SetExample(d)
}
return nil
}
type matchOnlyParam struct {
tgt *spec.Parameter
rx *regexp.Regexp
}
func (mo *matchOnlyParam) Matches(line string) bool {
return mo.rx.MatchString(line)
}
func (mo *matchOnlyParam) Parse(lines []string) error {
return nil
}
type setRequiredParam struct {
tgt *spec.Parameter
}
func (su *setRequiredParam) Matches(line string) bool {
return rxRequired.MatchString(line)
}
func (su *setRequiredParam) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := rxRequired.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
req, err := strconv.ParseBool(matches[1])
if err != nil {
return err
}
su.tgt.Required = req
}
return nil
}
type setReadOnlySchema struct {
tgt *spec.Schema
}
func (su *setReadOnlySchema) Matches(line string) bool {
return rxReadOnly.MatchString(line)
}
func (su *setReadOnlySchema) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := rxReadOnly.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
req, err := strconv.ParseBool(matches[1])
if err != nil {
return err
}
su.tgt.ReadOnly = req
}
return nil
}
type setDiscriminator struct {
schema *spec.Schema
field string
}
func (su *setDiscriminator) Matches(line string) bool {
return rxDiscriminator.MatchString(line)
}
func (su *setDiscriminator) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := rxDiscriminator.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
req, err := strconv.ParseBool(matches[1])
if err != nil {
return err
}
if req {
su.schema.Discriminator = su.field
} else {
if su.schema.Discriminator == su.field {
su.schema.Discriminator = ""
}
}
}
return nil
}
type setRequiredSchema struct {
schema *spec.Schema
field string
}
func (su *setRequiredSchema) Matches(line string) bool {
return rxRequired.MatchString(line)
}
func (su *setRequiredSchema) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := rxRequired.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
req, err := strconv.ParseBool(matches[1])
if err != nil {
return err
}
midx := -1
for i, nm := range su.schema.Required {
if nm == su.field {
midx = i
break
}
}
if req {
if midx < 0 {
su.schema.Required = append(su.schema.Required, su.field)
}
} else if midx >= 0 {
su.schema.Required = append(su.schema.Required[:midx], su.schema.Required[midx+1:]...)
}
}
return nil
}
func newMultilineDropEmptyParser(rx *regexp.Regexp, set func([]string)) *multiLineDropEmptyParser {
return &multiLineDropEmptyParser{
rx: rx,
set: set,
}
}
type multiLineDropEmptyParser struct {
set func([]string)
rx *regexp.Regexp
}
func (m *multiLineDropEmptyParser) Matches(line string) bool {
return m.rx.MatchString(line)
}
func (m *multiLineDropEmptyParser) Parse(lines []string) error {
m.set(removeEmptyLines(lines))
return nil
}
func newSetSchemes(set func([]string)) *setSchemes {
return &setSchemes{
set: set,
rx: rxSchemes,
}
}
type setSchemes struct {
set func([]string)
rx *regexp.Regexp
}
func (ss *setSchemes) Matches(line string) bool {
return ss.rx.MatchString(line)
}
func (ss *setSchemes) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
matches := ss.rx.FindStringSubmatch(lines[0])
if len(matches) > 1 && len(matches[1]) > 0 {
sch := strings.Split(matches[1], ", ")
var schemes []string
for _, s := range sch {
ts := strings.TrimSpace(s)
if ts != "" {
schemes = append(schemes, ts)
}
}
ss.set(schemes)
}
return nil
}
func newSetSecurity(rx *regexp.Regexp, setter func([]map[string][]string)) *setSecurity {
return &setSecurity{
set: setter,
rx: rx,
}
}
type setSecurity struct {
set func([]map[string][]string)
rx *regexp.Regexp
}
func (ss *setSecurity) Matches(line string) bool {
return ss.rx.MatchString(line)
}
func (ss *setSecurity) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
var result []map[string][]string
for _, line := range lines {
kv := strings.SplitN(line, ":", 2)
scopes := []string{}
var key string
if len(kv) > 1 {
scs := strings.Split(kv[1], ",")
for _, scope := range scs {
tr := strings.TrimSpace(scope)
if tr != "" {
tr = strings.SplitAfter(tr, " ")[0]
scopes = append(scopes, strings.TrimSpace(tr))
}
}
key = strings.TrimSpace(kv[0])
result = append(result, map[string][]string{key: scopes})
}
}
ss.set(result)
return nil
}
func newSetResponses(definitions map[string]spec.Schema, responses map[string]spec.Response, setter func(*spec.Response, map[int]spec.Response)) *setOpResponses {
return &setOpResponses{
set: setter,
rx: rxResponses,
definitions: definitions,
responses: responses,
}
}
type setOpResponses struct {
set func(*spec.Response, map[int]spec.Response)
rx *regexp.Regexp
definitions map[string]spec.Schema
responses map[string]spec.Response
}
func (ss *setOpResponses) Matches(line string) bool {
return ss.rx.MatchString(line)
}
// ResponseTag used when specifying a response to point to a defined swagger:response
const ResponseTag = "response"
// BodyTag used when specifying a response to point to a model/schema
const BodyTag = "body"
// DescriptionTag used when specifying a response that gives a description of the response
const DescriptionTag = "description"
func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef bool, description string, err error) {
tags := strings.Split(line, " ")
parsedModelOrResponse := false
for i, tagAndValue := range tags {
tagValList := strings.SplitN(tagAndValue, ":", 2)
var tag, value string
if len(tagValList) > 1 {
tag = tagValList[0]
value = tagValList[1]
} else {
//TODO: Print a warning, and in the long term, do not support not tagged values
//Add a default tag if none is supplied
if i == 0 {
tag = ResponseTag
} else {
tag = DescriptionTag
}
value = tagValList[0]
}
foundModelOrResponse := false
if !parsedModelOrResponse {
if tag == BodyTag {
foundModelOrResponse = true
isDefinitionRef = true
}
if tag == ResponseTag {
foundModelOrResponse = true
isDefinitionRef = false
}
}
if foundModelOrResponse {
//Read the model or response tag
parsedModelOrResponse = true
//Check for nested arrays
arrays = 0
for strings.HasPrefix(value, "[]") {
arrays++
value = value[2:]
}
//What's left over is the model name
modelOrResponse = value
} else {
foundDescription := false
if tag == DescriptionTag {
foundDescription = true
}
if foundDescription {
//Descriptions are special, they make they read the rest of the line
descriptionWords := []string{value}
if i < len(tags)-1 {
descriptionWords = append(descriptionWords, tags[i+1:]...)
}
description = strings.Join(descriptionWords, " ")
break
} else {
if tag == ResponseTag || tag == BodyTag || tag == DescriptionTag {
err = fmt.Errorf("Found valid tag %s, but not in a valid position", tag)
} else {
err = fmt.Errorf("Found invalid tag: %s", tag)
}
//return error
return
}
}
}
//TODO: Maybe do, if !parsedModelOrResponse {return some error}
return
}
func (ss *setOpResponses) Parse(lines []string) error {
if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
return nil
}
var def *spec.Response
var scr map[int]spec.Response
for _, line := range lines {
kv := strings.SplitN(line, ":", 2)
var key, value string
if len(kv) > 1 {
key = strings.TrimSpace(kv[0])
if key == "" {
// this must be some weird empty line
continue
}
value = strings.TrimSpace(kv[1])
if value == "" {
var resp spec.Response
if strings.EqualFold("default", key) {
if def == nil {
def = &resp
}
} else {
if sc, err := strconv.Atoi(key); err == nil {
if scr == nil {
scr = make(map[int]spec.Response)
}
scr[sc] = resp
}
}
continue
}
refTarget, arrays, isDefinitionRef, description, err := parseTags(value)
if err != nil {
return err
}
//A possible exception for having a definition
if _, ok := ss.responses[refTarget]; !ok {
if _, ok := ss.definitions[refTarget]; ok {
isDefinitionRef = true
}
}
var ref spec.Ref
if isDefinitionRef {
if description == "" {
description = refTarget
}
ref, err = spec.NewRef("#/definitions/" + refTarget)
} else {
ref, err = spec.NewRef("#/responses/" + refTarget)
}
if err != nil {
return err
}
// description should used on anyway.
resp := spec.Response{ResponseProps: spec.ResponseProps{Description: description}}
if isDefinitionRef {
resp.Schema = new(spec.Schema)
resp.Description = description
if arrays == 0 {
resp.Schema.Ref = ref
} else {
cs := resp.Schema
for i := 0; i < arrays; i++ {
cs.Typed("array", "")
cs.Items = new(spec.SchemaOrArray)
cs.Items.Schema = new(spec.Schema)
cs = cs.Items.Schema
}
cs.Ref = ref
}
// ref. could be empty while use description tag
} else if len(refTarget) > 0 {
resp.Ref = ref
}
if strings.EqualFold("default", key) {
if def == nil {
def = &resp
}
} else {
if sc, err := strconv.Atoi(key); err == nil {
if scr == nil {
scr = make(map[int]spec.Response)
}
scr[sc] = resp
}
}
}
}
ss.set(def, scr)
return nil
}
func parseEnum(val string, s *spec.SimpleSchema) []interface{} {
list := strings.Split(val, ",")
interfaceSlice := make([]interface{}, len(list))
for i, d := range list {
v, err := parseValueFromSchema(d, s)
if err != nil {
interfaceSlice[i] = d
continue
}
interfaceSlice[i] = v
}
return interfaceSlice
}