[Vendor] update go-swagger v0.21.0 -> v0.25.0 (#12670)

* Update go-swagger

* vendor
This commit is contained in:
6543 2020-09-01 16:01:23 +02:00 committed by GitHub
parent 66843f2237
commit 3270e7a443
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
350 changed files with 26353 additions and 5552 deletions

View file

@ -2,11 +2,14 @@ package commands
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"errors"
"github.com/go-openapi/loads"
"github.com/go-swagger/go-swagger/cmd/swagger/commands/diff"
)
@ -22,32 +25,46 @@ type DiffCommand struct {
Format string `long:"format" short:"f" description:"When present, writes output as json" default:"txt" choice:"txt" choice:"json"`
IgnoreFile string `long:"ignore" short:"i" description:"Exception file of diffs to ignore (copy output from json diff format)" default:"none specified"`
Destination string `long:"dest" short:"d" description:"Output destination file or stdout" default:"stdout"`
Args struct {
OldSpec string `positional-arg-name:"{old spec}"`
NewSpec string `positional-arg-name:"{new spec}"`
} `required:"2" positional-args:"specs" description:"Input specs to be diff-ed"`
}
// Execute diffs the two specs provided
func (c *DiffCommand) Execute(args []string) error {
if len(args) != 2 {
msg := `missing arguments for diff command (use --help for more info)`
return errors.New(msg)
func (c *DiffCommand) Execute(_ []string) error {
if c.Args.OldSpec == "" || c.Args.NewSpec == "" {
return errors.New(`missing arguments for diff command (use --help for more info)`)
}
log.Println("Run Config:")
log.Printf("Spec1: %s", args[0])
log.Printf("Spec2: %s", args[1])
log.Printf("ReportOnlyBreakingChanges (-c) :%v", c.OnlyBreakingChanges)
log.Printf("OutputFormat (-f) :%s", c.Format)
log.Printf("IgnoreFile (-i) :%s", c.IgnoreFile)
log.Printf("Diff Report Destination (-d) :%s", c.Destination)
c.printInfo()
diffs, err := getDiffs(args[0], args[1])
var (
output io.WriteCloser
err error
)
if c.Destination != "" {
output, err = os.OpenFile(c.Destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("%s: %w", c.Destination, err)
}
defer func() {
_ = output.Close()
}()
} else {
output = os.Stdout
}
diffs, err := c.getDiffs()
if err != nil {
return err
}
ignores, err := readIgnores(c.IgnoreFile)
ignores, err := c.readIgnores()
if err != nil {
return err
}
diffs = diffs.FilterIgnores(ignores)
if len(ignores) > 0 {
log.Printf("Diff Report Ignored Items from IgnoreFile")
@ -56,40 +73,44 @@ func (c *DiffCommand) Execute(args []string) error {
}
}
if c.Format == JSONFormat {
err = diffs.ReportAllDiffs(true)
if err != nil {
return err
}
var (
input io.Reader
warn error
)
if c.Format != JSONFormat && c.OnlyBreakingChanges {
input, err, warn = diffs.ReportCompatibility()
} else {
if c.OnlyBreakingChanges {
err = diffs.ReportCompatibility()
} else {
err = diffs.ReportAllDiffs(false)
}
input, err, warn = diffs.ReportAllDiffs(c.Format == JSONFormat)
}
return err
if err != nil {
return err
}
_, err = io.Copy(output, input)
if err != nil {
return err
}
return warn
}
func readIgnores(ignoreFile string) (diff.SpecDifferences, error) {
func (c *DiffCommand) readIgnores() (diff.SpecDifferences, error) {
ignoreFile := c.IgnoreFile
ignoreDiffs := diff.SpecDifferences{}
if ignoreFile == "none specified" {
if ignoreFile == "none specified" || ignoreFile == "" {
return ignoreDiffs, nil
}
// Open our jsonFile
jsonFile, err := os.Open(ignoreFile)
// if we os.Open returns an error then handle it
if err != nil {
return nil, err
return nil, fmt.Errorf("%s: %w", ignoreFile, err)
}
// defer the closing of our jsonFile so that we can parse it later on
defer jsonFile.Close()
defer func() {
_ = jsonFile.Close()
}()
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
return nil, err
return nil, fmt.Errorf("reading %s: %w", ignoreFile, err)
}
// def
err = json.Unmarshal(byteValue, &ignoreDiffs)
if err != nil {
return nil, err
@ -97,10 +118,10 @@ func readIgnores(ignoreFile string) (diff.SpecDifferences, error) {
return ignoreDiffs, nil
}
func getDiffs(oldSpecPath, newSpecPath string) (diff.SpecDifferences, error) {
func (c *DiffCommand) getDiffs() (diff.SpecDifferences, error) {
oldSpecPath, newSpecPath := c.Args.OldSpec, c.Args.NewSpec
swaggerDoc1 := oldSpecPath
specDoc1, err := loads.Spec(swaggerDoc1)
if err != nil {
return nil, err
}
@ -113,3 +134,13 @@ func getDiffs(oldSpecPath, newSpecPath string) (diff.SpecDifferences, error) {
return diff.Compare(specDoc1.Spec(), specDoc2.Spec())
}
func (c *DiffCommand) printInfo() {
log.Println("Run Config:")
log.Printf("Spec1: %s", c.Args.OldSpec)
log.Printf("Spec2: %s", c.Args.NewSpec)
log.Printf("ReportOnlyBreakingChanges (-c) :%v", c.OnlyBreakingChanges)
log.Printf("OutputFormat (-f) :%s", c.Format)
log.Printf("IgnoreFile (-i) :%s", c.IgnoreFile)
log.Printf("Diff Report Destination (-d) :%s", c.Destination)
}

View file

@ -2,22 +2,29 @@ package diff
// This is a simple DSL for diffing arrays
// FromArrayStruct utility struct to encompass diffing of string arrays
type FromArrayStruct struct {
// fromArrayStruct utility struct to encompass diffing of string arrays
type fromArrayStruct struct {
from []string
}
// FromStringArray starts a fluent diff expression
func FromStringArray(from []string) FromArrayStruct {
return FromArrayStruct{from}
// fromStringArray starts a fluent diff expression
func fromStringArray(from []string) fromArrayStruct {
return fromArrayStruct{from}
}
// DiffsTo completes a fluent dff expression
func (f FromArrayStruct) DiffsTo(toArray []string) (added, deleted, common []string) {
// DiffsTo completes a fluent diff expression
func (f fromArrayStruct) DiffsTo(toArray []string) (added, deleted, common []string) {
inFrom := 1
inTo := 2
m := make(map[string]int)
if f.from == nil {
return toArray, []string{}, []string{}
}
m := make(map[string]int, len(toArray))
added = make([]string, 0, len(toArray))
deleted = make([]string, 0, len(f.from))
common = make([]string, 0, len(f.from))
for _, item := range f.from {
m[item] = inFrom
@ -43,14 +50,14 @@ func (f FromArrayStruct) DiffsTo(toArray []string) (added, deleted, common []str
return
}
// FromMapStruct utility struct to encompass diffing of string arrays
type FromMapStruct struct {
// fromMapStruct utility struct to encompass diffing of string arrays
type fromMapStruct struct {
srcMap map[string]interface{}
}
// FromStringMap starts a comparison by declaring a source map
func FromStringMap(srcMap map[string]interface{}) FromMapStruct {
return FromMapStruct{srcMap}
// fromStringMap starts a comparison by declaring a source map
func fromStringMap(srcMap map[string]interface{}) fromMapStruct {
return fromMapStruct{srcMap}
}
// Pair stores a pair of items which share a key in two maps
@ -60,7 +67,7 @@ type Pair struct {
}
// DiffsTo - generates diffs for a comparison
func (f FromMapStruct) DiffsTo(destMap map[string]interface{}) (added, deleted, common map[string]interface{}) {
func (f fromMapStruct) DiffsTo(destMap map[string]interface{}) (added, deleted, common map[string]interface{}) {
added = make(map[string]interface{})
deleted = make(map[string]interface{})
common = make(map[string]interface{})

View file

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"log"
)
// SpecChangeCode enumerates the various types of diffs from one spec to another
@ -175,18 +176,18 @@ var toStringSpecChangeCode = map[SpecChangeCode]string{
var toIDSpecChangeCode = map[string]SpecChangeCode{}
// Description returns an english version of this error
func (s *SpecChangeCode) Description() (result string) {
result, ok := toLongStringSpecChangeCode[*s]
func (s SpecChangeCode) Description() (result string) {
result, ok := toLongStringSpecChangeCode[s]
if !ok {
fmt.Printf("WARNING: No description for %v", *s)
log.Printf("warning: No description for %v", s)
result = "UNDEFINED"
}
return
}
// MarshalJSON marshals the enum as a quoted json string
func (s *SpecChangeCode) MarshalJSON() ([]byte, error) {
return stringAsQuotedBytes(toStringSpecChangeCode[*s])
func (s SpecChangeCode) MarshalJSON() ([]byte, error) {
return stringAsQuotedBytes(toStringSpecChangeCode[s])
}
// UnmarshalJSON unmashalls a quoted json string to the enum value
@ -228,8 +229,8 @@ var toStringCompatibility = map[Compatibility]string{
var toIDCompatibility = map[string]Compatibility{}
// MarshalJSON marshals the enum as a quoted json string
func (s *Compatibility) MarshalJSON() ([]byte, error) {
return stringAsQuotedBytes(toStringCompatibility[*s])
func (s Compatibility) MarshalJSON() ([]byte, error) {
return stringAsQuotedBytes(toStringCompatibility[s])
}
// UnmarshalJSON unmashals a quoted json string to the enum value

View file

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
@ -153,10 +154,10 @@ var numberWideness = map[string]int{
"integer.int32": 0,
}
func prettyprint(b []byte) ([]byte, error) {
func prettyprint(b []byte) (io.ReadWriter, error) {
var out bytes.Buffer
err := json.Indent(&out, b, "", " ")
return out.Bytes(), err
return &out, err
}
// JSONMarshal allows the item to be correctly rendered to json

View file

@ -59,7 +59,7 @@ func (sd *SpecAnalyser) Analyse(spec1, spec2 *spec.Swagger) error {
func (sd *SpecAnalyser) analyseSpecMetadata(spec1, spec2 *spec.Swagger) {
// breaking if it no longer consumes any formats
added, deleted, _ := FromStringArray(spec1.Consumes).DiffsTo(spec2.Consumes)
added, deleted, _ := fromStringArray(spec1.Consumes).DiffsTo(spec2.Consumes)
node := getNameOnlyDiffNode("Spec")
location := DifferenceLocation{Node: node}
@ -74,7 +74,7 @@ func (sd *SpecAnalyser) analyseSpecMetadata(spec1, spec2 *spec.Swagger) {
}
// // breaking if it no longer produces any formats
added, deleted, _ = FromStringArray(spec1.Produces).DiffsTo(spec2.Produces)
added, deleted, _ = fromStringArray(spec1.Produces).DiffsTo(spec2.Produces)
producesLocation := location.AddNode(getNameOnlyDiffNode("produces"))
for _, eachAdded := range added {
sd.Diffs = sd.Diffs.addDiff(SpecDifference{DifferenceLocation: producesLocation, Code: AddedProducesFormat, Compatibility: NonBreaking, DiffInfo: eachAdded})
@ -84,7 +84,7 @@ func (sd *SpecAnalyser) analyseSpecMetadata(spec1, spec2 *spec.Swagger) {
}
// // breaking if it no longer supports a scheme
added, deleted, _ = FromStringArray(spec1.Schemes).DiffsTo(spec2.Schemes)
added, deleted, _ = fromStringArray(spec1.Schemes).DiffsTo(spec2.Schemes)
schemesLocation := location.AddNode(getNameOnlyDiffNode("schemes"))
for _, eachAdded := range added {
@ -120,7 +120,7 @@ func (sd *SpecAnalyser) analyseEndpointData() {
for URLMethod, op2 := range sd.urlMethods2 {
if op1, ok := sd.urlMethods1[URLMethod]; ok {
addedTags, deletedTags, _ := FromStringArray(op1.Operation.Tags).DiffsTo(op2.Operation.Tags)
addedTags, deletedTags, _ := fromStringArray(op1.Operation.Tags).DiffsTo(op2.Operation.Tags)
location := DifferenceLocation{URL: URLMethod.Path, Method: URLMethod.Method}
for _, eachAddedTag := range addedTags {
@ -566,7 +566,7 @@ func (sd *SpecAnalyser) compareEnums(left, right []interface{}) []TypeDiff {
for _, eachRight := range right {
rightStrs = append(rightStrs, fmt.Sprintf("%v", eachRight))
}
added, deleted, _ := FromStringArray(leftStrs).DiffsTo(rightStrs)
added, deleted, _ := fromStringArray(leftStrs).DiffsTo(rightStrs)
if len(added) > 0 {
typeChange := strings.Join(added, ",")
diffs = append(diffs, TypeDiff{Change: AddedEnumValue, Description: typeChange})

View file

@ -1,8 +1,10 @@
package diff
import (
"bytes"
"errors"
"fmt"
"log"
"io"
"sort"
)
@ -131,19 +133,23 @@ func (sd SpecDifferences) addDiff(diff SpecDifference) SpecDifferences {
}
// ReportCompatibility lists and spec
func (sd *SpecDifferences) ReportCompatibility() error {
func (sd *SpecDifferences) ReportCompatibility() (io.Reader, error, error) {
var out bytes.Buffer
breakingCount := sd.BreakingChangeCount()
if breakingCount > 0 {
fmt.Printf("\nBREAKING CHANGES:\n=================\n")
sd.reportChanges(Breaking)
return fmt.Errorf("compatibility Test FAILED: %d Breaking changes detected", breakingCount)
fmt.Fprintln(&out, "\nBREAKING CHANGES:\n=================")
_, _ = out.ReadFrom(sd.reportChanges(Breaking))
msg := fmt.Sprintf("compatibility test FAILED: %d breaking changes detected", breakingCount)
fmt.Fprintln(&out, msg)
return &out, nil, errors.New(msg)
}
log.Printf("Compatibility test OK. No breaking changes identified.")
return nil
fmt.Fprintf(&out, "compatibility test OK. No breaking changes identified.")
return &out, nil, nil
}
func (sd SpecDifferences) reportChanges(compat Compatibility) {
func (sd SpecDifferences) reportChanges(compat Compatibility) io.Reader {
toReportList := []string{}
var out bytes.Buffer
for _, diff := range sd {
if diff.Compatibility == compat {
@ -156,35 +162,36 @@ func (sd SpecDifferences) reportChanges(compat Compatibility) {
})
for _, eachDiff := range toReportList {
fmt.Println(eachDiff)
fmt.Fprintln(&out, eachDiff)
}
return &out
}
// ReportAllDiffs lists all the diffs between two specs
func (sd SpecDifferences) ReportAllDiffs(fmtJSON bool) error {
func (sd SpecDifferences) ReportAllDiffs(fmtJSON bool) (io.Reader, error, error) {
if fmtJSON {
b, err := JSONMarshal(sd)
if err != nil {
log.Fatalf("Couldn't print results: %v", err)
return nil, fmt.Errorf("couldn't print results: %v", err), nil
}
pretty, err := prettyprint(b)
if err != nil {
log.Fatalf("Couldn't print results: %v", err)
}
fmt.Println(string(pretty))
return nil
out, err := prettyprint(b)
return out, err, nil
}
numDiffs := len(sd)
if numDiffs == 0 {
fmt.Println("No changes identified")
return nil
return bytes.NewBuffer([]byte("No changes identified")), nil, nil
}
var out bytes.Buffer
if numDiffs != sd.BreakingChangeCount() {
fmt.Println("NON-BREAKING CHANGES:\n=====================")
sd.reportChanges(NonBreaking)
fmt.Fprintln(&out, "NON-BREAKING CHANGES:\n=====================")
_, _ = out.ReadFrom(sd.reportChanges(NonBreaking))
}
return sd.ReportCompatibility()
more, err, warn := sd.ReportCompatibility()
if err != nil {
return nil, err, warn
}
_, _ = out.ReadFrom(more)
return &out, nil, warn
}

View file

@ -69,5 +69,5 @@ func writeToFile(swspec *spec.Swagger, pretty bool, format string, output string
fmt.Println(string(b))
return nil
}
return ioutil.WriteFile(output, b, 0644)
return ioutil.WriteFile(output, b, 0644) // #nosec
}

View file

@ -20,56 +20,51 @@ import (
"github.com/go-swagger/go-swagger/generator"
)
type clientOptions struct {
ClientPackage string `long:"client-package" short:"c" description:"the package to save the client specific code" default:"client"`
}
func (co clientOptions) apply(opts *generator.GenOpts) {
opts.ClientPackage = co.ClientPackage
}
// Client the command to generate a swagger client
type Client struct {
shared
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
Operations []string `long:"operation" short:"O" description:"specify an operation to include, repeat for multiple"`
Tags []string `long:"tags" description:"the tags to include, if not specified defaults to all"`
Principal string `long:"principal" short:"P" description:"the model to use for the security principal"`
Models []string `long:"model" short:"M" description:"specify a model to include, repeat for multiple"`
DefaultScheme string `long:"default-scheme" description:"the default scheme for this client" default:"http"`
DefaultProduces string `long:"default-produces" description:"the default mime type that API operations produce" default:"application/json"`
SkipModels bool `long:"skip-models" description:"no models will be generated when this flag is specified"`
SkipOperations bool `long:"skip-operations" description:"no operations will be generated when this flag is specified"`
DumpData bool `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files"`
SkipValidation bool `long:"skip-validation" description:"skips validation of spec prior to generation"`
WithShared
WithModels
WithOperations
clientOptions
schemeOptions
mediaOptions
SkipModels bool `long:"skip-models" description:"no models will be generated when this flag is specified"`
SkipOperations bool `long:"skip-operations" description:"no operations will be generated when this flag is specified"`
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
}
func (c *Client) getOpts() (*generator.GenOpts, error) {
return &generator.GenOpts{
Spec: string(c.Spec),
func (c Client) apply(opts *generator.GenOpts) {
c.Shared.apply(opts)
c.Models.apply(opts)
c.Operations.apply(opts)
c.clientOptions.apply(opts)
c.schemeOptions.apply(opts)
c.mediaOptions.apply(opts)
Target: string(c.Target),
APIPackage: c.APIPackage,
ModelPackage: c.ModelPackage,
ServerPackage: c.ServerPackage,
ClientPackage: c.ClientPackage,
Principal: c.Principal,
DefaultScheme: c.DefaultScheme,
DefaultProduces: c.DefaultProduces,
IncludeModel: !c.SkipModels,
IncludeValidator: !c.SkipModels,
IncludeHandler: !c.SkipOperations,
IncludeParameters: !c.SkipOperations,
IncludeResponses: !c.SkipOperations,
ValidateSpec: !c.SkipValidation,
Tags: c.Tags,
IncludeSupport: true,
Template: c.Template,
TemplateDir: string(c.TemplateDir),
DumpData: c.DumpData,
ExistingModels: c.ExistingModels,
IsClient: true,
}, nil
}
opts.IncludeModel = !c.SkipModels
opts.IncludeValidator = !c.SkipModels
opts.IncludeHandler = !c.SkipOperations
opts.IncludeParameters = !c.SkipOperations
opts.IncludeResponses = !c.SkipOperations
opts.Name = c.Name
func (c *Client) getShared() *shared {
return &c.shared
opts.IsClient = true
opts.IncludeSupport = true
}
func (c *Client) generate(opts *generator.GenOpts) error {
return generator.GenerateClient(c.Name, c.Models, c.Operations, opts)
return generator.GenerateClient(c.Name, c.Models.Models, c.Operations.Operations, opts)
}
func (c *Client) log(rp string) {

View file

@ -6,6 +6,7 @@ import (
// contribOptionsOverride gives contributed templates the ability to override the options if they need
func contribOptionsOverride(opts *generator.GenOpts) {
// nolint: gocritic
switch opts.Template {
case "stratoscale":
// Stratoscale template needs to regenerate the configureapi on every run.

View file

@ -17,37 +17,83 @@ package generate
import (
"errors"
"log"
"github.com/go-swagger/go-swagger/generator"
)
// Model the generate model file command
type modelOptions struct {
ModelPackage string `long:"model-package" short:"m" description:"the package to save the models" default:"models"`
Models []string `long:"model" short:"M" description:"specify a model to include in generation, repeat for multiple (defaults to all)"`
ExistingModels string `long:"existing-models" description:"use pre-generated models e.g. github.com/foobar/model"`
StrictAdditionalProperties bool `long:"strict-additional-properties" description:"disallow extra properties when additionalProperties is set to false"`
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)"`
}
func (mo modelOptions) apply(opts *generator.GenOpts) {
opts.ModelPackage = mo.ModelPackage
opts.Models = mo.Models
opts.ExistingModels = mo.ExistingModels
opts.StrictAdditionalProperties = mo.StrictAdditionalProperties
opts.PropertiesSpecOrder = mo.KeepSpecOrder
opts.IgnoreOperations = mo.AllDefinitions
opts.StructTags = mo.StructTags
}
// WithModels adds the model options group.
//
// This group is available to all commands that need some model generation.
type WithModels struct {
Models modelOptions `group:"Options for model generation"`
}
// Model the generate model file command.
//
// Define the options that are specific to the "swagger generate model" command.
type Model struct {
shared
Name []string `long:"name" short:"n" description:"the model to generate"`
NoStruct bool `long:"skip-struct" description:"when present will not generate the model struct"`
DumpData bool `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files"`
SkipValidation bool `long:"skip-validation" description:"skips validation of spec prior to generation"`
WithShared
WithModels
NoStruct bool `long:"skip-struct" description:"when present will not generate the model struct" hidden:"deprecated"`
Name []string `long:"name" short:"n" description:"the model to generate, repeat for multiple (defaults to all). Same as --models"`
AcceptDefinitionsOnly bool `long:"accept-definitions-only" description:"accepts a partial swagger spec wih only the definitions key"`
}
func (m Model) apply(opts *generator.GenOpts) {
m.Shared.apply(opts)
m.Models.apply(opts)
opts.IncludeModel = !m.NoStruct
opts.IncludeValidator = !m.NoStruct
opts.AcceptDefinitionsOnly = m.AcceptDefinitionsOnly
}
func (m Model) log(rp string) {
log.Printf(`Generation completed!
For this generation to compile you need to have some packages in your GOPATH:
* github.com/go-openapi/validate
* github.com/go-openapi/strfmt
You can get these now with: go get -u -f %s/...
`, rp)
}
func (m *Model) generate(opts *generator.GenOpts) error {
return generator.GenerateModels(append(m.Name, m.Models.Models...), opts)
}
// Execute generates a model file
func (m *Model) Execute(args []string) error {
if m.DumpData && len(m.Name) > 1 {
if m.Shared.DumpData && len(append(m.Name, m.Models.Models...)) > 1 {
return errors.New("only 1 model at a time is supported for dumping data")
}
if m.ExistingModels != "" {
if m.Models.ExistingModels != "" {
log.Println("warning: Ignoring existing-models flag when generating models.")
}
s := &Server{
shared: m.shared,
Models: m.Name,
DumpData: m.DumpData,
ExcludeMain: true,
ExcludeSpec: true,
SkipSupport: true,
SkipOperations: true,
SkipModels: m.NoStruct,
SkipValidation: m.SkipValidation,
}
return s.Execute(args)
return createSwagger(m)
}

View file

@ -21,51 +21,69 @@ import (
"github.com/go-swagger/go-swagger/generator"
)
type operationOptions struct {
Operations []string `long:"operation" short:"O" description:"specify an operation to include, repeat for multiple (defaults to all)"`
Tags []string `long:"tags" description:"the tags to include, if not specified defaults to all" group:"operations"`
APIPackage string `long:"api-package" short:"a" description:"the package to save the operations" default:"operations"`
WithEnumCI bool `long:"with-enum-ci" description:"allow case-insensitive enumerations"`
// tags handling
SkipTagPackages bool `long:"skip-tag-packages" description:"skips the generation of tag-based operation packages, resulting in a flat generation"`
}
func (oo operationOptions) apply(opts *generator.GenOpts) {
opts.Operations = oo.Operations
opts.Tags = oo.Tags
opts.APIPackage = oo.APIPackage
opts.AllowEnumCI = oo.WithEnumCI
opts.SkipTagPackages = oo.SkipTagPackages
}
// WithOperations adds the operations options group
type WithOperations struct {
Operations operationOptions `group:"Options for operation generation"`
}
// Operation the generate operation files command
type Operation struct {
shared
Name []string `long:"name" short:"n" required:"true" description:"the operations to generate, repeat for multiple"`
Tags []string `long:"tags" description:"the tags to include, if not specified defaults to all"`
Principal string `short:"P" long:"principal" description:"the model to use for the security principal"`
DefaultScheme string `long:"default-scheme" description:"the default scheme for this API" default:"http"`
NoHandler bool `long:"skip-handler" description:"when present will not generate an operation handler"`
NoStruct bool `long:"skip-parameters" description:"when present will not generate the parameter model struct"`
NoResponses bool `long:"skip-responses" description:"when present will not generate the response model struct"`
NoURLBuilder bool `long:"skip-url-builder" description:"when present will not generate a URL builder"`
DumpData bool `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files"`
SkipValidation bool `long:"skip-validation" description:"skips validation of spec prior to generation"`
WithShared
WithOperations
clientOptions
serverOptions
schemeOptions
mediaOptions
ModelPackage string `long:"model-package" short:"m" description:"the package to save the models" default:"models"`
NoHandler bool `long:"skip-handler" description:"when present will not generate an operation handler"`
NoStruct bool `long:"skip-parameters" description:"when present will not generate the parameter model struct"`
NoResponses bool `long:"skip-responses" description:"when present will not generate the response model struct"`
NoURLBuilder bool `long:"skip-url-builder" description:"when present will not generate a URL builder"`
Name []string `long:"name" short:"n" description:"the operations to generate, repeat for multiple (defaults to all). Same as --operations"`
}
func (o *Operation) getOpts() (*generator.GenOpts, error) {
return &generator.GenOpts{
Spec: string(o.Spec),
Target: string(o.Target),
APIPackage: o.APIPackage,
ModelPackage: o.ModelPackage,
ServerPackage: o.ServerPackage,
ClientPackage: o.ClientPackage,
Principal: o.Principal,
DumpData: o.DumpData,
DefaultScheme: o.DefaultScheme,
TemplateDir: string(o.TemplateDir),
IncludeHandler: !o.NoHandler,
IncludeResponses: !o.NoResponses,
IncludeParameters: !o.NoStruct,
IncludeURLBuilder: !o.NoURLBuilder,
Tags: o.Tags,
ValidateSpec: !o.SkipValidation,
}, nil
}
func (o Operation) apply(opts *generator.GenOpts) {
o.Shared.apply(opts)
o.Operations.apply(opts)
o.clientOptions.apply(opts)
o.serverOptions.apply(opts)
o.schemeOptions.apply(opts)
o.mediaOptions.apply(opts)
func (o *Operation) getShared() *shared {
return &o.shared
opts.ModelPackage = o.ModelPackage
opts.IncludeHandler = !o.NoHandler
opts.IncludeResponses = !o.NoResponses
opts.IncludeParameters = !o.NoStruct
opts.IncludeURLBuilder = !o.NoURLBuilder
}
func (o *Operation) generate(opts *generator.GenOpts) error {
return generator.GenerateServerOperation(o.Name, opts)
return generator.GenerateServerOperation(append(o.Name, o.Operations.Operations...), opts)
}
func (o *Operation) log(rp string) {
func (o Operation) log(rp string) {
log.Printf(`Generation completed!
@ -79,7 +97,7 @@ You can get these now with: go get -u -f %s/...
// Execute generates a model file
func (o *Operation) Execute(args []string) error {
if o.DumpData && len(o.Name) > 1 {
if o.Shared.DumpData && len(append(o.Name, o.Operations.Operations...)) > 1 {
return errors.New("only 1 operation at a time is supported for dumping data")
}

View file

@ -21,87 +21,81 @@ import (
"github.com/go-swagger/go-swagger/generator"
)
// Server the command to generate an entire server application
type Server struct {
shared
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
Operations []string `long:"operation" short:"O" description:"specify an operation to include, repeat for multiple"`
Tags []string `long:"tags" description:"the tags to include, if not specified defaults to all"`
Principal string `long:"principal" short:"P" description:"the model to use for the security principal"`
DefaultScheme string `long:"default-scheme" description:"the default scheme for this API" default:"http"`
Models []string `long:"model" short:"M" description:"specify a model to include, repeat for multiple"`
SkipModels bool `long:"skip-models" description:"no models will be generated when this flag is specified"`
SkipOperations bool `long:"skip-operations" description:"no operations will be generated when this flag is specified"`
SkipSupport bool `long:"skip-support" description:"no supporting files will be generated when this flag is specified"`
ExcludeMain bool `long:"exclude-main" description:"exclude main function, so just generate the library"`
ExcludeSpec bool `long:"exclude-spec" description:"don't embed the swagger specification"`
WithContext bool `long:"with-context" description:"handlers get a context as first arg (deprecated)"`
DumpData bool `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files"`
FlagStrategy string `long:"flag-strategy" description:"the strategy to provide flags for the server" default:"go-flags" choice:"go-flags" choice:"pflag" choice:"flag"`
CompatibilityMode string `long:"compatibility-mode" description:"the compatibility mode for the tls server" default:"modern" choice:"modern" choice:"intermediate"`
SkipValidation bool `long:"skip-validation" description:"skips validation of spec prior to generation"`
RegenerateConfigureAPI bool `long:"regenerate-configureapi" description:"Force regeneration of configureapi.go"`
KeepSpecOrder bool `long:"keep-spec-order" description:"Keep schema properties order identical to spec file"`
StrictAdditionalProperties bool `long:"strict-additional-properties" description:"disallow extra properties when additionalProperties is set to false"`
type serverOptions struct {
ServerPackage string `long:"server-package" short:"s" description:"the package to save the server specific code" default:"restapi"`
MainTarget string `long:"main-package" short:"" description:"the location of the generated main. Defaults to cmd/{name}-server" default:""`
}
func (s *Server) getOpts() (*generator.GenOpts, error) {
// warning: deprecation
func (cs serverOptions) apply(opts *generator.GenOpts) {
opts.ServerPackage = cs.ServerPackage
}
// Server the command to generate an entire server application
type Server struct {
WithShared
WithModels
WithOperations
serverOptions
schemeOptions
mediaOptions
SkipModels bool `long:"skip-models" description:"no models will be generated when this flag is specified"`
SkipOperations bool `long:"skip-operations" description:"no operations will be generated when this flag is specified"`
SkipSupport bool `long:"skip-support" description:"no supporting files will be generated when this flag is specified"`
ExcludeMain bool `long:"exclude-main" description:"exclude main function, so just generate the library"`
ExcludeSpec bool `long:"exclude-spec" description:"don't embed the swagger specification"`
FlagStrategy string `long:"flag-strategy" description:"the strategy to provide flags for the server" default:"go-flags" choice:"go-flags" choice:"pflag" choice:"flag"` // nolint: staticcheck
CompatibilityMode string `long:"compatibility-mode" description:"the compatibility mode for the tls server" default:"modern" choice:"modern" choice:"intermediate"` // nolint: staticcheck
RegenerateConfigureAPI bool `long:"regenerate-configureapi" description:"Force regeneration of configureapi.go"`
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
// TODO(fredbi): CmdName string `long:"cmd-name" short:"A" description:"the name of the server command, when main is generated (defaults to {name}-server)"`
//deprecated flags
WithContext bool `long:"with-context" description:"handlers get a context as first arg (deprecated)"`
}
func (s Server) apply(opts *generator.GenOpts) {
if s.WithContext {
log.Printf("warning: deprecated option --with-context is ignored")
}
return &generator.GenOpts{
Spec: string(s.Spec),
Target: string(s.Target),
APIPackage: s.APIPackage,
ModelPackage: s.ModelPackage,
ServerPackage: s.ServerPackage,
ClientPackage: s.ClientPackage,
Principal: s.Principal,
DefaultScheme: s.DefaultScheme,
IncludeModel: !s.SkipModels,
IncludeValidator: !s.SkipModels,
IncludeHandler: !s.SkipOperations,
IncludeParameters: !s.SkipOperations,
IncludeResponses: !s.SkipOperations,
IncludeURLBuilder: !s.SkipOperations,
IncludeMain: !s.ExcludeMain,
IncludeSupport: !s.SkipSupport,
PropertiesSpecOrder: s.KeepSpecOrder,
ValidateSpec: !s.SkipValidation,
ExcludeSpec: s.ExcludeSpec,
StrictAdditionalProperties: s.StrictAdditionalProperties,
Template: s.Template,
RegenerateConfigureAPI: s.RegenerateConfigureAPI,
TemplateDir: string(s.TemplateDir),
DumpData: s.DumpData,
Models: s.Models,
Operations: s.Operations,
Tags: s.Tags,
Name: s.Name,
FlagStrategy: s.FlagStrategy,
CompatibilityMode: s.CompatibilityMode,
ExistingModels: s.ExistingModels,
AllowTemplateOverride: s.AllowTemplateOverride,
}, nil
}
s.Shared.apply(opts)
s.Models.apply(opts)
s.Operations.apply(opts)
s.serverOptions.apply(opts)
s.schemeOptions.apply(opts)
s.mediaOptions.apply(opts)
func (s *Server) getShared() *shared {
return &s.shared
opts.IncludeModel = !s.SkipModels
opts.IncludeValidator = !s.SkipModels
opts.IncludeHandler = !s.SkipOperations
opts.IncludeParameters = !s.SkipOperations
opts.IncludeResponses = !s.SkipOperations
opts.IncludeURLBuilder = !s.SkipOperations
opts.IncludeSupport = !s.SkipSupport
opts.IncludeMain = !s.ExcludeMain
opts.FlagStrategy = s.FlagStrategy
opts.CompatibilityMode = s.CompatibilityMode
opts.RegenerateConfigureAPI = s.RegenerateConfigureAPI
opts.Name = s.Name
opts.MainPackage = s.MainTarget
}
func (s *Server) generate(opts *generator.GenOpts) error {
return generator.GenerateServer(s.Name, s.Models, s.Operations, opts)
return generator.GenerateServer(s.Name, s.Models.Models, s.Operations.Operations, opts)
}
func (s *Server) log(rp string) {
func (s Server) log(rp string) {
var flagsPackage string
if strings.HasPrefix(s.FlagStrategy, "pflag") {
switch {
case strings.HasPrefix(s.FlagStrategy, "pflag"):
flagsPackage = "github.com/spf13/pflag"
} else if strings.HasPrefix(s.FlagStrategy, "flag") {
case strings.HasPrefix(s.FlagStrategy, "flag"):
flagsPackage = "flag"
} else {
default:
flagsPackage = "github.com/jessevdk/go-flags"
}

View file

@ -1,10 +1,12 @@
package generate
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/go-openapi/analysis"
"github.com/go-openapi/swag"
@ -15,8 +17,8 @@ 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)"`
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"`
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
}
// SetFlattenOptions builds flatten options from command line args
@ -30,138 +32,159 @@ func (f *FlattenCmdOptions) SetFlattenOptions(dflt *analysis.FlattenOpts) (res *
}
verboseIsSet := false
minimalIsSet := false
//removeUnusedIsSet := false
expandIsSet := false
if f.WithExpand {
res.Expand = true
expandIsSet = true
}
for _, opt := range f.WithFlatten {
if opt == "verbose" {
switch opt {
case "verbose":
res.Verbose = true
verboseIsSet = true
}
if opt == "noverbose" && !verboseIsSet {
// verbose flag takes precedence
res.Verbose = false
verboseIsSet = true
}
if opt == "remove-unused" {
case "noverbose":
if !verboseIsSet {
// verbose flag takes precedence
res.Verbose = false
verboseIsSet = true
}
case "remove-unused":
res.RemoveUnused = true
//removeUnusedIsSet = true
}
if opt == "expand" {
case "expand":
res.Expand = true
expandIsSet = true
}
if opt == "full" && !minimalIsSet && !expandIsSet {
// minimal flag takes precedence
res.Minimal = false
minimalIsSet = true
}
if opt == "minimal" && !expandIsSet {
// expand flag takes precedence
res.Minimal = true
minimalIsSet = true
case "full":
if !minimalIsSet && !expandIsSet {
// minimal flag takes precedence
res.Minimal = false
minimalIsSet = true
}
case "minimal":
if !expandIsSet {
// expand flag takes precedence
res.Minimal = true
minimalIsSet = true
}
}
}
return
}
type shared struct {
Spec flags.Filename `long:"spec" short:"f" description:"the spec file to use (default swagger.{json,yml,yaml})"`
APIPackage string `long:"api-package" short:"a" description:"the package to save the operations" default:"operations"`
ModelPackage string `long:"model-package" short:"m" description:"the package to save the models" default:"models"`
ServerPackage string `long:"server-package" short:"s" description:"the package to save the server specific code" default:"restapi"`
ClientPackage string `long:"client-package" short:"c" description:"the package to save the client specific code" default:"client"`
Target flags.Filename `long:"target" short:"t" default:"./" description:"the base directory for generating the files"`
Template string `long:"template" description:"Load contributed templates" choice:"stratoscale"`
TemplateDir flags.Filename `long:"template-dir" short:"T" description:"alternative template override directory"`
ConfigFile flags.Filename `long:"config-file" short:"C" description:"configuration file to use for overriding template options"`
CopyrightFile flags.Filename `long:"copyright-file" short:"r" description:"copyright file used to add copyright header"`
ExistingModels string `long:"existing-models" description:"use pre-generated models e.g. github.com/foobar/model"`
AdditionalInitialisms []string `long:"additional-initialism" description:"consecutive capitals that should be considered intialisms"`
AllowTemplateOverride bool `long:"allow-template-override" description:"allows overriding protected templates"`
FlattenCmdOptions
}
type sharedCommand interface {
getOpts() (*generator.GenOpts, error)
getShared() *shared
getConfigFile() flags.Filename
getAdditionalInitialisms() []string
apply(*generator.GenOpts)
getConfigFile() string
generate(*generator.GenOpts) error
log(string)
}
func (s *shared) getConfigFile() flags.Filename {
return s.ConfigFile
type schemeOptions struct {
Principal string `short:"P" long:"principal" description:"the model to use for the security principal"`
DefaultScheme string `long:"default-scheme" description:"the default scheme for this API" default:"http"`
}
func (s *shared) getAdditionalInitialisms() []string {
return s.AdditionalInitialisms
func (so schemeOptions) apply(opts *generator.GenOpts) {
opts.Principal = so.Principal
opts.DefaultScheme = so.DefaultScheme
}
func (s *shared) setCopyright() (string, error) {
var copyrightstr string
copyrightfile := string(s.CopyrightFile)
if copyrightfile != "" {
//Read the Copyright from file path in opts
bytebuffer, err := ioutil.ReadFile(copyrightfile)
if err != nil {
return "", err
}
copyrightstr = string(bytebuffer)
} else {
copyrightstr = ""
type mediaOptions struct {
DefaultProduces string `long:"default-produces" description:"the default mime type that API operations produce" default:"application/json"`
DefaultConsumes string `long:"default-consumes" description:"the default mime type that API operations consume" default:"application/json"`
}
func (m mediaOptions) apply(opts *generator.GenOpts) {
opts.DefaultProduces = m.DefaultProduces
opts.DefaultConsumes = m.DefaultConsumes
const xmlIdentifier = "xml"
opts.WithXML = strings.Contains(opts.DefaultProduces, xmlIdentifier) || strings.Contains(opts.DefaultConsumes, xmlIdentifier)
}
// WithShared adds the shared options group
type WithShared struct {
Shared sharedOptions `group:"Options common to all code generation commands"`
}
func (w WithShared) getConfigFile() string {
return string(w.Shared.ConfigFile)
}
type sharedOptions struct {
Spec flags.Filename `long:"spec" short:"f" description:"the spec file to use (default swagger.{json,yml,yaml})" group:"shared"`
Target flags.Filename `long:"target" short:"t" default:"./" description:"the base directory for generating the files" group:"shared"`
Template string `long:"template" description:"load contributed templates" choice:"stratoscale" group:"shared"`
TemplateDir flags.Filename `long:"template-dir" short:"T" description:"alternative template override directory" group:"shared"`
ConfigFile flags.Filename `long:"config-file" short:"C" description:"configuration file to use for overriding template options" group:"shared"`
CopyrightFile flags.Filename `long:"copyright-file" short:"r" description:"copyright file used to add copyright header" group:"shared"`
AdditionalInitialisms []string `long:"additional-initialism" description:"consecutive capitals that should be considered intialisms" group:"shared"`
AllowTemplateOverride bool `long:"allow-template-override" description:"allows overriding protected templates" group:"shared"`
SkipValidation bool `long:"skip-validation" description:"skips validation of spec prior to generation" group:"shared"`
DumpData bool `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files" group:"shared"`
StrictResponders bool `long:"strict-responders" description:"Use strict type for the handler return value"`
FlattenCmdOptions
}
func (s sharedOptions) apply(opts *generator.GenOpts) {
opts.Spec = string(s.Spec)
opts.Target = string(s.Target)
opts.Template = s.Template
opts.TemplateDir = string(s.TemplateDir)
opts.AllowTemplateOverride = s.AllowTemplateOverride
opts.ValidateSpec = !s.SkipValidation
opts.DumpData = s.DumpData
opts.FlattenOpts = s.FlattenCmdOptions.SetFlattenOptions(opts.FlattenOpts)
opts.Copyright = string(s.CopyrightFile)
opts.StrictResponders = s.StrictResponders
swag.AddInitialisms(s.AdditionalInitialisms...)
}
func setCopyright(copyrightFile string) (string, error) {
// read the Copyright from file path in opts
if copyrightFile == "" {
return "", nil
}
return copyrightstr, nil
bytebuffer, err := ioutil.ReadFile(copyrightFile)
if err != nil {
return "", err
}
return string(bytebuffer), nil
}
func createSwagger(s sharedCommand) error {
cfg, erc := readConfig(string(s.getConfigFile()))
if erc != nil {
return erc
cfg, err := readConfig(s.getConfigFile())
if err != nil {
return err
}
setDebug(cfg)
setDebug(cfg) // viper config Debug
opts, ero := s.getOpts()
if ero != nil {
return ero
opts := new(generator.GenOpts)
s.apply(opts)
opts.Copyright, err = setCopyright(opts.Copyright)
if err != nil {
return fmt.Errorf("could not load copyright file: %v", err)
}
if opts.Template != "" {
contribOptionsOverride(opts)
}
if err := opts.EnsureDefaults(); err != nil {
if err = opts.EnsureDefaults(); err != nil {
return err
}
if err := configureOptsFromConfig(cfg, opts); err != nil {
if err = configureOptsFromConfig(cfg, opts); err != nil {
return err
}
swag.AddInitialisms(s.getAdditionalInitialisms()...)
if sharedOpts := s.getShared(); sharedOpts != nil {
// process shared options
opts.FlattenOpts = sharedOpts.FlattenCmdOptions.SetFlattenOptions(opts.FlattenOpts)
copyrightStr, erc := sharedOpts.setCopyright()
if erc != nil {
return erc
}
opts.Copyright = copyrightStr
}
if err := s.generate(opts); err != nil {
if err = s.generate(opts); err != nil {
return err
}
basepath, era := filepath.Abs(".")
if era != nil {
return era
basepath, err := filepath.Abs(".")
if err != nil {
return err
}
targetAbs, err := filepath.Abs(opts.Target)
@ -204,11 +227,12 @@ func configureOptsFromConfig(cfg *viper.Viper, opts *generator.GenOpts) error {
}
func setDebug(cfg *viper.Viper) {
// viper config debug
if os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != "" {
if cfg != nil {
cfg.Debug()
} else {
log.Println("NO config read")
log.Println("No config read")
}
}
}

View file

@ -94,7 +94,7 @@ func writeToFile(swspec *spec.Swagger, pretty bool, output string) error {
fmt.Println(string(b))
return nil
}
return ioutil.WriteFile(output, b, 0644)
return ioutil.WriteFile(output, b, 0644) // #nosec
}
func marshalToJSONFormat(swspec *spec.Swagger, pretty bool) ([]byte, error) {

View file

@ -22,40 +22,33 @@ import (
// Support generates the supporting files
type Support struct {
shared
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
Operations []string `long:"operation" short:"O" description:"specify an operation to include, repeat for multiple"`
Principal string `long:"principal" description:"the model to use for the security principal"`
Models []string `long:"model" short:"M" description:"specify a model to include, repeat for multiple"`
DumpData bool `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files"`
DefaultScheme string `long:"default-scheme" description:"the default scheme for this API" default:"http"`
WithShared
WithModels
WithOperations
clientOptions
serverOptions
schemeOptions
mediaOptions
Name string `long:"name" short:"A" description:"the name of the application, defaults to a mangled value of info.title"`
}
func (s *Support) getOpts() (*generator.GenOpts, error) {
return &generator.GenOpts{
Spec: string(s.Spec),
Target: string(s.Target),
APIPackage: s.APIPackage,
ModelPackage: s.ModelPackage,
ServerPackage: s.ServerPackage,
ClientPackage: s.ClientPackage,
Principal: s.Principal,
DumpData: s.DumpData,
DefaultScheme: s.DefaultScheme,
Template: s.Template,
TemplateDir: string(s.TemplateDir),
}, nil
}
func (s *Support) getShared() *shared {
return &s.shared
func (s *Support) apply(opts *generator.GenOpts) {
s.Shared.apply(opts)
s.Models.apply(opts)
s.Operations.apply(opts)
s.clientOptions.apply(opts)
s.serverOptions.apply(opts)
s.schemeOptions.apply(opts)
s.mediaOptions.apply(opts)
}
func (s *Support) generate(opts *generator.GenOpts) error {
return generator.GenerateSupport(s.Name, nil, nil, opts)
return generator.GenerateSupport(s.Name, s.Models.Models, s.Operations.Operations, opts)
}
func (s *Support) log(rp string) {
func (s Support) log(rp string) {
log.Printf(`Generation completed!

View file

@ -16,7 +16,7 @@ import (
const (
// Output messages
nothingToDo = "Nothing to do. Need some swagger files to merge.\nUSAGE: swagger mixin [-c <expected#Collisions>] <primary-swagger-file> <mixin-swagger-file>..."
nothingToDo = "nothing to do. Need some swagger files to merge.\nUSAGE: swagger mixin [-c <expected#Collisions>] <primary-swagger-file> <mixin-swagger-file...>"
)
// MixinSpec holds command line flag definitions specific to the mixin

View file

@ -4,16 +4,15 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/go-openapi/spec"
"log"
"net"
"net/http"
"net/url"
"path"
"strconv"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
"github.com/gorilla/handlers"
"github.com/toqueteos/webbrowser"
@ -43,7 +42,6 @@ func (s *ServeCmd) Execute(args []string) error {
}
if s.Flatten {
var err error
specDoc, err = specDoc.Expanded(&spec.ExpandOptions{
SkipSchemas: false,
ContinueOnError: true,
@ -88,17 +86,12 @@ func (s *ServeCmd) Execute(args []string) error {
}, handler)
visit = fmt.Sprintf("http://%s:%d%s", sh, sp, path.Join(basePath, "docs"))
} else if visit != "" || s.Flavor == "swagger" {
if visit == "" {
visit = "http://petstore.swagger.io/"
}
u, err := url.Parse(visit)
if err != nil {
return err
}
q := u.Query()
q.Add("url", fmt.Sprintf("http://%s:%d%s", sh, sp, path.Join(basePath, "swagger.json")))
u.RawQuery = q.Encode()
visit = u.String()
handler = middleware.SwaggerUI(middleware.SwaggerUIOpts{
BasePath: basePath,
SpecURL: path.Join(basePath, "swagger.json"),
Path: "docs",
}, handler)
visit = fmt.Sprintf("http://%s:%d%s", sh, sp, path.Join(basePath, "docs"))
}
}

View file

@ -26,7 +26,7 @@ import (
const (
// Output messages
missingArgMsg = "The validate command requires the swagger document url to be specified"
missingArgMsg = "the validate command requires the swagger document url to be specified"
validSpecMsg = "\nThe swagger spec at %q is valid against swagger specification %s\n"
invalidSpecMsg = "\nThe swagger spec at %q is invalid against swagger specification %s.\nSee errors below:\n"
warningSpecMsg = "\nThe swagger spec at %q showed up some valid but possibly unwanted constructs."

View file

@ -29,11 +29,6 @@ func init() {
loads.AddLoader(fmts.YAMLMatcher, fmts.YAMLDoc)
}
var (
// Debug is true when the SWAGGER_DEBUG env var is not empty
Debug = os.Getenv("SWAGGER_DEBUG") != ""
)
var opts struct {
// General options applicable to all commands
Quiet func() `long:"quiet" short:"q" description:"silence logs"`

View file

@ -3,6 +3,7 @@ package codescan
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"log"
"os"
@ -290,7 +291,7 @@ func (s *scanCtx) FindModel(pkgPath, name string) (*entityDecl, bool) {
}
}
if decl, found := s.FindDecl(pkgPath, name); found {
s.app.Models[decl.Ident] = decl
s.app.ExtraModels[decl.Ident] = decl
return decl, true
}
return nil, false
@ -352,6 +353,36 @@ func (s *scanCtx) FindComments(pkg *packages.Package, name string) (*ast.Comment
return nil, false
}
func (s *scanCtx) FindEnumValues(pkg *packages.Package, enumName string) (list []interface{}, _ bool) {
for _, f := range pkg.Syntax {
for _, d := range f.Decls {
gd, ok := d.(*ast.GenDecl)
if !ok {
continue
}
if gd.Tok != token.CONST {
continue
}
for _, s := range gd.Specs {
if vs, ok := s.(*ast.ValueSpec); ok {
if vsIdent, ok := vs.Type.(*ast.Ident); ok {
if vsIdent.Name == enumName {
if len(vs.Values) > 0 {
if bl, ok := vs.Values[0].(*ast.BasicLit); ok {
list = append(list, getEnumBasicLitValue(bl))
}
}
}
}
}
}
}
}
return list, true
}
func newTypeIndex(pkgs []*packages.Package,
excludeDeps bool, includeTags, excludeTags map[string]bool,
includePkgs, excludePkgs []string) (*typeIndex, error) {
@ -359,6 +390,7 @@ func newTypeIndex(pkgs []*packages.Package,
ac := &typeIndex{
AllPackages: make(map[string]*packages.Package),
Models: make(map[*ast.Ident]*entityDecl),
ExtraModels: make(map[*ast.Ident]*entityDecl),
excludeDeps: excludeDeps,
includeTags: includeTags,
excludeTags: excludeTags,
@ -374,6 +406,7 @@ func newTypeIndex(pkgs []*packages.Package,
type typeIndex struct {
AllPackages map[string]*packages.Package
Models map[*ast.Ident]*entityDecl
ExtraModels map[*ast.Ident]*entityDecl
Meta []metaSection
Routes []parsedPathContent
Operations []parsedPathContent
@ -483,12 +516,12 @@ func (a *typeIndex) processDecl(pkg *packages.Package, file *ast.File, n node, g
def, ok := pkg.TypesInfo.Defs[ts.Name]
if !ok {
debugLog("couldn't find type info for %s", ts.Name)
//continue
continue
}
nt, isNamed := def.Type().(*types.Named)
if !isNamed {
debugLog("%s is not a named type but a %T", ts.Name, def.Type())
//continue
continue
}
decl := &entityDecl{
Comments: gd.Doc,
@ -516,16 +549,16 @@ func (a *typeIndex) walkImports(pkg *packages.Package) error {
if a.excludeDeps {
return nil
}
for k := range pkg.Imports {
if _, known := a.AllPackages[k]; known {
for _, v := range pkg.Imports {
if _, known := a.AllPackages[v.PkgPath]; known {
continue
}
pk := pkg.Imports[k]
a.AllPackages[pk.PkgPath] = pk
if err := a.processPackage(pk); err != nil {
a.AllPackages[v.PkgPath] = v
if err := a.processPackage(v); err != nil {
return err
}
if err := a.walkImports(pk); err != nil {
if err := a.walkImports(v); err != nil {
return err
}
}

View file

@ -0,0 +1,23 @@
package codescan
import (
"go/ast"
"strconv"
"strings"
)
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
}

View file

@ -201,9 +201,10 @@ func parseContactInfo(line string) (*spec.ContactInfo, error) {
name, email = addr.Name, addr.Address
}
return &spec.ContactInfo{
URL: url,
Name: name,
Email: email,
ContactInfoProps: spec.ContactInfoProps{
URL: url,
Name: name,
Email: email,},
}, nil
}
@ -215,8 +216,10 @@ func setInfoLicense(swspec *spec.Swagger, lines []string) error {
line := lines[0]
name, url := splitURL(line)
info.License = &spec.License{
Name: name,
URL: url,
LicenseProps: spec.LicenseProps{
Name: name,
URL: url,
},
}
return nil
}

View file

@ -59,6 +59,10 @@ func (pt paramTypable) AddExtension(key string, value interface{}) {
}
}
func (pt paramTypable) WithEnum(values ...interface{}) {
pt.param.WithEnum(values...)
}
type itemsTypable struct {
items *spec.Items
level int
@ -90,6 +94,10 @@ func (pt itemsTypable) AddExtension(key string, value interface{}) {
pt.items.AddExtension(key, value)
}
func (pt itemsTypable) WithEnum(values ...interface{}) {
pt.items.WithEnum(values...)
}
type paramValidations struct {
current *spec.Parameter
}
@ -286,6 +294,11 @@ func (p *parameterBuilder) buildFromStruct(decl *entityDecl, tpe *types.Struct,
continue
}
if !fld.Exported() {
debugLog("skipping field %s because it's not exported", fld.Name())
continue
}
tg := tpe.Tag(i)
var afld *ast.Field

View file

@ -200,6 +200,7 @@ type swaggerTypable interface {
Schema() *spec.Schema
Level() int
AddExtension(key string, value interface{})
WithEnum(...interface{})
}
// Map all Go builtin types that have Json representation to Swagger/Json types.

View file

@ -72,13 +72,19 @@ func (ht responseTypable) Schema() *spec.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)
}
func (ht responseTypable) AddExtension(key string, value interface{}) {
ht.response.AddExtension(key, value)
}
func (ht responseTypable) WithEnum(values ...interface{}) {
ht.header.WithEnum(values)
}
type headerValidations struct {
current *spec.Header
}
@ -185,6 +191,7 @@ func (r *responseBuilder) buildFromField(fld *types.Var, tpe types.Type, typable
if err := sb.buildFromType(ftpe.Elem(), schemaTypable{schema, typable.Level() + 1}); err != nil {
return err
}
r.postDecls = append(r.postDecls, sb.postDecls...)
return nil
case *types.Named:
if decl, found := r.ctx.DeclForType(ftpe.Obj().Type()); found {

View file

@ -65,11 +65,17 @@ func (st schemaTypable) AdditionalProperties() swaggerTypable {
st.schema.Typed("object", "")
return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1}
}
func (st schemaTypable) Level() int { return st.level }
func (st schemaTypable) AddExtension(key string, value interface{}) {
addExtension(&st.schema.VendorExtensible, key, value)
}
func (st schemaTypable) WithEnum(values ...interface{}) {
st.schema.WithEnum(values...)
}
type schemaValidations struct {
current *spec.Schema
}
@ -182,8 +188,8 @@ func (s *schemaBuilder) buildFromDecl(decl *entityDecl, schema *spec.Schema) err
debugLog("map: %v -> [%v]%v", s.decl.Ident.Name, tpe.Key().String(), tpe.Elem().String())
case *types.Named:
o := tpe.Obj()
debugLog("got the named type object: %s.%s | isAlias: %t | exported: %t", o.Pkg().Path(), o.Name(), o.IsAlias(), o.Exported())
if o != nil {
debugLog("got the named type object: %s.%s | isAlias: %t | exported: %t", o.Pkg().Path(), o.Name(), o.IsAlias(), o.Exported())
if o.Pkg().Name() == "time" && o.Name() == "Time" {
schema.Typed("string", "date-time")
return nil
@ -263,6 +269,10 @@ func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error
tgt.Typed("string", "date-time")
return nil
}
if pkg.PkgPath == "encoding/json" && tio.Name() == "RawMessage" {
tgt.Typed("object", "")
return nil
}
cmt, hasComments := s.ctx.FindComments(pkg, tio.Name())
if !hasComments {
cmt = new(ast.CommentGroup)
@ -298,7 +308,12 @@ func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error
}
if enumName, ok := enumName(cmt); ok {
debugLog(enumName)
enumValues, _ := s.ctx.FindEnumValues(pkg, enumName)
if len(enumValues) > 0 {
tgt.WithEnum(enumValues...)
enumTypeName := reflect.TypeOf(enumValues[0]).String()
_ = swaggerSchemaForType(enumTypeName, tgt)
}
return nil
}
@ -693,6 +708,11 @@ func (s *schemaBuilder) buildFromStruct(decl *entityDecl, st *types.Struct, sche
break
}
if afld == nil {
debugLog("can't find source associated with %s", fld.String())
continue
}
// if the field is annotated with swagger:ignore, ignore it
if ignored(afld.Doc) {
continue
@ -973,6 +993,21 @@ func schemaVendorExtensibleSetter(meta *spec.Schema) func(json.RawMessage) error
}
}
type tagOptions []string
func (t tagOptions) Contain(option string) bool {
for i := 1; i < len(t); i++ {
if t[i] == option {
return true
}
}
return false
}
func (t tagOptions) Name() string {
return t[0]
}
func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, err error) {
if len(field.Names) > 0 {
name = field.Names[0].Name
@ -988,19 +1023,21 @@ func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, er
if strings.TrimSpace(tv) != "" {
st := reflect.StructTag(tv)
jsonParts := strings.Split(st.Get("json"), ",")
jsonName := jsonParts[0]
jsonParts := tagOptions(strings.Split(st.Get("json"), ","))
if len(jsonParts) > 1 && jsonParts[1] == "string" {
if jsonParts.Contain("string") {
// Need to check if the field type is a scalar. Otherwise, the
// ",string" directive doesn't apply.
isString = isFieldStringable(field.Type)
}
if jsonName == "-" {
switch jsonParts.Name() {
case "-":
return name, true, isString, nil
} else if jsonName != "" {
return jsonName, false, isString, nil
case "":
return name, false, isString, nil
default:
return jsonParts.Name(), false, isString, nil
}
}
return name, false, false, nil

View file

@ -1,6 +1,8 @@
package codescan
import (
"go/ast"
"github.com/go-openapi/spec"
)
@ -193,11 +195,35 @@ func (s *specBuilder) buildModels() error {
if !s.scanModels {
return nil
}
for _, decl := range s.ctx.app.Models {
if err := s.buildDiscoveredSchema(decl); err != nil {
return err
}
}
return s.joinExtraModels()
}
func (s *specBuilder) joinExtraModels() error {
tmp := make(map[*ast.Ident]*entityDecl, len(s.ctx.app.ExtraModels))
for k, v := range s.ctx.app.ExtraModels {
tmp[k] = v
s.ctx.app.Models[k] = v
delete(s.ctx.app.ExtraModels, k)
}
// process extra models and see if there is any reference to a new extra one
for _, decl := range tmp {
if err := s.buildDiscoveredSchema(decl); err != nil {
return err
}
}
if len(s.ctx.app.ExtraModels) > 0 {
return s.joinExtraModels()
}
return nil
}

File diff suppressed because one or more lines are too long

View file

@ -15,102 +15,54 @@
package generator
import (
"encoding/json"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"sort"
"github.com/go-openapi/analysis"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
)
// GenerateClient generates a client library for a swagger spec document.
func GenerateClient(name string, modelNames, operationIDs []string, opts *GenOpts) error {
templates.LoadDefaults()
if opts == nil {
return errors.New("gen opts are required")
}
if opts.Template != "" {
if err := templates.LoadContrib(opts.Template); err != nil {
return err
}
}
templates.SetAllowOverride(opts.AllowTemplateOverride)
if opts.TemplateDir != "" {
if err := templates.LoadDir(opts.TemplateDir); err != nil {
return err
}
}
if err := opts.CheckOpts(); err != nil {
return err
}
// Load the spec
_, specDoc, err := loadSpec(opts.Spec)
if err != nil {
if err := opts.setTemplates(); err != nil {
return err
}
// Validate and Expand. specDoc is in/out param.
specDoc, err = validateAndFlattenSpec(opts, specDoc)
specDoc, analyzed, err := opts.analyzeSpec()
if err != nil {
return err
}
analyzed := analysis.New(specDoc.Spec())
models, err := gatherModels(specDoc, modelNames)
if err != nil {
return err
}
operations := gatherOperations(analyzed, operationIDs)
if len(operations) == 0 {
return errors.New("no operations were selected")
}
defaultScheme := opts.DefaultScheme
if defaultScheme == "" {
defaultScheme = sHTTP
}
defaultConsumes := opts.DefaultConsumes
if defaultConsumes == "" {
defaultConsumes = runtime.JSONMime
}
defaultProduces := opts.DefaultProduces
if defaultProduces == "" {
defaultProduces = runtime.JSONMime
}
generator := appGenerator{
Name: appNameOrDefault(specDoc, name, "rest"),
Name: appNameOrDefault(specDoc, name, defaultClientName),
SpecDoc: specDoc,
Analyzed: analyzed,
Models: models,
Operations: operations,
Target: opts.Target,
DumpData: opts.DumpData,
Package: opts.LanguageOpts.ManglePackageName(opts.ClientPackage, "client"),
APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, "api"),
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, "definitions"),
ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server"),
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, "client"),
OperationsPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, "client"),
Principal: opts.Principal,
DefaultScheme: defaultScheme,
DefaultProduces: defaultProduces,
DefaultConsumes: defaultConsumes,
Package: opts.LanguageOpts.ManglePackageName(opts.ClientPackage, defaultClientTarget),
APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget),
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget),
ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget),
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
OperationsPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
Principal: opts.PrincipalAlias(),
DefaultScheme: opts.DefaultScheme,
DefaultProduces: opts.DefaultProduces,
DefaultConsumes: opts.DefaultConsumes,
GenOpts: opts,
}
generator.Receiver = "o"
@ -123,69 +75,33 @@ type clientGenerator struct {
func (c *clientGenerator) Generate() error {
app, err := c.makeCodegenApp()
if app.Name == "" {
app.Name = "APIClient"
}
baseImport := c.GenOpts.LanguageOpts.baseImport(c.Target)
if c.GenOpts.ExistingModels == "" {
if app.Imports == nil {
app.Imports = make(map[string]string)
}
pkgAlias := c.GenOpts.LanguageOpts.ManglePackageName(c.ModelsPackage, "models")
app.Imports[pkgAlias] = path.Join(
filepath.ToSlash(baseImport),
c.GenOpts.LanguageOpts.ManglePackagePath(c.GenOpts.ModelPackage, "models"))
} else {
app.DefaultImports = append(app.DefaultImports, c.GenOpts.LanguageOpts.ManglePackagePath(c.GenOpts.ExistingModels, ""))
}
if err != nil {
return err
}
if c.DumpData {
bb, _ := json.MarshalIndent(swag.ToDynamicJSON(app), "", " ")
fmt.Fprintln(os.Stdout, string(bb))
return nil
return dumpData(swag.ToDynamicJSON(app))
}
if c.GenOpts.IncludeModel {
for _, mod := range app.Models {
modCopy := mod
modCopy.IncludeValidator = true
if !mod.IsStream {
if err := c.GenOpts.renderDefinition(&modCopy); err != nil {
return err
}
if mod.IsStream {
continue
}
if err := c.GenOpts.renderDefinition(&mod); err != nil {
return err
}
}
}
if c.GenOpts.IncludeHandler {
sort.Sort(app.OperationGroups)
for i := range app.OperationGroups {
opGroup := app.OperationGroups[i]
opGroup.DefaultImports = app.DefaultImports
opGroup.RootPackage = c.ClientPackage
opGroup.GenOpts = c.GenOpts
app.OperationGroups[i] = opGroup
sort.Sort(opGroup.Operations)
for _, op := range opGroup.Operations {
opCopy := op
if opCopy.Package == "" {
opCopy.Package = c.Package
}
if err := c.GenOpts.renderOperation(&opCopy); err != nil {
for _, opg := range app.OperationGroups {
for _, op := range opg.Operations {
if err := c.GenOpts.renderOperation(&op); err != nil {
return err
}
}
app.DefaultImports = append(app.DefaultImports,
path.Join(
filepath.ToSlash(baseImport),
c.GenOpts.LanguageOpts.ManglePackagePath(c.ClientPackage, "client"),
opGroup.Name))
if err := c.GenOpts.renderOperationGroup(&opGroup); err != nil {
if err := c.GenOpts.renderOperationGroup(&opg); err != nil {
return err
}
}

View file

@ -31,10 +31,6 @@ var (
generatorLogger *log.Logger
)
func init() {
debugOptions()
}
func debugOptions() {
generatorLogger = log.New(os.Stdout, "generator:", log.LstdFlags)
}

View file

@ -0,0 +1,433 @@
package generator
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"regexp"
goruntime "runtime"
"sort"
"strings"
"github.com/go-openapi/swag"
"golang.org/x/tools/imports"
)
var (
// DefaultLanguageFunc defines the default generation language
DefaultLanguageFunc func() *LanguageOpts
moduleRe *regexp.Regexp
)
func initLanguage() {
DefaultLanguageFunc = GoLangOpts
moduleRe = regexp.MustCompile(`module[ \t]+([^\s]+)`)
}
// LanguageOpts to describe a language to the code generator
type LanguageOpts struct {
ReservedWords []string
BaseImportFunc func(string) string `json:"-"`
ImportsFunc func(map[string]string) string `json:"-"`
ArrayInitializerFunc func(interface{}) (string, error) `json:"-"`
reservedWordsSet map[string]struct{}
initialized bool
formatFunc func(string, []byte) ([]byte, error)
fileNameFunc func(string) string // language specific source file naming rules
dirNameFunc func(string) string // language specific directory naming rules
}
// Init the language option
func (l *LanguageOpts) Init() {
if l.initialized {
return
}
l.initialized = true
l.reservedWordsSet = make(map[string]struct{})
for _, rw := range l.ReservedWords {
l.reservedWordsSet[rw] = struct{}{}
}
}
// MangleName makes sure a reserved word gets a safe name
func (l *LanguageOpts) MangleName(name, suffix string) string {
if _, ok := l.reservedWordsSet[swag.ToFileName(name)]; !ok {
return name
}
return strings.Join([]string{name, suffix}, "_")
}
// MangleVarName makes sure a reserved word gets a safe name
func (l *LanguageOpts) MangleVarName(name string) string {
nm := swag.ToVarName(name)
if _, ok := l.reservedWordsSet[nm]; !ok {
return nm
}
return nm + "Var"
}
// MangleFileName makes sure a file name gets a safe name
func (l *LanguageOpts) MangleFileName(name string) string {
if l.fileNameFunc != nil {
return l.fileNameFunc(name)
}
return swag.ToFileName(name)
}
// ManglePackageName makes sure a package gets a safe name.
// In case of a file system path (e.g. name contains "/" or "\" on Windows), this return only the last element.
func (l *LanguageOpts) ManglePackageName(name, suffix string) string {
if name == "" {
return suffix
}
if l.dirNameFunc != nil {
name = l.dirNameFunc(name)
}
pth := filepath.ToSlash(filepath.Clean(name)) // preserve path
pkg := importAlias(pth) // drop path
return l.MangleName(swag.ToFileName(prefixForName(pkg)+pkg), suffix)
}
// ManglePackagePath makes sure a full package path gets a safe name.
// Only the last part of the path is altered.
func (l *LanguageOpts) ManglePackagePath(name string, suffix string) string {
if name == "" {
return suffix
}
target := filepath.ToSlash(filepath.Clean(name)) // preserve path
parts := strings.Split(target, "/")
parts[len(parts)-1] = l.ManglePackageName(parts[len(parts)-1], suffix)
return strings.Join(parts, "/")
}
// FormatContent formats a file with a language specific formatter
func (l *LanguageOpts) FormatContent(name string, content []byte) ([]byte, error) {
if l.formatFunc != nil {
return l.formatFunc(name, content)
}
return content, nil
}
// imports generate the code to import some external packages, possibly aliased
func (l *LanguageOpts) imports(imports map[string]string) string {
if l.ImportsFunc != nil {
return l.ImportsFunc(imports)
}
return ""
}
// arrayInitializer builds a litteral array
func (l *LanguageOpts) arrayInitializer(data interface{}) (string, error) {
if l.ArrayInitializerFunc != nil {
return l.ArrayInitializerFunc(data)
}
return "", nil
}
// baseImport figures out the base path to generate import statements
func (l *LanguageOpts) baseImport(tgt string) string {
if l.BaseImportFunc != nil {
return l.BaseImportFunc(tgt)
}
debugLog("base import func is nil")
return ""
}
// GoLangOpts for rendering items as golang code
func GoLangOpts() *LanguageOpts {
var goOtherReservedSuffixes = map[string]bool{
// see:
// https://golang.org/src/go/build/syslist.go
// https://golang.org/doc/install/source#environment
// goos
"aix": true,
"android": true,
"darwin": true,
"dragonfly": true,
"freebsd": true,
"hurd": true,
"illumos": true,
"js": true,
"linux": true,
"nacl": true,
"netbsd": true,
"openbsd": true,
"plan9": true,
"solaris": true,
"windows": true,
"zos": true,
// arch
"386": true,
"amd64": true,
"amd64p32": true,
"arm": true,
"armbe": true,
"arm64": true,
"arm64be": true,
"mips": true,
"mipsle": true,
"mips64": true,
"mips64le": true,
"mips64p32": true,
"mips64p32le": true,
"ppc": true,
"ppc64": true,
"ppc64le": true,
"riscv": true,
"riscv64": true,
"s390": true,
"s390x": true,
"sparc": true,
"sparc64": true,
"wasm": true,
// other reserved suffixes
"test": true,
}
opts := new(LanguageOpts)
opts.ReservedWords = []string{
"break", "default", "func", "interface", "select",
"case", "defer", "go", "map", "struct",
"chan", "else", "goto", "package", "switch",
"const", "fallthrough", "if", "range", "type",
"continue", "for", "import", "return", "var",
}
opts.formatFunc = func(ffn string, content []byte) ([]byte, error) {
opts := new(imports.Options)
opts.TabIndent = true
opts.TabWidth = 2
opts.Fragment = true
opts.Comments = true
return imports.Process(ffn, content, opts)
}
opts.fileNameFunc = func(name string) string {
// whenever a generated file name ends with a suffix
// that is meaningful to go build, adds a "swagger"
// suffix
parts := strings.Split(swag.ToFileName(name), "_")
if goOtherReservedSuffixes[parts[len(parts)-1]] {
// file name ending with a reserved arch or os name
// are appended an innocuous suffix "swagger"
parts = append(parts, "swagger")
}
return strings.Join(parts, "_")
}
opts.dirNameFunc = func(name string) string {
// whenever a generated directory name is a special
// golang directory, append an innocuous suffix
switch name {
case "vendor", "internal":
return strings.Join([]string{name, "swagger"}, "_")
}
return name
}
opts.ImportsFunc = func(imports map[string]string) string {
if len(imports) == 0 {
return ""
}
result := make([]string, 0, len(imports))
for k, v := range imports {
_, name := path.Split(v)
if name != k {
result = append(result, fmt.Sprintf("\t%s %q", k, v))
} else {
result = append(result, fmt.Sprintf("\t%q", v))
}
}
sort.Strings(result)
return strings.Join(result, "\n")
}
opts.ArrayInitializerFunc = func(data interface{}) (string, error) {
// ArrayInitializer constructs a Go literal initializer from interface{} literals.
// e.g. []interface{}{"a", "b"} is transformed in {"a","b",}
// e.g. map[string]interface{}{ "a": "x", "b": "y"} is transformed in {"a":"x","b":"y",}.
//
// NOTE: this is currently used to construct simple slice intializers for default values.
// This allows for nicer slice initializers for slices of primitive types and avoid systematic use for json.Unmarshal().
b, err := json.Marshal(data)
if err != nil {
return "", err
}
return strings.Replace(strings.Replace(strings.Replace(string(b), "}", ",}", -1), "[", "{", -1), "]", ",}", -1), nil
}
opts.BaseImportFunc = func(tgt string) string {
tgt = filepath.Clean(tgt)
// On Windows, filepath.Abs("") behaves differently than on Unix.
// Windows: yields an error, since Abs() does not know the volume.
// UNIX: returns current working directory
if tgt == "" {
tgt = "."
}
tgtAbsPath, err := filepath.Abs(tgt)
if err != nil {
log.Fatalf("could not evaluate base import path with target \"%s\": %v", tgt, err)
}
var tgtAbsPathExtended string
tgtAbsPathExtended, err = filepath.EvalSymlinks(tgtAbsPath)
if err != nil {
log.Fatalf("could not evaluate base import path with target \"%s\" (with symlink resolution): %v", tgtAbsPath, err)
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = filepath.Join(os.Getenv("HOME"), "go")
}
var pth string
for _, gp := range filepath.SplitList(gopath) {
// EvalSymLinks also calls the Clean
gopathExtended, er := filepath.EvalSymlinks(gp)
if er != nil {
log.Fatalln(er)
}
gopathExtended = filepath.Join(gopathExtended, "src")
gp = filepath.Join(gp, "src")
// At this stage we have expanded and unexpanded target path. GOPATH is fully expanded.
// Expanded means symlink free.
// We compare both types of targetpath<s> with gopath.
// If any one of them coincides with gopath , it is imperative that
// target path lies inside gopath. How?
// - Case 1: Irrespective of symlinks paths coincide. Both non-expanded paths.
// - Case 2: Symlink in target path points to location inside GOPATH. (Expanded Target Path)
// - Case 3: Symlink in target path points to directory outside GOPATH (Unexpanded target path)
// Case 1: - Do nothing case. If non-expanded paths match just generate base import path as if
// there are no symlinks.
// Case 2: - Symlink in target path points to location inside GOPATH. (Expanded Target Path)
// First if will fail. Second if will succeed.
// Case 3: - Symlink in target path points to directory outside GOPATH (Unexpanded target path)
// First if will succeed and break.
// compares non expanded path for both
if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gp); ok {
pth = relativepath
break
}
// Compares non-expanded target path
if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gopathExtended); ok {
pth = relativepath
break
}
// Compares expanded target path.
if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPathExtended, gopathExtended); ok {
pth = relativepath
break
}
}
mod, goModuleAbsPath, err := tryResolveModule(tgtAbsPath)
switch {
case err != nil:
log.Fatalf("Failed to resolve module using go.mod file: %s", err)
case mod != "":
relTgt := relPathToRelGoPath(goModuleAbsPath, tgtAbsPath)
if !strings.HasSuffix(mod, relTgt) {
return filepath.ToSlash(mod + relTgt)
}
return filepath.ToSlash(mod)
}
if pth == "" {
log.Fatalln("target must reside inside a location in the $GOPATH/src or be a module")
}
return filepath.ToSlash(pth)
}
opts.Init()
return opts
}
// resolveGoModFile walks up the directory tree starting from 'dir' until it
// finds a go.mod file. If go.mod is found it will return the related file
// object. If no go.mod file is found it will return an error.
func resolveGoModFile(dir string) (*os.File, string, error) {
goModPath := filepath.Join(dir, "go.mod")
f, err := os.Open(goModPath)
if err != nil {
if os.IsNotExist(err) && dir != filepath.Dir(dir) {
return resolveGoModFile(filepath.Dir(dir))
}
return nil, "", err
}
return f, dir, nil
}
// relPathToRelGoPath takes a relative os path and returns the relative go
// package path. For unix nothing will change but for windows \ will be
// converted to /.
func relPathToRelGoPath(modAbsPath, absPath string) string {
if absPath == "." {
return ""
}
path := strings.TrimPrefix(absPath, modAbsPath)
pathItems := strings.Split(path, string(filepath.Separator))
return strings.Join(pathItems, "/")
}
func tryResolveModule(baseTargetPath string) (string, string, error) {
f, goModAbsPath, err := resolveGoModFile(baseTargetPath)
switch {
case os.IsNotExist(err):
return "", "", nil
case err != nil:
return "", "", err
}
src, err := ioutil.ReadAll(f)
if err != nil {
return "", "", err
}
match := moduleRe.FindSubmatch(src)
if len(match) != 2 {
return "", "", nil
}
return string(match[1]), goModAbsPath, nil
}
// 1. Checks if the child path and parent path coincide.
// 2. If they do return child path relative to parent path.
// 3. Everything else return false
func checkPrefixAndFetchRelativePath(childpath string, parentpath string) (bool, string) {
// Windows (local) file systems - NTFS, as well as FAT and variants
// are case insensitive.
cp, pp := childpath, parentpath
if goruntime.GOOS == "windows" {
cp = strings.ToLower(cp)
pp = strings.ToLower(pp)
}
if strings.HasPrefix(cp, pp) {
pth, err := filepath.Rel(parentpath, childpath)
if err != nil {
log.Fatalln(err)
}
return true, pth
}
return false, ""
}

View file

@ -0,0 +1,191 @@
package generator
import (
"regexp"
"sort"
"strings"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
)
const jsonSerializer = "json"
var mediaTypeNames = map[*regexp.Regexp]string{
regexp.MustCompile("application/.*json"): jsonSerializer,
regexp.MustCompile("application/.*yaml"): "yaml",
regexp.MustCompile("application/.*protobuf"): "protobuf",
regexp.MustCompile("application/.*capnproto"): "capnproto",
regexp.MustCompile("application/.*thrift"): "thrift",
regexp.MustCompile("(?:application|text)/.*xml"): "xml",
regexp.MustCompile("text/.*markdown"): "markdown",
regexp.MustCompile("text/.*html"): "html",
regexp.MustCompile("text/.*csv"): "csv",
regexp.MustCompile("text/.*tsv"): "tsv",
regexp.MustCompile("text/.*javascript"): "js",
regexp.MustCompile("text/.*css"): "css",
regexp.MustCompile("text/.*plain"): "txt",
regexp.MustCompile("application/.*octet-stream"): "bin",
regexp.MustCompile("application/.*tar"): "tar",
regexp.MustCompile("application/.*gzip"): "gzip",
regexp.MustCompile("application/.*gz"): "gzip",
regexp.MustCompile("application/.*raw-stream"): "bin",
regexp.MustCompile("application/x-www-form-urlencoded"): "urlform",
regexp.MustCompile("application/javascript"): "txt",
regexp.MustCompile("multipart/form-data"): "multipartform",
regexp.MustCompile("image/.*"): "bin",
regexp.MustCompile("audio/.*"): "bin",
regexp.MustCompile("application/pdf"): "bin",
}
var knownProducers = map[string]string{
jsonSerializer: "runtime.JSONProducer()",
"yaml": "yamlpc.YAMLProducer()",
"xml": "runtime.XMLProducer()",
"txt": "runtime.TextProducer()",
"bin": "runtime.ByteStreamProducer()",
"urlform": "runtime.DiscardProducer",
"multipartform": "runtime.DiscardProducer",
}
var knownConsumers = map[string]string{
jsonSerializer: "runtime.JSONConsumer()",
"yaml": "yamlpc.YAMLConsumer()",
"xml": "runtime.XMLConsumer()",
"txt": "runtime.TextConsumer()",
"bin": "runtime.ByteStreamConsumer()",
"urlform": "runtime.DiscardConsumer",
"multipartform": "runtime.DiscardConsumer",
}
func wellKnownMime(tn string) (string, bool) {
for k, v := range mediaTypeNames {
if k.MatchString(tn) {
return v, true
}
}
return "", false
}
func mediaMime(orig string) string {
return strings.SplitN(orig, ";", 2)[0]
}
func mediaParameters(orig string) string {
parts := strings.SplitN(orig, ";", 2)
if len(parts) < 2 {
return ""
}
return parts[1]
}
func (a *appGenerator) makeSerializers(mediaTypes []string, known func(string) (string, bool)) (GenSerGroups, bool) {
supportsJSON := false
uniqueSerializers := make(map[string]*GenSerializer, len(mediaTypes))
uniqueSerializerGroups := make(map[string]*GenSerGroup, len(mediaTypes))
// build all required serializers
for _, media := range mediaTypes {
key := mediaMime(media)
nm, ok := wellKnownMime(key)
if !ok {
// keep this serializer named, even though its implementation is empty (cf. #1557)
nm = key
}
name := swag.ToJSONName(nm)
impl, _ := known(name)
ser, ok := uniqueSerializers[key]
if !ok {
ser = &GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: name,
MediaType: key,
Implementation: impl,
Parameters: []string{},
}
uniqueSerializers[key] = ser
}
// provide all known parameters (currently unused by codegen templates)
if params := strings.TrimSpace(mediaParameters(media)); params != "" {
found := false
for _, p := range ser.Parameters {
if params == p {
found = true
break
}
}
if !found {
ser.Parameters = append(ser.Parameters, params)
}
}
uniqueSerializerGroups[name] = &GenSerGroup{
GenSerializer: GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: name,
Implementation: impl,
},
}
}
if len(uniqueSerializers) == 0 {
impl, _ := known(jsonSerializer)
uniqueSerializers[runtime.JSONMime] = &GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: jsonSerializer,
MediaType: runtime.JSONMime,
Implementation: impl,
Parameters: []string{},
}
uniqueSerializerGroups[jsonSerializer] = &GenSerGroup{
GenSerializer: GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: jsonSerializer,
Implementation: impl,
},
}
supportsJSON = true
}
// group serializers by consumer/producer to serve several mime media types
serializerGroups := make(GenSerGroups, 0, len(uniqueSerializers))
for _, group := range uniqueSerializerGroups {
if group.Name == jsonSerializer {
supportsJSON = true
}
serializers := make(GenSerializers, 0, len(uniqueSerializers))
for _, ser := range uniqueSerializers {
if group.Name == ser.Name {
sort.Strings(ser.Parameters)
serializers = append(serializers, *ser)
}
}
sort.Sort(serializers)
group.AllSerializers = serializers // provides the full list of mime media types for this serializer group
serializerGroups = append(serializerGroups, *group)
}
sort.Sort(serializerGroups)
return serializerGroups, supportsJSON
}
func (a *appGenerator) makeConsumes() (GenSerGroups, bool) {
// builds a codegen struct from all consumes in the spec
return a.makeSerializers(a.Analyzed.RequiredConsumes(), func(media string) (string, bool) {
c, ok := knownConsumers[media]
return c, ok
})
}
func (a *appGenerator) makeProduces() (GenSerGroups, bool) {
// builds a codegen struct from all produces in the spec
return a.makeSerializers(a.Analyzed.RequiredProduces(), func(media string) (string, bool) {
p, ok := knownProducers[media]
return p, ok
})
}

View file

@ -15,11 +15,9 @@
package generator
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path"
"path/filepath"
"sort"
@ -49,30 +47,38 @@ Every action that happens tracks the path which is a linked list of refs
*/
// GenerateDefinition generates a model file for a schema definition.
// GenerateModels generates all model files for some schema definitions
func GenerateModels(modelNames []string, opts *GenOpts) error {
// overide any default or incompatible options setting
opts.IncludeModel = true
opts.IgnoreOperations = true
opts.ExistingModels = ""
opts.IncludeHandler = false
opts.IncludeMain = false
opts.IncludeSupport = false
generator, err := newAppGenerator("", modelNames, nil, opts)
if err != nil {
return err
}
return generator.Generate()
}
// GenerateDefinition generates a single model file for some schema definitions
func GenerateDefinition(modelNames []string, opts *GenOpts) error {
if opts == nil {
return errors.New("gen opts are required")
}
templates.SetAllowOverride(opts.AllowTemplateOverride)
if opts.TemplateDir != "" {
if err := templates.LoadDir(opts.TemplateDir); err != nil {
return err
}
}
if err := opts.CheckOpts(); err != nil {
return err
}
// Load the spec
specPath, specDoc, err := loadSpec(opts.Spec)
if err := opts.setTemplates(); err != nil {
return err
}
specDoc, _, err := opts.analyzeSpec()
if err != nil {
return err
}
modelNames = pruneEmpty(modelNames)
if len(modelNames) == 0 {
for k := range specDoc.Spec().Definitions {
modelNames = append(modelNames, k)
@ -83,7 +89,7 @@ func GenerateDefinition(modelNames []string, opts *GenOpts) error {
// lookup schema
model, ok := specDoc.Spec().Definitions[modelName]
if !ok {
return fmt.Errorf("model %q not found in definitions given by %q", modelName, specPath)
return fmt.Errorf("model %q not found in definitions given by %q", modelName, opts.Spec)
}
// generate files
@ -121,9 +127,7 @@ func (m *definitionGenerator) Generate() error {
}
if m.opts.DumpData {
bb, _ := json.MarshalIndent(swag.ToDynamicJSON(mod), "", " ")
fmt.Fprintln(os.Stdout, string(bb))
return nil
return dumpData(swag.ToDynamicJSON(mod))
}
if m.opts.IncludeModel {
@ -169,13 +173,17 @@ func shallowValidationLookup(sch GenSchema) bool {
// and NeedsValidation (e.g. should have a Validate method with something in it).
// The latter was almost not used anyhow.
if sch.HasAdditionalProperties && sch.AdditionalProperties == nil {
log.Printf("warning: schema for additional properties in schema %q is empty. skipped", sch.Name)
}
if sch.IsArray && sch.HasValidations {
return true
}
if sch.IsStream || sch.IsInterface { // these types have no validation - aliased types on those do not implement the Validatable interface
return false
}
if sch.Required || sch.IsCustomFormatter && !sch.IsStream {
if sch.Required || hasFormatValidation(sch.resolvedType) {
return true
}
if sch.MaxLength != nil || sch.MinLength != nil || sch.Pattern != "" || sch.MultipleOf != nil || sch.Minimum != nil || sch.Maximum != nil || len(sch.Enum) > 0 || len(sch.ItemsEnum) > 0 {
@ -196,11 +204,11 @@ func shallowValidationLookup(sch GenSchema) bool {
if sch.IsTuple && (sch.AdditionalItems != nil && (sch.AdditionalItems.HasValidations || sch.AdditionalItems.Required)) {
return true
}
if sch.HasAdditionalProperties && (sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream) {
if sch.HasAdditionalProperties && sch.AdditionalProperties != nil && (sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream) {
return false
}
if sch.HasAdditionalProperties && (sch.AdditionalProperties.HasValidations || sch.AdditionalProperties.Required || sch.AdditionalProperties.IsAliased && !(sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream)) {
if sch.HasAdditionalProperties && sch.AdditionalProperties != nil && (sch.AdditionalProperties.HasValidations || sch.AdditionalProperties.Required || sch.AdditionalProperties.IsAliased && !(sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream)) {
return true
}
@ -213,10 +221,13 @@ func shallowValidationLookup(sch GenSchema) bool {
return false
}
func isExternal(schema spec.Schema) bool {
extType, ok := hasExternalType(schema.Extensions)
return ok && !extType.Embedded
}
func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) {
// Check if model is imported from external package using x-go-type
_, external := schema.Extensions[xGoType]
receiver := "m"
// models are resolved in the current package
resolver := newTypeResolver("", specDoc)
@ -241,6 +252,8 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
IncludeValidator: opts.IncludeValidator,
IncludeModel: opts.IncludeModel,
StrictAdditionalProperties: opts.StrictAdditionalProperties,
WithXML: opts.WithXML,
StructTags: opts.StructTags,
}
if err := pg.makeGenSchema(); err != nil {
return nil, fmt.Errorf("could not generate schema for %s: %v", name, err)
@ -282,6 +295,10 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
// replace the ref with this new genschema
swsp := specDoc.Spec()
for i, ss := range schema.AllOf {
if pg.GenSchema.AllOf == nil {
log.Printf("warning: resolved schema for subtype %q.AllOf[%d] is empty. skipped", name, i)
continue
}
ref := ss.Ref
for ref.String() != "" {
var rsch *spec.Schema
@ -290,7 +307,6 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
if err != nil {
return nil, err
}
ref = rsch.Ref
if rsch != nil && rsch.Ref.String() != "" {
ref = rsch.Ref
continue
@ -336,17 +352,17 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
}
defaultImports := []string{
"github.com/go-openapi/errors",
"github.com/go-openapi/runtime",
"github.com/go-openapi/swag",
"github.com/go-openapi/validate",
defaultImports := map[string]string{
"errors": "github.com/go-openapi/errors",
"runtime": "github.com/go-openapi/runtime",
"swag": "github.com/go-openapi/swag",
"validate": "github.com/go-openapi/validate",
}
return &GenDefinition{
GenCommon: GenCommon{
Copyright: opts.Copyright,
TargetImportPath: filepath.ToSlash(opts.LanguageOpts.baseImport(opts.Target)),
TargetImportPath: opts.LanguageOpts.baseImport(opts.Target),
},
Package: opts.LanguageOpts.ManglePackageName(path.Base(filepath.ToSlash(pkg)), "definitions"),
GenSchema: pg.GenSchema,
@ -354,7 +370,7 @@ func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema,
DefaultImports: defaultImports,
ExtraSchemas: gatherExtraSchemas(pg.ExtraSchemas),
Imports: findImports(&pg.GenSchema),
External: external,
External: isExternal(schema),
}, nil
}
@ -364,6 +380,11 @@ func findImports(sch *GenSchema) map[string]string {
if t.Pkg != "" && t.PkgAlias != "" {
imp[t.PkgAlias] = t.Pkg
}
if t.IsEmbedded && t.ElemType != nil {
if t.ElemType.Pkg != "" && t.ElemType.PkgAlias != "" {
imp[t.ElemType.PkgAlias] = t.ElemType.Pkg
}
}
if sch.Items != nil {
sub := findImports(sch.Items)
for k, v := range sub {
@ -404,6 +425,9 @@ func findImports(sch *GenSchema) map[string]string {
}
}
}
for k, v := range sch.ExtraImports {
imp[k] = v
}
return imp
}
@ -418,6 +442,7 @@ type schemaGenContext struct {
IncludeValidator bool
IncludeModel bool
StrictAdditionalProperties bool
WithXML bool
Index int
Path string
@ -431,6 +456,7 @@ type schemaGenContext struct {
Container string
Schema spec.Schema
TypeResolver *typeResolver
StructTags []string
GenSchema GenSchema
Dependencies []string // NOTE: Dependencies is actually set nowhere
@ -438,6 +464,9 @@ type schemaGenContext struct {
Discriminator *discor
Discriminated *discee
Discrimination *discInfo
// force to use container in inlined definitions (for deconflicting)
UseContainerInName bool
}
func (sg *schemaGenContext) NewSliceBranch(schema *spec.Schema) *schemaGenContext {
@ -548,7 +577,7 @@ func (sg *schemaGenContext) shallowClone() *schemaGenContext {
if pg.Container == "" {
pg.Container = sg.Name
}
pg.GenSchema = GenSchema{}
pg.GenSchema = GenSchema{StructTags: sg.StructTags}
pg.Dependencies = nil
pg.Named = false
pg.Index = 0
@ -621,6 +650,16 @@ func hasValidations(model *spec.Schema, isRequired bool) (hasValidation bool) {
return
}
func hasFormatValidation(tpe resolvedType) bool {
if tpe.IsCustomFormatter && !tpe.IsStream && !tpe.IsBase64 {
return true
}
if tpe.IsArray && tpe.ElemType != nil {
return hasFormatValidation(*tpe.ElemType)
}
return false
}
// handleFormatConflicts handles all conflicting model properties when a format is set
func handleFormatConflicts(model *spec.Schema) {
switch model.Format {
@ -690,6 +729,14 @@ func (sg *schemaGenContext) MergeResult(other *schemaGenContext, liftsRequired b
if other.GenSchema.IsMapNullOverride {
sg.GenSchema.IsMapNullOverride = true
}
// lift extra imports
if other.GenSchema.Pkg != "" && other.GenSchema.PkgAlias != "" {
sg.GenSchema.ExtraImports[other.GenSchema.PkgAlias] = other.GenSchema.Pkg
}
for k, v := range other.GenSchema.ExtraImports {
sg.GenSchema.ExtraImports[k] = v
}
}
func (sg *schemaGenContext) buildProperties() error {
@ -702,7 +749,7 @@ func (sg *schemaGenContext) buildProperties() error {
sg.Name, k, sg.IsTuple, sg.GenSchema.HasValidations)
// check if this requires de-anonymizing, if so lift this as a new struct and extra schema
tpe, err := sg.TypeResolver.ResolveSchema(&v, true, sg.IsTuple || containsString(sg.Schema.Required, k))
tpe, err := sg.TypeResolver.ResolveSchema(&v, true, sg.IsTuple || swag.ContainsStrings(sg.Schema.Required, k))
if sg.Schema.Discriminator == k {
tpe.IsNullable = false
}
@ -714,7 +761,7 @@ func (sg *schemaGenContext) buildProperties() error {
var hasValidation bool
if tpe.IsComplexObject && tpe.IsAnonymous && len(v.Properties) > 0 {
// this is an anonymous complex construct: build a new new type for it
pg := sg.makeNewStruct(sg.Name+swag.ToGoName(k), v)
pg := sg.makeNewStruct(sg.makeRefName()+swag.ToGoName(k), v)
pg.IsTuple = sg.IsTuple
if sg.Path != "" {
pg.Path = sg.Path + "+ \".\"+" + fmt.Sprintf("%q", k)
@ -754,7 +801,7 @@ func (sg *schemaGenContext) buildProperties() error {
}
// generates format validation on property
emprop.GenSchema.HasValidations = emprop.GenSchema.HasValidations || (tpe.IsCustomFormatter && !tpe.IsStream) || (tpe.IsArray && tpe.ElemType.IsCustomFormatter && !tpe.ElemType.IsStream)
emprop.GenSchema.HasValidations = emprop.GenSchema.HasValidations || hasFormatValidation(tpe)
if emprop.Schema.Ref.String() != "" {
// expand the schema of this property, so we take informed decisions about its type
@ -768,7 +815,9 @@ func (sg *schemaGenContext) buildProperties() error {
if err != nil {
return err
}
ref = rsch.Ref
if rsch == nil {
return errors.New("spec.ResolveRef returned nil schema")
}
if rsch != nil && rsch.Ref.String() != "" {
ref = rsch.Ref
continue
@ -804,7 +853,7 @@ func (sg *schemaGenContext) buildProperties() error {
hv := hasValidations(sch, false)
// include format validation, excluding binary
hv = hv || (ttpe.IsCustomFormatter && !ttpe.IsStream) || (ttpe.IsArray && ttpe.ElemType.IsCustomFormatter && !ttpe.ElemType.IsStream)
hv = hv || hasFormatValidation(ttpe)
// a base type property is always validated against the base type
// exception: for the base type definition itself (see shallowValidationLookup())
@ -898,7 +947,7 @@ func (sg *schemaGenContext) buildAllOf() error {
// - nested allOf: this one is itself a AllOf: build a new type for it
// - anonymous simple types for edge cases: array, primitive, interface{}
// NOTE: when branches are aliased or anonymous, the nullable property in the branch type is lost.
name := swag.ToVarName(goName(&sch, sg.Name+"AllOf"+strconv.Itoa(i)))
name := swag.ToVarName(goName(&sch, sg.makeRefName()+"AllOf"+strconv.Itoa(i)))
debugLog("building anonymous nested allOf in %s: %s", sg.Name, name)
ng := sg.makeNewStruct(name, sch)
if err := ng.makeGenSchema(); err != nil {
@ -991,7 +1040,7 @@ func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) {
//reached the end of the rabbit hole
if tpe.IsComplexObject && tpe.IsAnonymous {
// found an anonymous object: create the struct from a newly created definition
nw := l.Context.makeNewStruct(l.Context.Name+" Anon", *l.Type.AdditionalProperties.Schema)
nw := l.Context.makeNewStruct(l.Context.makeRefName()+" Anon", *l.Type.AdditionalProperties.Schema)
sch := spec.RefProperty("#/definitions/" + nw.Name)
l.NewObj = nw
@ -1216,7 +1265,7 @@ func (sg *schemaGenContext) buildAdditionalProperties() error {
if tpe.IsComplexObject && tpe.IsAnonymous {
// if the AdditionalProperties is an anonymous complex object, generate a new type for it
pg := sg.makeNewStruct(sg.Name+" Anon", *addp.Schema)
pg := sg.makeNewStruct(sg.makeRefName()+" Anon", *addp.Schema)
if err := pg.makeGenSchema(); err != nil {
return err
}
@ -1311,7 +1360,7 @@ func (sg *schemaGenContext) buildAdditionalProperties() error {
}
hasMapNullOverride := sg.GenSchema.IsMapNullOverride
sg.GenSchema = GenSchema{}
sg.GenSchema = GenSchema{StructTags: sg.StructTags}
sg.Schema = *spec.RefProperty("#/definitions/" + newObj.Name)
if err := sg.makeGenSchema(); err != nil {
return err
@ -1356,6 +1405,7 @@ func (sg *schemaGenContext) makeNewStruct(name string, schema spec.Schema) *sche
IncludeValidator: sg.IncludeValidator,
IncludeModel: sg.IncludeModel,
StrictAdditionalProperties: sg.StrictAdditionalProperties,
StructTags: sg.StructTags,
}
if schema.Ref.String() == "" {
pg.TypeResolver = sg.TypeResolver.NewWithModelName(name)
@ -1374,7 +1424,7 @@ func (sg *schemaGenContext) buildArray() error {
// check if the element is a complex object, if so generate a new type for it
if tpe.IsComplexObject && tpe.IsAnonymous {
pg := sg.makeNewStruct(sg.Name+" items"+strconv.Itoa(sg.Index), *sg.Schema.Items.Schema)
pg := sg.makeNewStruct(sg.makeRefName()+" items"+strconv.Itoa(sg.Index), *sg.Schema.Items.Schema)
if err := pg.makeGenSchema(); err != nil {
return err
}
@ -1419,10 +1469,8 @@ func (sg *schemaGenContext) buildArray() error {
schemaCopy.Required = false
// validations of items
hv := hasValidations(sg.Schema.Items.Schema, false)
// include format validation, excluding binary
hv = hv || (schemaCopy.IsCustomFormatter && !schemaCopy.IsStream) || (schemaCopy.IsArray && schemaCopy.ElemType.IsCustomFormatter && !schemaCopy.ElemType.IsStream)
// include format validation, excluding binary and base64 format validation
hv := hasValidations(sg.Schema.Items.Schema, false) || hasFormatValidation(schemaCopy.resolvedType)
// base types of polymorphic types must be validated
// NOTE: IsNullable is not useful to figure out a validation: we use Refed and IsAliased below instead
@ -1478,7 +1526,7 @@ func (sg *schemaGenContext) buildItems() error {
}
if tpe.IsComplexObject && tpe.IsAnonymous {
// if the tuple element is an anonymous complex object, build a new type for it
pg := sg.makeNewStruct(sg.Name+" Items"+strconv.Itoa(i), s)
pg := sg.makeNewStruct(sg.makeRefName()+" Items"+strconv.Itoa(i), s)
if err := pg.makeGenSchema(); err != nil {
return err
}
@ -1546,7 +1594,7 @@ func (sg *schemaGenContext) buildAdditionalItems() error {
return err
}
if tpe.IsComplexObject && tpe.IsAnonymous {
pg := sg.makeNewStruct(sg.Name+" Items", *sg.Schema.AdditionalItems.Schema)
pg := sg.makeNewStruct(sg.makeRefName()+" Items", *sg.Schema.AdditionalItems.Schema)
if err := pg.makeGenSchema(); err != nil {
return err
}
@ -1581,16 +1629,21 @@ func (sg *schemaGenContext) buildAdditionalItems() error {
return nil
}
func (sg *schemaGenContext) buildXMLName() error {
if sg.Schema.XML == nil {
return nil
}
sg.GenSchema.XMLName = sg.Name
func (sg *schemaGenContext) buildXMLNameWithTags() error {
if sg.WithXML || sg.Schema.XML != nil {
sg.GenSchema.XMLName = sg.Name
if sg.Schema.XML.Name != "" {
sg.GenSchema.XMLName = sg.Schema.XML.Name
if sg.Schema.XML.Attribute {
sg.GenSchema.XMLName += ",attr"
if sg.Schema.XML != nil {
if sg.Schema.XML.Name != "" {
sg.GenSchema.XMLName = sg.Schema.XML.Name
}
if sg.Schema.XML.Attribute {
sg.GenSchema.XMLName += ",attr"
}
}
if !sg.GenSchema.Required && sg.GenSchema.IsEmptyOmitted {
sg.GenSchema.XMLName += ",omitempty"
}
}
return nil
@ -1646,6 +1699,7 @@ func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) {
tpe.IsNullable = tpx.IsNullable // TODO
tpe.IsInterface = tpx.IsInterface
tpe.IsStream = tpx.IsStream
tpe.IsEmbedded = tpx.IsEmbedded
tpe.SwaggerType = tpx.SwaggerType
sch := spec.Schema{}
@ -1764,6 +1818,15 @@ func (sg *schemaGenContext) buildAliased() error {
return nil
}
func (sg schemaGenContext) makeRefName() string {
// figure out a longer name for deconflicting anonymous models.
// This is used when makeNewStruct() is followed by the creation of a new ref to definitions
if sg.UseContainerInName && sg.Container != sg.Name {
return sg.Container + swag.ToGoName(sg.Name)
}
return sg.Name
}
func (sg *schemaGenContext) GoName() string {
return goName(&sg.Schema, sg.Name)
}
@ -1852,6 +1915,8 @@ func (sg *schemaGenContext) makeGenSchema() error {
sg.GenSchema.IncludeModel = sg.IncludeModel
sg.GenSchema.StrictAdditionalProperties = sg.StrictAdditionalProperties
sg.GenSchema.Default = sg.Schema.Default
sg.GenSchema.StructTags = sg.StructTags
sg.GenSchema.ExtraImports = make(map[string]string)
var err error
returns, err := sg.shortCircuitNamedRef()
@ -1893,7 +1958,7 @@ func (sg *schemaGenContext) makeGenSchema() error {
sg.GenSchema.HasDiscriminator = tpe.HasDiscriminator
// include format validations, excluding binary
sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || (tpe.IsCustomFormatter && !tpe.IsStream) || (tpe.IsArray && tpe.ElemType != nil && tpe.ElemType.IsCustomFormatter && !tpe.ElemType.IsStream)
sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || hasFormatValidation(tpe)
// usage of a polymorphic base type is rendered with getter funcs on private properties.
// In the case of aliased types, the value expression remains unchanged to the receiver.
@ -1940,7 +2005,7 @@ func (sg *schemaGenContext) makeGenSchema() error {
return err
}
if err := sg.buildXMLName(); err != nil {
if err := sg.buildXMLNameWithTags(); err != nil {
return err
}
@ -1958,6 +2023,21 @@ func (sg *schemaGenContext) makeGenSchema() error {
sg.buildMapOfNullable(nil)
// extra serializers & interfaces
// generate MarshalBinary for:
// - tuple
// - struct
// - map
// - aliased primitive of a formatter type which is not a stringer
//
// but not for:
// - interface{}
// - io.Reader
gs := sg.GenSchema
sg.GenSchema.WantsMarshalBinary = !(gs.IsInterface || gs.IsStream || gs.IsBaseType) &&
(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,8 +18,6 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strings"
@ -55,86 +53,58 @@ func sortedResponses(input map[int]spec.Response) responses {
return res
}
// GenerateServerOperation generates a parameter model, parameter validator, http handler implementations for a given operation
// GenerateServerOperation generates a parameter model, parameter validator, http handler implementations for a given operation.
//
// It also generates an operation handler interface that uses the parameter model for handling a valid request.
// Allows for specifying a list of tags to include only certain tags for the generation
func GenerateServerOperation(operationNames []string, opts *GenOpts) error {
if opts == nil {
return errors.New("gen opts are required")
}
templates.LoadDefaults()
templates.SetAllowOverride(opts.AllowTemplateOverride)
if opts.TemplateDir != "" {
if err := templates.LoadDir(opts.TemplateDir); err != nil {
return err
}
}
if err := opts.CheckOpts(); err != nil {
return err
}
// Load the spec
_, specDoc, err := loadSpec(opts.Spec)
if err != nil {
if err := opts.setTemplates(); err != nil {
return err
}
// Validate and Expand. specDoc is in/out param.
specDoc, err = validateAndFlattenSpec(opts, specDoc)
specDoc, analyzed, err := opts.analyzeSpec()
if err != nil {
return err
}
analyzed := analysis.New(specDoc.Spec())
ops := gatherOperations(analyzed, operationNames)
if len(ops) == 0 {
return errors.New("no operations were selected")
}
for operationName, opRef := range ops {
method, path, operation := opRef.Method, opRef.Path, opRef.Op
defaultScheme := opts.DefaultScheme
if defaultScheme == "" {
defaultScheme = sHTTP
}
defaultProduces := opts.DefaultProduces
if defaultProduces == "" {
defaultProduces = runtime.JSONMime
}
defaultConsumes := opts.DefaultConsumes
if defaultConsumes == "" {
defaultConsumes = runtime.JSONMime
}
serverPackage := opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server")
serverPackage := opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget)
generator := operationGenerator{
Name: operationName,
Method: method,
Path: path,
BasePath: specDoc.BasePath(),
APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, "api"),
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, "definitions"),
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, "client"),
APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget),
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget),
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
ServerPackage: serverPackage,
Operation: *operation,
SecurityRequirements: analyzed.SecurityRequirementsFor(operation),
SecurityDefinitions: analyzed.SecurityDefinitionsFor(operation),
Principal: opts.Principal,
Principal: opts.PrincipalAlias(),
Target: filepath.Join(opts.Target, filepath.FromSlash(serverPackage)),
Base: opts.Target,
Tags: opts.Tags,
IncludeHandler: opts.IncludeHandler,
IncludeParameters: opts.IncludeParameters,
IncludeResponses: opts.IncludeResponses,
IncludeValidator: true, // we no more support the CLI option to disable validation
IncludeValidator: opts.IncludeValidator,
DumpData: opts.DumpData,
DefaultScheme: defaultScheme,
DefaultProduces: defaultProduces,
DefaultConsumes: defaultConsumes,
DefaultScheme: opts.DefaultScheme,
DefaultProduces: opts.DefaultProduces,
DefaultConsumes: opts.DefaultConsumes,
Doc: specDoc,
Analyzed: analyzed,
GenOpts: opts,
@ -177,78 +147,55 @@ type operationGenerator struct {
GenOpts *GenOpts
}
func intersectTags(left, right []string) (filtered []string) {
if len(right) == 0 {
filtered = left
return
}
for _, l := range left {
if containsString(right, l) {
filtered = append(filtered, l)
}
}
return
}
// Generate a single operation
func (o *operationGenerator) Generate() error {
// Build a list of codegen operations based on the tags,
// the tag decides the actual package for an operation
// the user specified package serves as root for generating the directory structure
var operations GenOperations
authed := len(o.SecurityRequirements) > 0
var bldr codeGenOpBuilder
bldr.Name = o.Name
bldr.Method = o.Method
bldr.Path = o.Path
bldr.BasePath = o.BasePath
bldr.ModelsPackage = o.ModelsPackage
bldr.Principal = o.Principal
bldr.Target = o.Target
bldr.Operation = o.Operation
bldr.Authed = authed
bldr.Security = o.SecurityRequirements
bldr.SecurityDefinitions = o.SecurityDefinitions
bldr.Doc = o.Doc
bldr.Analyzed = o.Analyzed
bldr.DefaultScheme = o.DefaultScheme
bldr.DefaultProduces = o.DefaultProduces
bldr.RootAPIPackage = o.GenOpts.LanguageOpts.ManglePackageName(o.ServerPackage, "server")
bldr.GenOpts = o.GenOpts
bldr.DefaultConsumes = o.DefaultConsumes
bldr.IncludeValidator = o.IncludeValidator
defaultImports := o.GenOpts.defaultImports()
bldr.DefaultImports = []string{o.GenOpts.ExistingModels}
if o.GenOpts.ExistingModels == "" {
bldr.DefaultImports = []string{
path.Join(
filepath.ToSlash(o.GenOpts.LanguageOpts.baseImport(o.Base)),
o.GenOpts.LanguageOpts.ManglePackagePath(o.ModelsPackage, "")),
}
apiPackage := o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.APIPackage, defaultOperationsTarget)
imports := o.GenOpts.initImports(
filepath.Join(o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.ServerPackage, defaultServerTarget), apiPackage))
bldr := codeGenOpBuilder{
ModelsPackage: o.ModelsPackage,
Principal: o.GenOpts.PrincipalAlias(),
Target: o.Target,
DefaultImports: defaultImports,
Imports: imports,
DefaultScheme: o.DefaultScheme,
Doc: o.Doc,
Analyzed: o.Analyzed,
BasePath: o.BasePath,
GenOpts: o.GenOpts,
Name: o.Name,
Operation: o.Operation,
Method: o.Method,
Path: o.Path,
IncludeValidator: o.IncludeValidator,
APIPackage: o.APIPackage, // defaults to main operations package
DefaultProduces: o.DefaultProduces,
DefaultConsumes: o.DefaultConsumes,
Authed: len(o.Analyzed.SecurityRequirementsFor(&o.Operation)) > 0,
Security: o.Analyzed.SecurityRequirementsFor(&o.Operation),
SecurityDefinitions: o.Analyzed.SecurityDefinitionsFor(&o.Operation),
RootAPIPackage: o.GenOpts.LanguageOpts.ManglePackageName(o.ServerPackage, defaultServerTarget),
}
bldr.APIPackage = o.APIPackage
st := o.Tags
if o.GenOpts != nil {
st = o.GenOpts.Tags
}
intersected := intersectTags(o.Operation.Tags, st)
if len(intersected) > 0 {
tag := intersected[0]
bldr.APIPackage = o.GenOpts.LanguageOpts.ManglePackagePath(tag, o.APIPackage)
}
_, tags, _ := bldr.analyzeTags()
op, err := bldr.MakeOperation()
if err != nil {
return err
}
op.Tags = intersected
op.Tags = tags
operations := make(GenOperations, 0, 1)
operations = append(operations, op)
sort.Sort(operations)
for _, op := range operations {
if o.GenOpts.DumpData {
bb, _ := json.MarshalIndent(swag.ToDynamicJSON(op), "", " ")
fmt.Fprintln(os.Stdout, string(bb))
_ = dumpData(swag.ToDynamicJSON(op))
continue
}
if err := o.GenOpts.renderOperation(&op); err != nil {
@ -268,14 +215,16 @@ type codeGenOpBuilder struct {
Path string
BasePath string
APIPackage string
APIPackageAlias string
RootAPIPackage string
ModelsPackage string
Principal string
Target string
Operation spec.Operation
Doc *loads.Document
PristineDoc *loads.Document
Analyzed *analysis.Spec
DefaultImports []string
DefaultImports map[string]string
Imports map[string]string
DefaultScheme string
DefaultProduces string
@ -286,16 +235,57 @@ type codeGenOpBuilder struct {
GenOpts *GenOpts
}
// paramMappings yields a map of safe parameter names for an operation
func paramMappings(params map[string]spec.Parameter) (map[string]map[string]string, string) {
idMapping := map[string]map[string]string{
"query": make(map[string]string, len(params)),
"path": make(map[string]string, len(params)),
"formData": make(map[string]string, len(params)),
"header": make(map[string]string, len(params)),
"body": make(map[string]string, len(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))
for id, p := range params {
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
idMapping[previous.in][p.Name] = swag.ToGoName(previous.id)
} 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}
}
// pick a deconflicted private name for timeout for this operation
timeoutName := renameTimeout(seenIds, "timeout")
return idMapping, timeoutName
}
// renameTimeout renames the variable in use by client template to avoid conflicting
// with param names.
func renameTimeout(seenIds map[string][]string, current string) string {
//
// 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 {
return timeoutName
}
current := strings.ToLower(timeoutName)
if _, ok := seenIds[current]; !ok {
return timeoutName
}
var next string
switch strings.ToLower(current) {
switch current {
case "timeout":
next = "requestTimeout"
case "requesttimeout":
next = "httpRequestTimeout"
case "httptrequesttimeout":
case "httprequesttimeout":
next = "swaggerTimeout"
case "swaggertimeout":
next = "operationTimeout"
@ -303,11 +293,10 @@ func renameTimeout(seenIds map[string][]string, current string) string {
next = "opTimeout"
case "optimeout":
next = "operTimeout"
default:
next = timeoutName + "1"
}
if _, ok := seenIds[next]; ok {
return renameTimeout(seenIds, next)
}
return next
return renameTimeout(seenIds, next)
}
func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
@ -323,35 +312,15 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
//
// In all cases, resetting definitions to the _original_ (untransformed) spec is not an option:
// we take from there the spec possibly already transformed by the GenDefinitions stage.
resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, "models"), b.Doc)
resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, defaultModelsTarget), b.Doc)
receiver := "o"
operation := b.Operation
var params, qp, pp, hp, fp GenParameters
var hasQueryParams, hasPathParams, hasHeaderParams, hasFormParams, hasFileParams, hasFormValueParams, hasBodyParams bool
paramsForOperation := b.Analyzed.ParamsFor(b.Method, b.Path)
timeoutName := "timeout"
idMapping := map[string]map[string]string{
"query": make(map[string]string, len(paramsForOperation)),
"path": make(map[string]string, len(paramsForOperation)),
"formData": make(map[string]string, len(paramsForOperation)),
"header": make(map[string]string, len(paramsForOperation)),
"body": make(map[string]string, len(paramsForOperation)),
}
seenIds := make(map[string][]string, len(paramsForOperation))
for id, p := range paramsForOperation {
if _, ok := seenIds[p.Name]; ok {
idMapping[p.In][p.Name] = swag.ToGoName(id)
} else {
idMapping[p.In][p.Name] = swag.ToGoName(p.Name)
}
seenIds[p.Name] = append(seenIds[p.Name], p.In)
if strings.EqualFold(p.Name, timeoutName) {
timeoutName = renameTimeout(seenIds, timeoutName)
}
}
idMapping, timeoutName := paramMappings(paramsForOperation)
for _, p := range paramsForOperation {
cp, err := b.MakeParameter(receiver, resolver, p, idMapping)
@ -489,15 +458,17 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
return GenOperation{
GenCommon: GenCommon{
Copyright: b.GenOpts.Copyright,
TargetImportPath: filepath.ToSlash(b.GenOpts.LanguageOpts.baseImport(b.GenOpts.Target)),
TargetImportPath: b.GenOpts.LanguageOpts.baseImport(b.GenOpts.Target),
},
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "api"),
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
PackageAlias: b.APIPackageAlias,
RootPackage: b.RootAPIPackage,
Name: b.Name,
Method: b.Method,
Path: b.Path,
BasePath: b.BasePath,
Tags: operation.Tags,
UseTags: len(operation.Tags) > 0 && !b.GenOpts.SkipTagPackages,
Description: trimBOM(operation.Description),
ReceiverName: receiver,
DefaultImports: b.DefaultImports,
@ -531,6 +502,7 @@ func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
ExtraSchemes: extraSchemes,
TimeoutName: timeoutName,
Extensions: operation.Extensions,
StrictResponders: b.GenOpts.StrictResponders,
}, nil
}
@ -573,18 +545,20 @@ func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, r
// assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema)
res := GenResponse{
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "api"),
ModelsPackage: b.ModelsPackage,
ReceiverName: receiver,
Name: name,
Description: trimBOM(resp.Description),
DefaultImports: b.DefaultImports,
Imports: b.Imports,
IsSuccess: isSuccess,
Code: code,
Method: b.Method,
Path: b.Path,
Extensions: resp.Extensions,
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
ModelsPackage: b.ModelsPackage,
ReceiverName: receiver,
Name: name,
Description: trimBOM(resp.Description),
DefaultImports: b.DefaultImports,
Imports: b.Imports,
IsSuccess: isSuccess,
Code: code,
Method: b.Method,
Path: b.Path,
Extensions: resp.Extensions,
StrictResponders: b.GenOpts.StrictResponders,
OperationName: b.Name,
}
// prepare response headers
@ -615,7 +589,7 @@ func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (G
res := GenHeader{
sharedValidations: sharedValidationsFromSimple(hdr.CommonValidations, true), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates.
resolvedType: tpe,
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "api"),
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
ReceiverName: receiver,
ID: id,
Name: name,
@ -662,6 +636,7 @@ func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, v
res.Formatter = stringFormatters[res.GoType]
res.IndexVar = indexVar
res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType)
res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(items.Extensions)
if items.Items != nil {
// Recursively follows nested arrays
@ -684,7 +659,7 @@ func (b *codeGenOpBuilder) HasValidations(sh spec.CommonValidations, rt resolved
hasNumberValidation := sh.Maximum != nil || sh.Minimum != nil || sh.MultipleOf != nil
hasStringValidation := sh.MaxLength != nil || sh.MinLength != nil || sh.Pattern != ""
hasSliceValidations = sh.MaxItems != nil || sh.MinItems != nil || sh.UniqueItems || len(sh.Enum) > 0
hasValidations = (hasNumberValidation || hasStringValidation || hasSliceValidations || rt.IsCustomFormatter) && !rt.IsStream && !rt.IsInterface
hasValidations = hasNumberValidation || hasStringValidation || hasSliceValidations || hasFormatValidation(rt)
return
}
@ -703,6 +678,8 @@ func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path
res.IndexVar = indexVar
res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType)
res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(items.Extensions)
res.NeedsIndex = res.HasValidations || res.Converter != "" || (res.IsCustomFormatter && !res.SkipParse)
if items.Items != nil {
// Recursively follows nested arrays
@ -715,6 +692,7 @@ func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path
pi.Parent = &res
// Propagates HasValidations flag to outer Items definition
res.HasValidations = res.HasValidations || pi.HasValidations
res.NeedsIndex = res.NeedsIndex || pi.NeedsIndex
}
return res, nil
@ -727,7 +705,13 @@ func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver
var child *GenItems
id := swag.ToGoName(param.Name)
if len(idMapping) > 0 {
if goName, ok := param.Extensions["x-go-name"]; ok {
id, ok = goName.(string)
if !ok {
return GenParameter{}, fmt.Errorf(`%s %s, parameter %q: "x-go-name" field must be a string, not a %T`,
b.Method, b.Path, param.Name, goName)
}
} else if len(idMapping) > 0 {
id = idMapping[param.In][param.Name]
}
@ -776,6 +760,7 @@ func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver
res.IsNullable = !param.Required && !param.AllowEmptyValue
res.HasValidations, res.HasSliceValidations = b.HasValidations(param.CommonValidations, res.resolvedType)
res.HasValidations = res.HasValidations || hasChildValidations
res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(param.Extensions)
}
// Select codegen strategy for body param validation
@ -875,9 +860,10 @@ func (b *codeGenOpBuilder) MakeBodyParameterItemsAndMaps(res *GenParameter, it *
next.IsAliased = true
break
}
if next.IsInterface || next.IsStream {
if next.IsInterface || next.IsStream || next.IsBase64 {
next.HasValidations = false
}
next.NeedsIndex = next.HasValidations || next.Converter != "" || (next.IsCustomFormatter && !next.SkipParse)
prev = next
next = new(GenItems)
@ -891,15 +877,17 @@ func (b *codeGenOpBuilder) MakeBodyParameterItemsAndMaps(res *GenParameter, it *
}
}
// propagate HasValidations
var propag func(child *GenItems) bool
propag = func(child *GenItems) bool {
var propag func(child *GenItems) (bool, bool)
propag = func(child *GenItems) (bool, bool) {
if child == nil {
return false
return false, false
}
child.HasValidations = child.HasValidations || propag(child.Child)
return child.HasValidations
cValidations, cIndex := propag(child.Child)
child.HasValidations = child.HasValidations || cValidations
child.NeedsIndex = child.HasValidations || child.Converter != "" || (child.IsCustomFormatter && !child.SkipParse) || cIndex
return child.HasValidations, child.NeedsIndex
}
items.HasValidations = propag(items)
items.HasValidations, items.NeedsIndex = propag(items)
// resolve nullability conflicts when declaring body as a map of array of an anonymous complex object
// (e.g. refer to an extra schema type, which is nullable, but not rendered as a pointer in arrays or maps)
@ -938,7 +926,7 @@ func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) {
var hasSimpleBodyParams, hasSimpleBodyItems, hasSimpleBodyMap, hasModelBodyParams, hasModelBodyItems, hasModelBodyMap bool
s := p.Schema
if s != nil {
doNot := s.IsInterface || s.IsStream
doNot := s.IsInterface || s.IsStream || s.IsBase64
// composition of primitive fields must be properly identified: hack this through
_, isPrimitive := primitives[s.GoType]
_, isFormatter := customFormatters[s.GoType]
@ -949,7 +937,7 @@ func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) {
if s.IsArray && s.Items != nil {
it := s.Items
doNot = it.IsInterface || it.IsStream
doNot = it.IsInterface || it.IsStream || it.IsBase64
hasSimpleBodyItems = !it.IsComplexObject && !(it.IsAliased || doNot)
hasModelBodyItems = (it.IsComplexObject || it.IsAliased) && !doNot
}
@ -1015,7 +1003,10 @@ 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) {
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, "models"), b.Doc.Pristine())
if b.PristineDoc == nil {
b.PristineDoc = b.Doc.Pristine()
}
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.PristineDoc)
return rslv, b.cloneSchema(schema)
}
@ -1027,19 +1018,23 @@ func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *sp
// these ExtraSchemas in the operation's package.
// We need to rebuild the schema with a new type resolver to reflect this change in the
// models package.
func (b *codeGenOpBuilder) liftExtraSchemas(resolver, br *typeResolver, bs *spec.Schema, sc *schemaGenContext) (schema *GenSchema, err error) {
func (b *codeGenOpBuilder) liftExtraSchemas(resolver, rslv *typeResolver, bs *spec.Schema, sc *schemaGenContext) (schema *GenSchema, err error) {
// restore resolving state before previous call to makeGenSchema()
rslv := br
sc.Schema = *bs
pg := sc.shallowClone()
pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, "models")
pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget)
// make a resolver for current package (i.e. operations)
pg.TypeResolver = newTypeResolver("", rslv.Doc).withKeepDefinitionsPackage(pkg)
pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas))
pg.UseContainerInName = true
// rebuild schema within local package
if err = pg.makeGenSchema(); err != nil {
return
}
// lift nested extra schemas (inlined types)
if b.ExtraSchemas == nil {
b.ExtraSchemas = make(map[string]GenSchema, len(pg.ExtraSchemas))
@ -1079,17 +1074,20 @@ func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schem
TypeResolver: rslv,
Named: false,
IncludeModel: true,
IncludeValidator: true,
IncludeValidator: b.GenOpts.IncludeValidator,
StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties,
ExtraSchemas: make(map[string]GenSchema),
StructTags: b.GenOpts.StructTags,
}
var (
br *typeResolver
bs *spec.Schema
)
// these backups are not needed when sch has name.
if sch.Ref.String() == "" {
// backup the type resolver context
// (not needed when the schema has a name)
br, bs = b.saveResolveContext(rslv, sch)
}
@ -1148,3 +1146,119 @@ func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schem
}
return schema, nil
}
func intersectTags(left, right []string) []string {
// dedupe
uniqueTags := make(map[string]struct{}, maxInt(len(left), len(right)))
for _, l := range left {
if len(right) == 0 || swag.ContainsStrings(right, l) {
uniqueTags[l] = struct{}{}
}
}
filtered := make([]string, 0, len(uniqueTags))
// stable output across generations, preserving original order
for _, k := range left {
if _, ok := uniqueTags[k]; !ok {
continue
}
filtered = append(filtered, k)
delete(uniqueTags, k)
}
return filtered
}
// analyze tags for an operation
func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) {
var (
filter []string
tag string
hasTagOverride bool
)
if b.GenOpts != nil {
filter = b.GenOpts.Tags
}
intersected := intersectTags(pruneEmpty(b.Operation.Tags), filter)
if !b.GenOpts.SkipTagPackages && len(intersected) > 0 {
// override generation with: x-go-operation-tag
tag, hasTagOverride = b.Operation.Extensions.GetString(xGoOperationTag)
if !hasTagOverride {
// TODO(fred): this part should be delegated to some new TagsFor(operation) in go-openapi/analysis
tag = intersected[0]
gtags := b.Doc.Spec().Tags
for _, gtag := range gtags {
if gtag.Name != tag {
continue
}
// honor x-go-name in tag
if name, hasGoName := gtag.Extensions.GetString(xGoName); hasGoName {
tag = name
break
}
// honor x-go-operation-tag in tag
if name, hasOpName := gtag.Extensions.GetString(xGoOperationTag); hasOpName {
tag = name
break
}
}
}
}
if tag == b.APIPackage {
// confict with "operations" package is handled separately
tag = renameOperationPackage(intersected, tag)
}
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
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
// deconflictTag ensures generated packages for operations based on tags do not conflict
// with other imports
func deconflictTag(seenTags []string, pkg string) string {
return deconflictPkg(pkg, func(pkg string) string { return renameOperationPackage(seenTags, pkg) })
}
// deconflictPrincipal ensures that whenever an external principal package is added, it doesn't conflict
// with standard inports
func deconflictPrincipal(pkg string) string {
switch pkg {
case "principal":
return renamePrincipalPackage(pkg)
default:
return deconflictPkg(pkg, renamePrincipalPackage)
}
}
// deconflictPkg renames package names which conflict with standard imports
func deconflictPkg(pkg string, renamer func(string) string) string {
switch pkg {
case "api", "httptransport", "formats":
fallthrough
case "errors", "runtime", "middleware", "security", "spec", "strfmt", "loads", "swag", "validate":
fallthrough
case "tls", "http", "fmt", "strings", "log":
return renamer(pkg)
}
return pkg
}
func renameOperationPackage(seenTags []string, pkg string) string {
current := strings.ToLower(pkg) + "ops"
if len(seenTags) == 0 {
return current
}
for swag.ContainsStringsCI(seenTags, current) {
current += "1"
}
return current
}
func renamePrincipalPackage(pkg string) string {
return "auth"
}

View file

@ -16,6 +16,7 @@ package generator
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
@ -24,360 +25,36 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"text/template"
"unicode"
swaggererrors "github.com/go-openapi/errors"
"github.com/go-openapi/analysis"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"golang.org/x/tools/imports"
)
//go:generate go-bindata -mode 420 -modtime 1482416923 -pkg=generator -ignore=.*\.sw? -ignore=.*\.md ./templates/...
// LanguageOpts to describe a language to the code generator
type LanguageOpts struct {
ReservedWords []string
BaseImportFunc func(string) string `json:"-"`
reservedWordsSet map[string]struct{}
initialized bool
formatFunc func(string, []byte) ([]byte, error)
fileNameFunc func(string) string
}
const (
// default generation targets structure
defaultModelsTarget = "models"
defaultServerTarget = "restapi"
defaultClientTarget = "client"
defaultOperationsTarget = "operations"
defaultClientName = "rest"
defaultServerName = "swagger"
defaultScheme = "http"
)
// Init the language option
func (l *LanguageOpts) Init() {
if !l.initialized {
l.initialized = true
l.reservedWordsSet = make(map[string]struct{})
for _, rw := range l.ReservedWords {
l.reservedWordsSet[rw] = struct{}{}
}
}
}
// MangleName makes sure a reserved word gets a safe name
func (l *LanguageOpts) MangleName(name, suffix string) string {
if _, ok := l.reservedWordsSet[swag.ToFileName(name)]; !ok {
return name
}
return strings.Join([]string{name, suffix}, "_")
}
// MangleVarName makes sure a reserved word gets a safe name
func (l *LanguageOpts) MangleVarName(name string) string {
nm := swag.ToVarName(name)
if _, ok := l.reservedWordsSet[nm]; !ok {
return nm
}
return nm + "Var"
}
// MangleFileName makes sure a file name gets a safe name
func (l *LanguageOpts) MangleFileName(name string) string {
if l.fileNameFunc != nil {
return l.fileNameFunc(name)
}
return swag.ToFileName(name)
}
// ManglePackageName makes sure a package gets a safe name.
// In case of a file system path (e.g. name contains "/" or "\" on Windows), this return only the last element.
func (l *LanguageOpts) ManglePackageName(name, suffix string) string {
if name == "" {
return suffix
}
pth := filepath.ToSlash(filepath.Clean(name)) // preserve path
_, pkg := path.Split(pth) // drop path
return l.MangleName(swag.ToFileName(pkg), suffix)
}
// ManglePackagePath makes sure a full package path gets a safe name.
// Only the last part of the path is altered.
func (l *LanguageOpts) ManglePackagePath(name string, suffix string) string {
if name == "" {
return suffix
}
target := filepath.ToSlash(filepath.Clean(name)) // preserve path
parts := strings.Split(target, "/")
parts[len(parts)-1] = l.ManglePackageName(parts[len(parts)-1], suffix)
return strings.Join(parts, "/")
}
// FormatContent formats a file with a language specific formatter
func (l *LanguageOpts) FormatContent(name string, content []byte) ([]byte, error) {
if l.formatFunc != nil {
return l.formatFunc(name, content)
}
return content, nil
}
func (l *LanguageOpts) baseImport(tgt string) string {
if l.BaseImportFunc != nil {
return l.BaseImportFunc(tgt)
}
return ""
}
var golang = GoLangOpts()
// GoLangOpts for rendering items as golang code
func GoLangOpts() *LanguageOpts {
var goOtherReservedSuffixes = map[string]bool{
// see:
// https://golang.org/src/go/build/syslist.go
// https://golang.org/doc/install/source#environment
// goos
"android": true,
"darwin": true,
"dragonfly": true,
"freebsd": true,
"js": true,
"linux": true,
"nacl": true,
"netbsd": true,
"openbsd": true,
"plan9": true,
"solaris": true,
"windows": true,
"zos": true,
// arch
"386": true,
"amd64": true,
"amd64p32": true,
"arm": true,
"armbe": true,
"arm64": true,
"arm64be": true,
"mips": true,
"mipsle": true,
"mips64": true,
"mips64le": true,
"mips64p32": true,
"mips64p32le": true,
"ppc": true,
"ppc64": true,
"ppc64le": true,
"riscv": true,
"riscv64": true,
"s390": true,
"s390x": true,
"sparc": true,
"sparc64": true,
"wasm": true,
// other reserved suffixes
"test": true,
}
opts := new(LanguageOpts)
opts.ReservedWords = []string{
"break", "default", "func", "interface", "select",
"case", "defer", "go", "map", "struct",
"chan", "else", "goto", "package", "switch",
"const", "fallthrough", "if", "range", "type",
"continue", "for", "import", "return", "var",
}
opts.formatFunc = func(ffn string, content []byte) ([]byte, error) {
opts := new(imports.Options)
opts.TabIndent = true
opts.TabWidth = 2
opts.Fragment = true
opts.Comments = true
return imports.Process(ffn, content, opts)
}
opts.fileNameFunc = func(name string) string {
// whenever a generated file name ends with a suffix
// that is meaningful to go build, adds a "swagger"
// suffix
parts := strings.Split(swag.ToFileName(name), "_")
if goOtherReservedSuffixes[parts[len(parts)-1]] {
// file name ending with a reserved arch or os name
// are appended an innocuous suffix "swagger"
parts = append(parts, "swagger")
}
return strings.Join(parts, "_")
}
opts.BaseImportFunc = func(tgt string) string {
tgt = filepath.Clean(tgt)
// On Windows, filepath.Abs("") behaves differently than on Unix.
// Windows: yields an error, since Abs() does not know the volume.
// UNIX: returns current working directory
if tgt == "" {
tgt = "."
}
tgtAbsPath, err := filepath.Abs(tgt)
if err != nil {
log.Fatalf("could not evaluate base import path with target \"%s\": %v", tgt, err)
}
var tgtAbsPathExtended string
tgtAbsPathExtended, err = filepath.EvalSymlinks(tgtAbsPath)
if err != nil {
log.Fatalf("could not evaluate base import path with target \"%s\" (with symlink resolution): %v", tgtAbsPath, err)
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = filepath.Join(os.Getenv("HOME"), "go")
}
var pth string
for _, gp := range filepath.SplitList(gopath) {
// EvalSymLinks also calls the Clean
gopathExtended, er := filepath.EvalSymlinks(gp)
if er != nil {
log.Fatalln(er)
}
gopathExtended = filepath.Join(gopathExtended, "src")
gp = filepath.Join(gp, "src")
// At this stage we have expanded and unexpanded target path. GOPATH is fully expanded.
// Expanded means symlink free.
// We compare both types of targetpath<s> with gopath.
// If any one of them coincides with gopath , it is imperative that
// target path lies inside gopath. How?
// - Case 1: Irrespective of symlinks paths coincide. Both non-expanded paths.
// - Case 2: Symlink in target path points to location inside GOPATH. (Expanded Target Path)
// - Case 3: Symlink in target path points to directory outside GOPATH (Unexpanded target path)
// Case 1: - Do nothing case. If non-expanded paths match just generate base import path as if
// there are no symlinks.
// Case 2: - Symlink in target path points to location inside GOPATH. (Expanded Target Path)
// First if will fail. Second if will succeed.
// Case 3: - Symlink in target path points to directory outside GOPATH (Unexpanded target path)
// First if will succeed and break.
//compares non expanded path for both
if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gp); ok {
pth = relativepath
break
}
// Compares non-expanded target path
if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gopathExtended); ok {
pth = relativepath
break
}
// Compares expanded target path.
if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPathExtended, gopathExtended); ok {
pth = relativepath
break
}
}
mod, goModuleAbsPath, err := tryResolveModule(tgtAbsPath)
switch {
case err != nil:
log.Fatalf("Failed to resolve module using go.mod file: %s", err)
case mod != "":
relTgt := relPathToRelGoPath(goModuleAbsPath, tgtAbsPath)
if !strings.HasSuffix(mod, relTgt) {
return mod + relTgt
}
return mod
}
if pth == "" {
log.Fatalln("target must reside inside a location in the $GOPATH/src or be a module")
}
return pth
}
opts.Init()
return opts
}
var moduleRe = regexp.MustCompile(`module[ \t]+([^\s]+)`)
// resolveGoModFile walks up the directory tree starting from 'dir' until it
// finds a go.mod file. If go.mod is found it will return the related file
// object. If no go.mod file is found it will return an error.
func resolveGoModFile(dir string) (*os.File, string, error) {
goModPath := filepath.Join(dir, "go.mod")
f, err := os.Open(goModPath)
if err != nil {
if os.IsNotExist(err) && dir != filepath.Dir(dir) {
return resolveGoModFile(filepath.Dir(dir))
}
return nil, "", err
}
return f, dir, nil
}
// relPathToRelGoPath takes a relative os path and returns the relative go
// package path. For unix nothing will change but for windows \ will be
// converted to /.
func relPathToRelGoPath(modAbsPath, absPath string) string {
if absPath == "." {
return ""
}
path := strings.TrimPrefix(absPath, modAbsPath)
pathItems := strings.Split(path, string(filepath.Separator))
return strings.Join(pathItems, "/")
}
func tryResolveModule(baseTargetPath string) (string, string, error) {
f, goModAbsPath, err := resolveGoModFile(baseTargetPath)
switch {
case os.IsNotExist(err):
return "", "", nil
case err != nil:
return "", "", err
}
src, err := ioutil.ReadAll(f)
if err != nil {
return "", "", err
}
match := moduleRe.FindSubmatch(src)
if len(match) != 2 {
return "", "", nil
}
return string(match[1]), goModAbsPath, nil
}
func findSwaggerSpec(nm string) (string, error) {
specs := []string{"swagger.json", "swagger.yml", "swagger.yaml"}
if nm != "" {
specs = []string{nm}
}
var name string
for _, nn := range specs {
f, err := os.Stat(nn)
if err != nil && !os.IsNotExist(err) {
return "", err
}
if err != nil && os.IsNotExist(err) {
continue
}
if f.IsDir() {
return "", fmt.Errorf("%s is a directory", nn)
}
name = nn
break
}
if name == "" {
return "", errors.New("couldn't find a swagger spec")
}
return name, nil
func init() {
// all initializations for the generator package
debugOptions()
initLanguage()
initTemplateRepo()
initTypes()
}
// DefaultSectionOpts for a given opts, this is used when no config file is passed
@ -411,14 +88,13 @@ func DefaultSectionOpts(gen *GenOpts) {
FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
},
}
} else {
ops := []TemplateOpts{}
if gen.IncludeParameters {
ops = append(ops, TemplateOpts{
Name: "parameters",
Source: "asset:serverParameter",
Target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
})
}
@ -426,7 +102,7 @@ func DefaultSectionOpts(gen *GenOpts) {
ops = append(ops, TemplateOpts{
Name: "urlbuilder",
Source: "asset:serverUrlbuilder",
Target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
FileName: "{{ (snakize (pascalize .Name)) }}_urlbuilder.go",
})
}
@ -434,7 +110,7 @@ func DefaultSectionOpts(gen *GenOpts) {
ops = append(ops, TemplateOpts{
Name: "responses",
Source: "asset:serverResponses",
Target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
})
}
@ -442,7 +118,7 @@ func DefaultSectionOpts(gen *GenOpts) {
ops = append(ops, TemplateOpts{
Name: "handler",
Source: "asset:serverOperation",
Target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
FileName: "{{ (snakize (pascalize .Name)) }}.go",
})
}
@ -487,7 +163,7 @@ func DefaultSectionOpts(gen *GenOpts) {
{
Name: "main",
Source: "asset:serverMain",
Target: "{{ joinFilePath .Target \"cmd\" (dasherize (pascalize .Name)) }}-server",
Target: "{{ joinFilePath .Target \"cmd\" .MainPackage }}",
FileName: "main.go",
},
{
@ -521,7 +197,7 @@ func DefaultSectionOpts(gen *GenOpts) {
}
// TemplateOpts allows
// TemplateOpts allows for codegen customization
type TemplateOpts struct {
Name string `mapstructure:"name"`
Source string `mapstructure:"source"`
@ -573,37 +249,58 @@ type GenOpts struct {
DefaultScheme string
DefaultProduces string
DefaultConsumes string
WithXML bool
TemplateDir string
Template string
RegenerateConfigureAPI bool
Operations []string
Models []string
Tags []string
StructTags []string
Name string
FlagStrategy string
CompatibilityMode string
ExistingModels string
Copyright string
SkipTagPackages bool
MainPackage string
IgnoreOperations bool
AllowEnumCI bool
StrictResponders bool
AcceptDefinitionsOnly bool
}
// CheckOpts carries out some global consistency checks on options.
//
// At the moment, these checks simply protect TargetPath() and SpecPath()
// functions. More checks may be added here.
func (g *GenOpts) CheckOpts() error {
if g == nil {
return errors.New("gen opts are required")
}
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)
}
}
if filepath.IsAbs(g.ServerPackage) {
return fmt.Errorf("you shouldn't specify an absolute path in --server-package: %s", g.ServerPackage)
}
if !filepath.IsAbs(g.Spec) && !strings.HasPrefix(g.Spec, "http://") && !strings.HasPrefix(g.Spec, "https://") {
if _, err := filepath.Abs(g.Spec); err != nil {
return fmt.Errorf("could not locate spec: %s", g.Spec)
}
if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") {
return nil
}
pth, err := findSwaggerSpec(g.Spec)
if err != nil {
return err
}
// ensure spec path is absolute
g.Spec, err = filepath.Abs(pth)
if err != nil {
return fmt.Errorf("could not locate spec: %s", g.Spec)
}
return nil
}
@ -670,17 +367,42 @@ func (g *GenOpts) EnsureDefaults() error {
if g.defaultsEnsured {
return nil
}
DefaultSectionOpts(g)
if g.LanguageOpts == nil {
g.LanguageOpts = GoLangOpts()
g.LanguageOpts = DefaultLanguageFunc()
}
DefaultSectionOpts(g)
// set defaults for flattening options
g.FlattenOpts = &analysis.FlattenOpts{
Minimal: true,
Verbose: true,
RemoveUnused: false,
Expand: false,
if g.FlattenOpts == nil {
g.FlattenOpts = &analysis.FlattenOpts{
Minimal: true,
Verbose: true,
RemoveUnused: false,
Expand: false,
}
}
if g.DefaultScheme == "" {
g.DefaultScheme = defaultScheme
}
if g.DefaultConsumes == "" {
g.DefaultConsumes = runtime.JSONMime
}
if g.DefaultProduces == "" {
g.DefaultProduces = runtime.JSONMime
}
// always include validator with models
g.IncludeValidator = true
if g.Principal == "" {
g.Principal = iface
}
g.defaultsEnsured = true
return nil
}
@ -707,19 +429,29 @@ func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, e
tags = tagsF.Interface().([]string)
}
pthTpl, err := template.New(t.Name + "-target").Funcs(FuncMap).Parse(t.Target)
var useTags bool
useTagsF := v.FieldByName("UseTags")
if useTagsF.IsValid() {
useTags = useTagsF.Interface().(bool)
}
funcMap := FuncMapFunc(g.LanguageOpts)
pthTpl, err := template.New(t.Name + "-target").Funcs(funcMap).Parse(t.Target)
if err != nil {
return "", "", err
}
fNameTpl, err := template.New(t.Name + "-filename").Funcs(FuncMap).Parse(t.FileName)
fNameTpl, err := template.New(t.Name + "-filename").Funcs(funcMap).Parse(t.FileName)
if err != nil {
return "", "", err
}
d := struct {
Name, Package, APIPackage, ServerPackage, ClientPackage, ModelPackage, Target string
Tags []string
Name, Package, APIPackage, ServerPackage, ClientPackage, ModelPackage, MainPackage, Target string
Tags []string
UseTags bool
Context interface{}
}{
Name: name,
Package: pkg,
@ -727,11 +459,13 @@ func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, e
ServerPackage: g.ServerPackage,
ClientPackage: g.ClientPackage,
ModelPackage: g.ModelPackage,
MainPackage: g.MainPackage,
Target: g.Target,
Tags: tags,
UseTags: useTags,
Context: data,
}
// pretty.Println(data)
var pthBuf bytes.Buffer
if e := pthTpl.Execute(&pthBuf, d); e != nil {
return "", "", e
@ -777,7 +511,7 @@ func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err)
}
tt, err := template.New(t.Source).Funcs(FuncMap).Parse(string(content))
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)
}
@ -836,10 +570,10 @@ func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
var writeerr error
if !t.SkipFormat {
formatted, err = g.LanguageOpts.FormatContent(fname, content)
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 = ioutil.WriteFile(filepath.Join(dir, fname), content, 0644)
writeerr = ioutil.WriteFile(filepath.Join(dir, fname), content, 0644) // #nosec
if writeerr != nil {
return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr)
}
@ -848,7 +582,7 @@ func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
}
}
writeerr = ioutil.WriteFile(filepath.Join(dir, fname), formatted, 0644)
writeerr = ioutil.WriteFile(filepath.Join(dir, fname), formatted, 0644) // #nosec
if writeerr != nil {
return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr)
}
@ -930,42 +664,84 @@ func (g *GenOpts) renderDefinition(gg *GenDefinition) error {
return nil
}
func validateSpec(path string, doc *loads.Document) (err error) {
if doc == nil {
if path, doc, err = loadSpec(path); err != nil {
func (g *GenOpts) setTemplates() error {
templates.LoadDefaults()
if g.Template != "" {
// set contrib templates
if err := templates.LoadContrib(g.Template); err != nil {
return err
}
}
result := validate.Spec(doc, strfmt.Default)
if result == nil {
return nil
}
templates.SetAllowOverride(g.AllowTemplateOverride)
str := fmt.Sprintf("The swagger spec at %q is invalid against swagger specification %s. see errors :\n", path, doc.Version())
for _, desc := range result.(*swaggererrors.CompositeError).Errors {
str += fmt.Sprintf("- %s\n", desc)
}
return errors.New(str)
}
func loadSpec(specFile string) (string, *loads.Document, error) {
// find swagger spec document, verify it exists
specPath := specFile
var err error
if !strings.HasPrefix(specPath, "http") {
specPath, err = findSwaggerSpec(specFile)
if err != nil {
return "", nil, err
if g.TemplateDir != "" {
// set custom templates
if err := templates.LoadDir(g.TemplateDir); err != nil {
return err
}
}
return nil
}
// load swagger spec
specDoc, err := loads.Spec(specPath)
if err != nil {
return "", nil, err
// defaultImports produces a default map for imports with models
func (g *GenOpts) defaultImports() map[string]string {
baseImport := g.LanguageOpts.baseImport(g.Target)
defaultImports := make(map[string]string, 50)
if g.ExistingModels == "" {
// generated models
importPath := path.Join(
baseImport,
g.LanguageOpts.ManglePackagePath(g.ModelPackage, defaultModelsTarget))
defaultImports[g.LanguageOpts.ManglePackageName(g.ModelPackage, defaultModelsTarget)] = importPath
} else {
// external models
importPath := g.LanguageOpts.ManglePackagePath(g.ExistingModels, "")
defaultImports["models"] = importPath
}
return specPath, specDoc, nil
alias, _, target := g.resolvePrincipal()
if alias != "" {
if pth, _ := path.Split(target); pth != "" {
// if principal is specified with an path, generate this import
defaultImports[alias] = target
} else {
// if principal is specified with a relative path, assume it is located in generated target
defaultImports[alias] = path.Join(baseImport, target)
}
}
return defaultImports
}
// initImports produces a default map for import with the specified root for operations
func (g *GenOpts) initImports(operationsPackage string) map[string]string {
baseImport := g.LanguageOpts.baseImport(g.Target)
imports := make(map[string]string, 50)
imports[g.LanguageOpts.ManglePackageName(operationsPackage, defaultOperationsTarget)] = path.Join(
baseImport,
g.LanguageOpts.ManglePackagePath(operationsPackage, defaultOperationsTarget))
return imports
}
// PrincipalAlias returns an aliased type to the principal
func (g *GenOpts) PrincipalAlias() string {
_, principal, _ := g.resolvePrincipal()
return principal
}
func (g *GenOpts) resolvePrincipal() (string, string, string) {
dotLocation := strings.LastIndex(g.Principal, ".")
if dotLocation < 0 {
return "", g.Principal, ""
}
// handle possible conflicts with injected principal package
// NOTE(fred): we do not check here for conflicts with packages created from operation tags, only standard imports
alias := deconflictPrincipal(importAlias(g.Principal[:dotLocation]))
return alias, alias + g.Principal[dotLocation:], g.Principal[:dotLocation]
}
func fileExists(target, name string) bool {
@ -974,6 +750,7 @@ func fileExists(target, name string) bool {
}
func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec.Schema, error) {
modelNames = pruneEmpty(modelNames)
models, mnc := make(map[string]spec.Schema), len(modelNames)
defs := specDoc.Spec().Definitions
@ -1002,7 +779,8 @@ func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec
return models, nil
}
func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
// titleOrDefault infers a name for the app from the title of the spec
func titleOrDefault(specDoc *loads.Document, name, defaultName string) string {
if strings.TrimSpace(name) == "" {
if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" {
name = specDoc.Spec().Info.Title
@ -1010,16 +788,21 @@ func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string
name = defaultName
}
}
return strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(swag.ToGoName(name), "Test"), "API"), "Test")
return swag.ToGoName(name)
}
func containsString(names []string, name string) bool {
for _, nm := range names {
if nm == name {
return true
}
func mainNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
// *_test won't do as main server name
return strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test")
}
func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
// *_test won't do as app names
name = strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test")
if name == "" {
name = swag.ToGoName(defaultName)
}
return false
return name
}
type opRef struct {
@ -1037,14 +820,14 @@ func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]opRef {
operationIDs = pruneEmpty(operationIDs)
var oprefs opRefs
for method, pathItem := range specDoc.Operations() {
for path, operation := range pathItem {
// nm := ensureUniqueName(operation.ID, method, path, operations)
vv := *operation
oprefs = append(oprefs, opRef{
Key: swag.ToGoName(strings.ToLower(method) + " " + path),
Key: swag.ToGoName(strings.ToLower(method) + " " + strings.Title(path)),
Method: method,
Path: path,
ID: vv.ID,
@ -1066,7 +849,7 @@ func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]
if found && oo.Method != opr.Method && oo.Path != opr.Path {
nm = opr.Key
}
if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) {
if len(operationIDs) == 0 || swag.ContainsStrings(operationIDs, opr.ID) || swag.ContainsStrings(operationIDs, nm) {
opr.ID = nm
opr.Op.ID = nm
operations[nm] = opr
@ -1076,43 +859,6 @@ func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]
return operations
}
func pascalize(arg string) string {
runes := []rune(arg)
switch len(runes) {
case 0:
return ""
case 1: // handle special case when we have a single rune that is not handled by swag.ToGoName
switch runes[0] {
case '+', '-', '#', '_': // those cases are handled differently than swag utility
return prefixForName(arg)
}
}
return swag.ToGoName(swag.ToGoName(arg)) // want to remove spaces
}
func prefixForName(arg string) string {
first := []rune(arg)[0]
if len(arg) == 0 || unicode.IsLetter(first) {
return ""
}
switch first {
case '+':
return "Plus"
case '-':
return "Minus"
case '#':
return "HashTag"
// other cases ($,@ etc..) handled by swag.ToGoName
}
return "Nr"
}
func init() {
// this makes the ToGoName func behave with the special
// prefixing rule above
swag.GoNamePrefixFunc = prefixForName
}
func pruneEmpty(in []string) (out []string) {
for _, v := range in {
if v != "" {
@ -1126,73 +872,6 @@ func trimBOM(in string) string {
return strings.Trim(in, "\xef\xbb\xbf")
}
func validateAndFlattenSpec(opts *GenOpts, specDoc *loads.Document) (*loads.Document, error) {
var err error
// Validate if needed
if opts.ValidateSpec {
log.Printf("validating spec %v", opts.Spec)
if erv := validateSpec(opts.Spec, specDoc); erv != nil {
return specDoc, erv
}
}
// Restore spec to original
opts.Spec, specDoc, err = loadSpec(opts.Spec)
if err != nil {
return nil, err
}
absBasePath := specDoc.SpecFilePath()
if !filepath.IsAbs(absBasePath) {
cwd, _ := os.Getwd()
absBasePath = filepath.Join(cwd, absBasePath)
}
// Some preprocessing is required before codegen
//
// This ensures at least that $ref's in the spec document are canonical,
// i.e all $ref are local to this file and point to some uniquely named definition.
//
// Default option is to ensure minimal flattening of $ref, bundling remote $refs and relocating arbitrary JSON
// pointers as definitions.
// This preprocessing may introduce duplicate names (e.g. remote $ref with same name). In this case, a definition
// suffixed with "OAIGen" is produced.
//
// Full flattening option farther transforms the spec by moving every complex object (e.g. with some properties)
// as a standalone definition.
//
// Eventually, an "expand spec" option is available. It is essentially useful for testing purposes.
//
// NOTE(fredbi): spec expansion may produce some unsupported constructs and is not yet protected against the
// following cases:
// - polymorphic types generation may fail with expansion (expand destructs the reuse intent of the $ref in allOf)
// - name duplicates may occur and result in compilation failures
// The right place to fix these shortcomings is go-openapi/analysis.
opts.FlattenOpts.BasePath = absBasePath // BasePath must be absolute
opts.FlattenOpts.Spec = analysis.New(specDoc.Spec())
var preprocessingOption string
switch {
case opts.FlattenOpts.Expand:
preprocessingOption = "expand"
case opts.FlattenOpts.Minimal:
preprocessingOption = "minimal flattening"
default:
preprocessingOption = "full flattening"
}
log.Printf("preprocessing spec with option: %s", preprocessingOption)
if err = analysis.Flatten(*opts.FlattenOpts); err != nil {
return nil, err
}
// yields the preprocessed spec document
return specDoc, nil
}
// gatherSecuritySchemes produces a sorted representation from a map of spec security schemes
func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string) (security GenSecuritySchemes) {
for scheme, req := range securitySchemes {
@ -1285,3 +964,17 @@ func sharedValidationsFromSchema(v spec.Schema, isRequired bool) (sh sharedValid
}
return
}
func dumpData(data interface{}) error {
bb, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
fmt.Fprintln(os.Stdout, string(bb))
return nil
}
func importAlias(pkg string) string {
_, k := path.Split(pkg)
return k
}

View file

@ -0,0 +1,248 @@
package generator
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/go-openapi/analysis"
swaggererrors "github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"gopkg.in/yaml.v2"
)
func (g *GenOpts) validateAndFlattenSpec() (*loads.Document, error) {
// Load spec document
specDoc, err := loads.Spec(g.Spec)
if err != nil {
return nil, err
}
// If accepts definitions only, add dummy swagger header to pass validation
if g.AcceptDefinitionsOnly {
specDoc, err = applyDefaultSwagger(specDoc)
if err != nil {
return nil, err
}
}
// Validate if needed
if g.ValidateSpec {
log.Printf("validating spec %v", g.Spec)
validationErrors := validate.Spec(specDoc, strfmt.Default)
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)
}
return nil, errors.New(str)
}
// TODO(fredbi): due to uncontrolled $ref state in spec, we need to reload the spec atm, or flatten won't
// work properly (validate expansion alters the $ref cache in go-openapi/spec)
specDoc, _ = loads.Spec(g.Spec)
}
// Flatten spec
//
// Some preprocessing is required before codegen
//
// This ensures at least that $ref's in the spec document are canonical,
// i.e all $ref are local to this file and point to some uniquely named definition.
//
// Default option is to ensure minimal flattening of $ref, bundling remote $refs and relocating arbitrary JSON
// pointers as definitions.
// This preprocessing may introduce duplicate names (e.g. remote $ref with same name). In this case, a definition
// suffixed with "OAIGen" is produced.
//
// Full flattening option farther transforms the spec by moving every complex object (e.g. with some properties)
// as a standalone definition.
//
// Eventually, an "expand spec" option is available. It is essentially useful for testing purposes.
//
// NOTE(fredbi): spec expansion may produce some unsupported constructs and is not yet protected against the
// following cases:
// - polymorphic types generation may fail with expansion (expand destructs the reuse intent of the $ref in allOf)
// - name duplicates may occur and result in compilation failures
//
// The right place to fix these shortcomings is go-openapi/analysis.
g.FlattenOpts.BasePath = specDoc.SpecFilePath()
g.FlattenOpts.Spec = analysis.New(specDoc.Spec())
g.printFlattenOpts()
if err = analysis.Flatten(*g.FlattenOpts); err != nil {
return nil, err
}
// yields the preprocessed spec document
return specDoc, nil
}
func (g *GenOpts) analyzeSpec() (*loads.Document, *analysis.Spec, error) {
// spec preprocessing option
if g.PropertiesSpecOrder {
g.Spec = WithAutoXOrder(g.Spec)
}
// load, validate and flatten
specDoc, err := g.validateAndFlattenSpec()
if err != nil {
return nil, nil, err
}
// analyze the spec
analyzed := analysis.New(specDoc.Spec())
return specDoc, analyzed, nil
}
func (g *GenOpts) printFlattenOpts() {
var preprocessingOption string
switch {
case g.FlattenOpts.Expand:
preprocessingOption = "expand"
case g.FlattenOpts.Minimal:
preprocessingOption = "minimal flattening"
default:
preprocessingOption = "full flattening"
}
log.Printf("preprocessing spec with option: %s", preprocessingOption)
}
//findSwaggerSpec fetches a default swagger spec if none is provided
func findSwaggerSpec(nm string) (string, error) {
specs := []string{"swagger.json", "swagger.yml", "swagger.yaml"}
if nm != "" {
specs = []string{nm}
}
var name string
for _, nn := range specs {
f, err := os.Stat(nn)
if err != nil {
if os.IsNotExist(err) {
continue
}
return "", err
}
if f.IsDir() {
return "", fmt.Errorf("%s is a directory", nn)
}
name = nn
break
}
if name == "" {
return "", errors.New("couldn't find a swagger spec")
}
return name, nil
}
// WithAutoXOrder amends the spec to specify property order as they appear
// in the spec (supports yaml documents only).
func WithAutoXOrder(specPath string) string {
lookFor := func(ele interface{}, key string) (yaml.MapSlice, bool) {
if slice, ok := ele.(yaml.MapSlice); ok {
for _, v := range slice {
if v.Key == key {
if slice, ok := v.Value.(yaml.MapSlice); ok {
return slice, ok
}
}
}
}
return nil, false
}
var addXOrder func(interface{})
addXOrder = func(element interface{}) {
if props, ok := lookFor(element, "properties"); ok {
for i, prop := range props {
if pSlice, ok := prop.Value.(yaml.MapSlice); ok {
isObject := false
xOrderIndex := -1 //Find if x-order already exists
for i, v := range pSlice {
if v.Key == "type" && v.Value == object {
isObject = true
}
if v.Key == xOrder {
xOrderIndex = i
break
}
}
if xOrderIndex > -1 { //Override existing x-order
pSlice[xOrderIndex] = yaml.MapItem{Key: xOrder, Value: i}
} else { // append new x-order
pSlice = append(pSlice, yaml.MapItem{Key: xOrder, Value: i})
}
prop.Value = pSlice
props[i] = prop
if isObject {
addXOrder(pSlice)
}
}
}
}
}
yamlDoc, err := swag.YAMLData(specPath)
if err != nil {
panic(err)
}
if defs, ok := lookFor(yamlDoc, "definitions"); ok {
for _, def := range defs {
addXOrder(def.Value)
}
}
addXOrder(yamlDoc)
out, err := yaml.Marshal(yamlDoc)
if err != nil {
panic(err)
}
tmpFile, err := ioutil.TempFile("", filepath.Base(specPath))
if err != nil {
panic(err)
}
if err := ioutil.WriteFile(tmpFile.Name(), out, 0); err != nil {
panic(err)
}
return tmpFile.Name()
}
func applyDefaultSwagger(doc *loads.Document) (*loads.Document, error) {
// bake a minimal swagger spec to pass validation
swspec := doc.Spec()
if swspec.Swagger == "" {
swspec.Swagger = "2.0"
}
if swspec.Info == nil {
info := new(spec.Info)
info.Version = "0.0.0"
info.Title = "minimal"
swspec.Info = info
}
if swspec.Paths == nil {
swspec.Paths = &spec.Paths{}
}
// rewrite the document with the new addition
jazon, err := json.Marshal(swspec)
if err != nil {
return nil, err
}
return loads.Analyzed(jazon, swspec.Swagger)
}

View file

@ -28,7 +28,7 @@ type GenDefinition struct {
GenSchema
Package string
Imports map[string]string
DefaultImports []string
DefaultImports map[string]string
ExtraSchemas GenSchemaList
DependsOn []string
External bool
@ -91,6 +91,9 @@ type GenSchema struct {
IncludeValidator bool
IncludeModel bool
Default interface{}
WantsMarshalBinary bool // do we generate MarshalBinary interface?
StructTags []string
ExtraImports map[string]string // non-standard imports detected when using external types
}
func (g GenSchemaList) Len() int { return len(g) }
@ -167,9 +170,12 @@ type GenResponse struct {
AllowsForStreaming bool
Imports map[string]string
DefaultImports []string
DefaultImports map[string]string
Extensions map[string]interface{}
StrictResponders bool
OperationName string
}
// GenHeader represents a header on a response for code generation
@ -355,6 +361,8 @@ type GenItems struct {
// instructs generator to skip the splitting and parsing from CollectionFormat
SkipParse bool
// instructs generator that some nested structure needs an higher level loop index
NeedsIndex bool
}
// ItemsDepth returns a string "items.items..." with as many items as the level of nesting of the array.
@ -378,9 +386,10 @@ type GenOperationGroup struct {
Summary string
Description string
Imports map[string]string
DefaultImports []string
DefaultImports map[string]string
RootPackage string
GenOpts *GenOpts
PackageAlias string
}
// GenOperationGroups is a sorted collection of operation groups
@ -398,13 +407,20 @@ func (g GenStatusCodeResponses) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
func (g GenStatusCodeResponses) Less(i, j int) bool { return g[i].Code < g[j].Code }
// MarshalJSON marshals these responses to json
//
// This is used by DumpData.
func (g GenStatusCodeResponses) MarshalJSON() ([]byte, error) {
if g == nil {
return nil, nil
}
responses := make(GenStatusCodeResponses, len(g))
copy(responses, g)
// order marshalled output
sort.Sort(responses)
var buf bytes.Buffer
buf.WriteRune('{')
for i, v := range g {
for i, v := range responses {
rb, err := json.Marshal(v)
if err != nil {
return nil, err
@ -446,11 +462,13 @@ type GenOperation struct {
Path string
BasePath string
Tags []string
UseTags bool
RootPackage string
Imports map[string]string
DefaultImports []string
DefaultImports map[string]string
ExtraSchemas GenSchemaList
PackageAlias string
Authorized bool
Security []GenSecurityRequirements
@ -483,6 +501,8 @@ type GenOperation struct {
TimeoutName string
Extensions map[string]interface{}
StrictResponders bool
}
// GenOperations represents a list of operations to generate
@ -509,7 +529,7 @@ type GenApp struct {
Info *spec.Info
ExternalDocs *spec.ExternalDocumentation
Imports map[string]string
DefaultImports []string
DefaultImports map[string]string
Schemes []string
ExtraSchemes []string
Consumes GenSerGroups
@ -521,7 +541,7 @@ type GenApp struct {
SwaggerJSON string
// Embedded specs: this is important for when the generated server adds routes.
// NOTE: there is a distinct advantage to having this in runtime rather than generated code.
// We are noti ever going to generate the router.
// We are not ever going to generate the router.
// If embedding spec is an issue (e.g. memory usage), this can be excluded with the --exclude-spec
// generation option. Alternative methods to serve spec (e.g. from disk, ...) may be implemented by
// adding a middleware to the generated API.
@ -563,16 +583,14 @@ type GenSerGroups []GenSerGroup
func (g GenSerGroups) Len() int { return len(g) }
func (g GenSerGroups) Swap(i, j int) { g[i], g[j] = g[j], g[i] }
func (g GenSerGroups) Less(i, j int) bool { return g[i].MediaType < g[j].MediaType }
func (g GenSerGroups) Less(i, j int) bool { return g[i].Name < g[j].Name }
// GenSerGroup represents a group of serializers, most likely this is a media type to a list of
// prioritized serializers.
// GenSerGroup represents a group of serializers: this links a serializer to a list of
// prioritized media types (mime).
type GenSerGroup struct {
ReceiverName string
AppName string
Name string
MediaType string
Implementation string
GenSerializer
// All media types for this serializer. The redundant representation allows for easier use in templates
AllSerializers GenSerializers
}
@ -585,11 +603,12 @@ func (g GenSerializers) Less(i, j int) bool { return g[i].MediaType < g[j].Media
// GenSerializer represents a single serializer for a particular media type
type GenSerializer struct {
AppName string // Application name
ReceiverName string
AppName string
Name string
MediaType string
Implementation string
Name string // Name of the Producer/Consumer (e.g. json, yaml, txt, bin)
MediaType string // mime
Implementation string // func implementing the Producer/Consumer
Parameters []string // parameters supported by this serializer
}
// GenSecurityScheme represents a security scheme for code generation

View file

@ -1,4 +1,5 @@
// 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.
@ -19,21 +20,13 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"regexp"
goruntime "runtime"
"sort"
"strings"
yaml "gopkg.in/yaml.v2"
"github.com/go-openapi/analysis"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
)
@ -57,85 +50,37 @@ func GenerateSupport(name string, modelNames, operationIDs []string, opts *GenOp
}
func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOpts) (*appGenerator, error) {
if opts == nil {
return nil, errors.New("gen opts are required")
}
if err := opts.CheckOpts(); err != nil {
return nil, err
}
templates.LoadDefaults()
if opts.Template != "" {
if err := templates.LoadContrib(opts.Template); err != nil {
return nil, err
}
}
templates.SetAllowOverride(opts.AllowTemplateOverride)
if opts.TemplateDir != "" {
if err := templates.LoadDir(opts.TemplateDir); err != nil {
return nil, err
}
}
// Load the spec
var err error
var specDoc *loads.Document
opts.Spec, err = findSwaggerSpec(opts.Spec)
if err != nil {
if err := opts.setTemplates(); err != nil {
return nil, err
}
if !filepath.IsAbs(opts.Spec) {
cwd, _ := os.Getwd()
opts.Spec = filepath.Join(cwd, opts.Spec)
}
if opts.PropertiesSpecOrder {
opts.Spec = WithAutoXOrder(opts.Spec)
}
opts.Spec, specDoc, err = loadSpec(opts.Spec)
specDoc, analyzed, err := opts.analyzeSpec()
if err != nil {
return nil, err
}
specDoc, err = validateAndFlattenSpec(opts, specDoc)
if err != nil {
return nil, err
}
analyzed := analysis.New(specDoc.Spec())
models, err := gatherModels(specDoc, modelNames)
if err != nil {
return nil, err
}
operations := gatherOperations(analyzed, operationIDs)
if len(operations) == 0 {
if len(operations) == 0 && !opts.IgnoreOperations {
return nil, errors.New("no operations were selected")
}
defaultScheme := opts.DefaultScheme
if defaultScheme == "" {
defaultScheme = "http"
opts.Name = appNameOrDefault(specDoc, name, defaultServerName)
if opts.IncludeMain && opts.MainPackage == "" {
// default target for the generated main
opts.MainPackage = swag.ToCommandName(mainNameOrDefault(specDoc, name, defaultServerName) + "-server")
}
defaultProduces := opts.DefaultProduces
if defaultProduces == "" {
defaultProduces = runtime.JSONMime
}
defaultConsumes := opts.DefaultConsumes
if defaultConsumes == "" {
defaultConsumes = runtime.JSONMime
}
opts.Name = appNameOrDefault(specDoc, name, "swagger")
apiPackage := opts.LanguageOpts.ManglePackagePath(opts.APIPackage, "api")
apiPackage := opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget)
return &appGenerator{
Name: opts.Name,
Receiver: "o",
@ -145,16 +90,16 @@ func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOp
Operations: operations,
Target: opts.Target,
DumpData: opts.DumpData,
Package: opts.LanguageOpts.ManglePackageName(apiPackage, "api"),
Package: opts.LanguageOpts.ManglePackageName(apiPackage, defaultOperationsTarget),
APIPackage: apiPackage,
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, "definitions"),
ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server"),
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, "client"),
OperationsPackage: filepath.Join(opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server"), apiPackage),
Principal: opts.Principal,
DefaultScheme: defaultScheme,
DefaultProduces: defaultProduces,
DefaultConsumes: defaultConsumes,
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget),
ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget),
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
OperationsPackage: filepath.Join(opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget), apiPackage),
Principal: opts.PrincipalAlias(),
DefaultScheme: opts.DefaultScheme,
DefaultProduces: opts.DefaultProduces,
DefaultConsumes: opts.DefaultConsumes,
GenOpts: opts,
}, nil
}
@ -170,6 +115,7 @@ type appGenerator struct {
ServerPackage string
ClientPackage string
OperationsPackage string
MainPackage string
Principal string
Models map[string]spec.Schema
Operations map[string]opRef
@ -181,120 +127,14 @@ type appGenerator struct {
GenOpts *GenOpts
}
func WithAutoXOrder(specPath string) string {
lookFor := func(ele interface{}, key string) (yaml.MapSlice, bool) {
if slice, ok := ele.(yaml.MapSlice); ok {
for _, v := range slice {
if v.Key == key {
if slice, ok := v.Value.(yaml.MapSlice); ok {
return slice, ok
}
}
}
}
return nil, false
}
var addXOrder func(interface{})
addXOrder = func(element interface{}) {
if props, ok := lookFor(element, "properties"); ok {
for i, prop := range props {
if pSlice, ok := prop.Value.(yaml.MapSlice); ok {
isObject := false
xOrderIndex := -1 //Find if x-order already exists
for i, v := range pSlice {
if v.Key == "type" && v.Value == object {
isObject = true
}
if v.Key == xOrder {
xOrderIndex = i
break
}
}
if xOrderIndex > -1 { //Override existing x-order
pSlice[xOrderIndex] = yaml.MapItem{Key: xOrder, Value: i}
} else { // append new x-order
pSlice = append(pSlice, yaml.MapItem{Key: xOrder, Value: i})
}
prop.Value = pSlice
props[i] = prop
if isObject {
addXOrder(pSlice)
}
}
}
}
}
yamlDoc, err := swag.YAMLData(specPath)
if err != nil {
panic(err)
}
if defs, ok := lookFor(yamlDoc, "definitions"); ok {
for _, def := range defs {
addXOrder(def.Value)
}
}
addXOrder(yamlDoc)
out, err := yaml.Marshal(yamlDoc)
if err != nil {
panic(err)
}
tmpFile, err := ioutil.TempFile("", filepath.Base(specPath))
if err != nil {
panic(err)
}
if err := ioutil.WriteFile(tmpFile.Name(), out, 0); err != nil {
panic(err)
}
return tmpFile.Name()
}
// 1. Checks if the child path and parent path coincide.
// 2. If they do return child path relative to parent path.
// 3. Everything else return false
func checkPrefixAndFetchRelativePath(childpath string, parentpath string) (bool, string) {
// Windows (local) file systems - NTFS, as well as FAT and variants
// are case insensitive.
cp, pp := childpath, parentpath
if goruntime.GOOS == "windows" {
cp = strings.ToLower(cp)
pp = strings.ToLower(pp)
}
if strings.HasPrefix(cp, pp) {
pth, err := filepath.Rel(parentpath, childpath)
if err != nil {
log.Fatalln(err)
}
return true, pth
}
return false, ""
}
func (a *appGenerator) Generate() error {
app, err := a.makeCodegenApp()
if err != nil {
return err
}
if a.DumpData {
bb, err := json.MarshalIndent(app, "", " ")
if err != nil {
return err
}
fmt.Fprintln(os.Stdout, string(bb))
return nil
return dumpData(app)
}
// NOTE: relative to previous implem with chan.
@ -303,10 +143,9 @@ func (a *appGenerator) Generate() error {
if a.GenOpts.IncludeModel {
log.Printf("rendering %d models", len(app.Models))
for _, mod := range app.Models {
modCopy := mod
modCopy.IncludeValidator = true // a.GenOpts.IncludeValidator
modCopy.IncludeModel = true
if err := a.GenOpts.renderDefinition(&modCopy); err != nil {
mod.IncludeModel = true
mod.IncludeValidator = a.GenOpts.IncludeValidator
if err := a.GenOpts.renderDefinition(&mod); err != nil {
return err
}
}
@ -315,19 +154,14 @@ func (a *appGenerator) Generate() error {
if a.GenOpts.IncludeHandler {
log.Printf("rendering %d operation groups (tags)", app.OperationGroups.Len())
for _, opg := range app.OperationGroups {
opgCopy := opg
log.Printf("rendering %d operations for %s", opg.Operations.Len(), opg.Name)
for _, op := range opgCopy.Operations {
opCopy := op
if err := a.GenOpts.renderOperation(&opCopy); err != nil {
for _, op := range opg.Operations {
if err := a.GenOpts.renderOperation(&op); err != nil {
return err
}
}
// Optional OperationGroups templates generation
opGroup := opg
opGroup.DefaultImports = app.DefaultImports
if err := a.GenOpts.renderOperationGroup(&opGroup); err != nil {
// optional OperationGroups templates generation
if err := a.GenOpts.renderOperationGroup(&opg); err != nil {
return fmt.Errorf("error while rendering operation group: %v", err)
}
}
@ -345,247 +179,23 @@ func (a *appGenerator) Generate() error {
func (a *appGenerator) GenerateSupport(ap *GenApp) error {
app := ap
if ap == nil {
// allows for calling GenerateSupport standalone
ca, err := a.makeCodegenApp()
if err != nil {
return err
}
app = &ca
}
baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
importPath := path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, ""))
app.DefaultImports = append(
app.DefaultImports,
path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, "")),
importPath,
)
serverPath := path.Join(baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, defaultServerTarget))
app.DefaultImports[importAlias(serverPath)] = serverPath
return a.GenOpts.renderApplication(app)
}
var mediaTypeNames = map[*regexp.Regexp]string{
regexp.MustCompile("application/.*json"): "json",
regexp.MustCompile("application/.*yaml"): "yaml",
regexp.MustCompile("application/.*protobuf"): "protobuf",
regexp.MustCompile("application/.*capnproto"): "capnproto",
regexp.MustCompile("application/.*thrift"): "thrift",
regexp.MustCompile("(?:application|text)/.*xml"): "xml",
regexp.MustCompile("text/.*markdown"): "markdown",
regexp.MustCompile("text/.*html"): "html",
regexp.MustCompile("text/.*csv"): "csv",
regexp.MustCompile("text/.*tsv"): "tsv",
regexp.MustCompile("text/.*javascript"): "js",
regexp.MustCompile("text/.*css"): "css",
regexp.MustCompile("text/.*plain"): "txt",
regexp.MustCompile("application/.*octet-stream"): "bin",
regexp.MustCompile("application/.*tar"): "tar",
regexp.MustCompile("application/.*gzip"): "gzip",
regexp.MustCompile("application/.*gz"): "gzip",
regexp.MustCompile("application/.*raw-stream"): "bin",
regexp.MustCompile("application/x-www-form-urlencoded"): "urlform",
regexp.MustCompile("multipart/form-data"): "multipartform",
}
var knownProducers = map[string]string{
"json": "runtime.JSONProducer()",
"yaml": "yamlpc.YAMLProducer()",
"xml": "runtime.XMLProducer()",
"txt": "runtime.TextProducer()",
"bin": "runtime.ByteStreamProducer()",
"urlform": "runtime.DiscardProducer",
"multipartform": "runtime.DiscardProducer",
}
var knownConsumers = map[string]string{
"json": "runtime.JSONConsumer()",
"yaml": "yamlpc.YAMLConsumer()",
"xml": "runtime.XMLConsumer()",
"txt": "runtime.TextConsumer()",
"bin": "runtime.ByteStreamConsumer()",
"urlform": "runtime.DiscardConsumer",
"multipartform": "runtime.DiscardConsumer",
}
func getSerializer(sers []GenSerGroup, ext string) (*GenSerGroup, bool) {
for i := range sers {
s := &sers[i]
if s.Name == ext {
return s, true
}
}
return nil, false
}
func mediaTypeName(tn string) (string, bool) {
for k, v := range mediaTypeNames {
if k.MatchString(tn) {
return v, true
}
}
return "", false
}
func (a *appGenerator) makeConsumes() (consumes GenSerGroups, consumesJSON bool) {
reqCons := a.Analyzed.RequiredConsumes()
sort.Strings(reqCons)
for _, cons := range reqCons {
cn, ok := mediaTypeName(cons)
if !ok {
nm := swag.ToJSONName(cons)
ser := GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: nm,
MediaType: cons,
Implementation: "",
}
consumes = append(consumes, GenSerGroup{
AppName: ser.AppName,
ReceiverName: ser.ReceiverName,
Name: ser.Name,
MediaType: cons,
AllSerializers: []GenSerializer{ser},
Implementation: ser.Implementation,
})
continue
}
nm := swag.ToJSONName(cn)
if nm == "json" {
consumesJSON = true
}
if ser, ok := getSerializer(consumes, cn); ok {
ser.AllSerializers = append(ser.AllSerializers, GenSerializer{
AppName: ser.AppName,
ReceiverName: ser.ReceiverName,
Name: ser.Name,
MediaType: cons,
Implementation: knownConsumers[nm],
})
sort.Sort(ser.AllSerializers)
continue
}
ser := GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: nm,
MediaType: cons,
Implementation: knownConsumers[nm],
}
consumes = append(consumes, GenSerGroup{
AppName: ser.AppName,
ReceiverName: ser.ReceiverName,
Name: ser.Name,
MediaType: cons,
AllSerializers: []GenSerializer{ser},
Implementation: ser.Implementation,
})
}
if len(consumes) == 0 {
consumes = append(consumes, GenSerGroup{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: "json",
MediaType: runtime.JSONMime,
AllSerializers: []GenSerializer{{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: "json",
MediaType: runtime.JSONMime,
Implementation: knownConsumers["json"],
}},
Implementation: knownConsumers["json"],
})
consumesJSON = true
}
sort.Sort(consumes)
return
}
func (a *appGenerator) makeProduces() (produces GenSerGroups, producesJSON bool) {
reqProds := a.Analyzed.RequiredProduces()
sort.Strings(reqProds)
for _, prod := range reqProds {
pn, ok := mediaTypeName(prod)
if !ok {
nm := swag.ToJSONName(prod)
ser := GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: nm,
MediaType: prod,
Implementation: "",
}
produces = append(produces, GenSerGroup{
AppName: ser.AppName,
ReceiverName: ser.ReceiverName,
Name: ser.Name,
MediaType: prod,
Implementation: ser.Implementation,
AllSerializers: []GenSerializer{ser},
})
continue
}
nm := swag.ToJSONName(pn)
if nm == "json" {
producesJSON = true
}
if ser, ok := getSerializer(produces, pn); ok {
ser.AllSerializers = append(ser.AllSerializers, GenSerializer{
AppName: ser.AppName,
ReceiverName: ser.ReceiverName,
Name: ser.Name,
MediaType: prod,
Implementation: knownProducers[nm],
})
sort.Sort(ser.AllSerializers)
continue
}
ser := GenSerializer{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: nm,
MediaType: prod,
Implementation: knownProducers[nm],
}
produces = append(produces, GenSerGroup{
AppName: ser.AppName,
ReceiverName: ser.ReceiverName,
Name: ser.Name,
MediaType: prod,
Implementation: ser.Implementation,
AllSerializers: []GenSerializer{ser},
})
}
if len(produces) == 0 {
produces = append(produces, GenSerGroup{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: "json",
MediaType: runtime.JSONMime,
AllSerializers: []GenSerializer{{
AppName: a.Name,
ReceiverName: a.Receiver,
Name: "json",
MediaType: runtime.JSONMime,
Implementation: knownProducers["json"],
}},
Implementation: knownProducers["json"],
})
producesJSON = true
}
sort.Sort(produces)
return
}
func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes {
if a.Principal == "" {
a.Principal = "interface{}"
}
requiredSecuritySchemes := make(map[string]spec.SecurityScheme, len(a.Analyzed.RequiredSecuritySchemes()))
for _, scheme := range a.Analyzed.RequiredSecuritySchemes() {
if req, ok := a.SpecDoc.Spec().SecurityDefinitions[scheme]; ok && req != nil {
@ -597,36 +207,25 @@ func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes {
func (a *appGenerator) makeCodegenApp() (GenApp, error) {
log.Println("building a plan for generation")
sw := a.SpecDoc.Spec()
receiver := a.Receiver
var defaultImports []string
jsonb, _ := json.MarshalIndent(a.SpecDoc.OrigSpec(), "", " ")
flatjsonb, _ := json.MarshalIndent(a.SpecDoc.Spec(), "", " ")
consumes, _ := a.makeConsumes()
produces, _ := a.makeProduces()
sort.Sort(consumes)
sort.Sort(produces)
security := a.makeSecuritySchemes()
baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
var imports = make(map[string]string)
var genMods GenDefinitions
importPath := a.GenOpts.ExistingModels
if a.GenOpts.ExistingModels == "" {
imports[a.GenOpts.LanguageOpts.ManglePackageName(a.ModelsPackage, "models")] = path.Join(
filepath.ToSlash(baseImport),
a.GenOpts.LanguageOpts.ManglePackagePath(a.GenOpts.ModelPackage, "models"))
}
if importPath != "" {
defaultImports = append(defaultImports, importPath)
}
log.Println("generation target", a.Target)
baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
defaultImports := a.GenOpts.defaultImports()
imports := a.GenOpts.initImports(a.OperationsPackage)
log.Println("planning definitions")
genModels := make(GenDefinitions, 0, len(a.Models))
for mn, m := range a.Models {
mod, err := makeGenDefinition(
model, err := makeGenDefinition(
mn,
a.ModelsPackage,
m,
@ -636,52 +235,59 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
if err != nil {
return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %v", mn, err)
}
if mod != nil {
if !mod.External {
genMods = append(genMods, *mod)
if model != nil {
if !model.External {
genModels = append(genModels, *model)
}
// Copy model imports to operation imports
for alias, pkg := range mod.Imports {
// TODO(fredbi): mangle model pkg aliases
for alias, pkg := range model.Imports {
target := a.GenOpts.LanguageOpts.ManglePackageName(alias, "")
imports[target] = pkg
}
}
}
sort.Sort(genMods)
sort.Sort(genModels)
log.Println("planning operations")
tns := make(map[string]struct{})
var genOps GenOperations
for on, opp := range a.Operations {
genOps := make(GenOperations, 0, len(a.Operations))
for operationName, opp := range a.Operations {
o := opp.Op
o.Tags = pruneEmpty(o.Tags)
o.ID = on
o.ID = operationName
var bldr codeGenOpBuilder
bldr.ModelsPackage = a.ModelsPackage
bldr.Principal = a.Principal
bldr.Target = a.Target
bldr.DefaultImports = defaultImports
bldr.Imports = imports
bldr.DefaultScheme = a.DefaultScheme
bldr.Doc = a.SpecDoc
bldr.Analyzed = a.Analyzed
bldr.BasePath = a.SpecDoc.BasePath()
bldr.GenOpts = a.GenOpts
bldr := codeGenOpBuilder{
ModelsPackage: a.ModelsPackage,
Principal: a.GenOpts.PrincipalAlias(),
Target: a.Target,
DefaultImports: defaultImports,
Imports: imports,
DefaultScheme: a.DefaultScheme,
Doc: a.SpecDoc,
Analyzed: a.Analyzed,
BasePath: a.SpecDoc.BasePath(),
GenOpts: a.GenOpts,
Name: operationName,
Operation: *o,
Method: opp.Method,
Path: opp.Path,
IncludeValidator: a.GenOpts.IncludeValidator,
APIPackage: a.APIPackage, // defaults to main operations package
DefaultProduces: a.DefaultProduces,
DefaultConsumes: a.DefaultConsumes,
}
tag, tags, ok := bldr.analyzeTags()
if !ok {
continue // operation filtered according to CLI params
}
// TODO: change operation name to something safe
bldr.Name = on
bldr.Operation = *o
bldr.Method = opp.Method
bldr.Path = opp.Path
bldr.Authed = len(a.Analyzed.SecurityRequirementsFor(o)) > 0
bldr.Security = a.Analyzed.SecurityRequirementsFor(o)
bldr.SecurityDefinitions = a.Analyzed.SecurityDefinitionsFor(o)
bldr.RootAPIPackage = a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, "server")
bldr.IncludeValidator = true
bldr.RootAPIPackage = a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget)
bldr.APIPackage = a.APIPackage
st := o.Tags
if a.GenOpts != nil {
st = a.GenOpts.Tags
@ -691,42 +297,35 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
continue
}
if len(intersected) > 0 {
tag := intersected[0]
bldr.APIPackage = a.GenOpts.LanguageOpts.ManglePackagePath(tag, a.APIPackage)
for _, t := range intersected {
tns[t] = struct{}{}
}
}
op, err := bldr.MakeOperation()
if err != nil {
return GenApp{}, err
}
op.ReceiverName = receiver
op.Tags = intersected
op.Tags = tags // ordered tags for this operation, possibly filtered by CLI params
genOps = append(genOps, op)
}
for k := range tns {
importPath := filepath.ToSlash(
path.Join(
filepath.ToSlash(baseImport),
a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, ""),
swag.ToFileName(k)))
defaultImports = append(defaultImports, importPath)
if !a.GenOpts.SkipTagPackages && tag != "" {
importPath := filepath.ToSlash(
path.Join(
baseImport,
a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget),
a.GenOpts.LanguageOpts.ManglePackageName(bldr.APIPackage, defaultOperationsTarget),
))
defaultImports[bldr.APIPackageAlias] = importPath
}
}
sort.Sort(genOps)
log.Println("grouping operations into packages")
opsGroupedByPackage := make(map[string]GenOperations)
opsGroupedByPackage := make(map[string]GenOperations, len(genOps))
for _, operation := range genOps {
if operation.Package == "" {
operation.Package = a.Package
}
opsGroupedByPackage[operation.Package] = append(opsGroupedByPackage[operation.Package], operation)
opsGroupedByPackage[operation.PackageAlias] = append(opsGroupedByPackage[operation.PackageAlias], operation)
}
var opGroups GenOperationGroups
opGroups := make(GenOperationGroups, 0, len(opsGroupedByPackage))
for k, v := range opsGroupedByPackage {
sort.Sort(v)
// trim duplicate extra schemas within the same package
@ -743,13 +342,20 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
op.ExtraSchemas = uniqueExtraSchemas
vv = append(vv, op)
}
var pkg string
if len(vv) > 0 {
pkg = vv[0].Package
} else {
pkg = k
}
opGroup := GenOperationGroup{
GenCommon: GenCommon{
Copyright: a.GenOpts.Copyright,
TargetImportPath: filepath.ToSlash(baseImport),
TargetImportPath: baseImport,
},
Name: k,
Name: pkg,
PackageAlias: k,
Operations: vv,
DefaultImports: defaultImports,
Imports: imports,
@ -757,13 +363,6 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
GenOpts: a.GenOpts,
}
opGroups = append(opGroups, opGroup)
var importPath string
if k == a.APIPackage {
importPath = path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, ""))
} else {
importPath = path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, ""), k)
}
defaultImports = append(defaultImports, importPath)
}
sort.Sort(opGroups)
@ -788,12 +387,15 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
basePath = sw.BasePath
}
jsonb, _ := json.MarshalIndent(a.SpecDoc.OrigSpec(), "", " ")
flatjsonb, _ := json.MarshalIndent(a.SpecDoc.Spec(), "", " ")
return GenApp{
GenCommon: GenCommon{
Copyright: a.GenOpts.Copyright,
TargetImportPath: filepath.ToSlash(baseImport),
TargetImportPath: baseImport,
},
APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, "server"),
APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget),
Package: a.Package,
ReceiverName: receiver,
Name: a.Name,
@ -810,13 +412,13 @@ func (a *appGenerator) makeCodegenApp() (GenApp, error) {
DefaultImports: defaultImports,
Imports: imports,
SecurityDefinitions: security,
Models: genMods,
Models: genModels,
Operations: genOps,
OperationGroups: opGroups,
Principal: a.Principal,
Principal: a.GenOpts.PrincipalAlias(),
SwaggerJSON: generateReadableSpec(jsonb),
FlatSwaggerJSON: generateReadableSpec(flatjsonb),
ExcludeSpec: a.GenOpts != nil && a.GenOpts.ExcludeSpec,
ExcludeSpec: a.GenOpts.ExcludeSpec,
GenOpts: a.GenOpts,
}, nil
}

View file

@ -11,6 +11,7 @@ import (
"strings"
"text/template"
"text/template/parse"
"unicode"
"log"
@ -19,177 +20,166 @@ import (
"github.com/kr/pretty"
)
var templates *Repository
var (
assets map[string][]byte
protectedTemplates map[string]bool
// FuncMap is a map with default functions for use n the templates.
// FuncMapFunc yields a map with all functions for templates
FuncMapFunc func(*LanguageOpts) template.FuncMap
templates *Repository
)
func initTemplateRepo() {
FuncMapFunc = DefaultFuncMap
// this makes the ToGoName func behave with the special
// prefixing rule above
swag.GoNamePrefixFunc = prefixForName
assets = defaultAssets()
protectedTemplates = defaultProtectedTemplates()
templates = NewRepository(FuncMapFunc(DefaultLanguageFunc()))
}
// DefaultFuncMap yields a map with default functions for use n the templates.
// These are available in every template
var FuncMap template.FuncMap = map[string]interface{}{
"pascalize": pascalize,
"camelize": swag.ToJSONName,
"varname": golang.MangleVarName,
"humanize": swag.ToHumanNameLower,
"snakize": golang.MangleFileName,
"toPackagePath": func(name string) string {
return filepath.FromSlash(golang.ManglePackagePath(name, ""))
},
"toPackage": func(name string) string {
return golang.ManglePackagePath(name, "")
},
"toPackageName": func(name string) string {
return golang.ManglePackageName(name, "")
},
"dasherize": swag.ToCommandName,
"pluralizeFirstWord": func(arg string) string {
sentence := strings.Split(arg, " ")
if len(sentence) == 1 {
return inflect.Pluralize(arg)
}
return inflect.Pluralize(sentence[0]) + " " + strings.Join(sentence[1:], " ")
},
"json": asJSON,
"prettyjson": asPrettyJSON,
"hasInsecure": func(arg []string) bool {
return swag.ContainsStringsCI(arg, "http") || swag.ContainsStringsCI(arg, "ws")
},
"hasSecure": func(arg []string) bool {
return swag.ContainsStringsCI(arg, "https") || swag.ContainsStringsCI(arg, "wss")
},
// TODO: simplify redundant functions
"stripPackage": func(str, pkg string) string {
parts := strings.Split(str, ".")
strlen := len(parts)
if strlen > 0 {
return parts[strlen-1]
}
return str
},
"dropPackage": func(str string) string {
parts := strings.Split(str, ".")
strlen := len(parts)
if strlen > 0 {
return parts[strlen-1]
}
return str
},
"upper": strings.ToUpper,
"contains": func(coll []string, arg string) bool {
for _, v := range coll {
if v == arg {
return true
}
}
return false
},
"padSurround": func(entry, padWith string, i, ln int) string {
var res []string
if i > 0 {
for j := 0; j < i; j++ {
res = append(res, padWith)
}
}
res = append(res, entry)
tot := ln - i - 1
for j := 0; j < tot; j++ {
res = append(res, padWith)
}
return strings.Join(res, ",")
},
"joinFilePath": filepath.Join,
"comment": func(str string) string {
lines := strings.Split(str, "\n")
return (strings.Join(lines, "\n// "))
},
"blockcomment": func(str string) string {
return strings.Replace(str, "*/", "[*]/", -1)
},
"inspect": pretty.Sprint,
"cleanPath": path.Clean,
"mediaTypeName": func(orig string) string {
return strings.SplitN(orig, ";", 2)[0]
},
"goSliceInitializer": goSliceInitializer,
"hasPrefix": strings.HasPrefix,
"stringContains": strings.Contains,
func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
return template.FuncMap(map[string]interface{}{
"pascalize": pascalize,
"camelize": swag.ToJSONName,
"varname": lang.MangleVarName,
"humanize": swag.ToHumanNameLower,
"snakize": lang.MangleFileName,
"toPackagePath": func(name string) string {
return filepath.FromSlash(lang.ManglePackagePath(name, ""))
},
"toPackage": func(name string) string {
return lang.ManglePackagePath(name, "")
},
"toPackageName": func(name string) string {
return lang.ManglePackageName(name, "")
},
"dasherize": swag.ToCommandName,
"pluralizeFirstWord": pluralizeFirstWord,
"json": asJSON,
"prettyjson": asPrettyJSON,
"hasInsecure": func(arg []string) bool {
return swag.ContainsStringsCI(arg, "http") || swag.ContainsStringsCI(arg, "ws")
},
"hasSecure": func(arg []string) bool {
return swag.ContainsStringsCI(arg, "https") || swag.ContainsStringsCI(arg, "wss")
},
"dropPackage": dropPackage,
"upper": strings.ToUpper,
"contains": swag.ContainsStrings,
"padSurround": padSurround,
"joinFilePath": filepath.Join,
"comment": padComment,
"blockcomment": blockComment,
"inspect": pretty.Sprint,
"cleanPath": path.Clean,
"mediaTypeName": mediaMime,
"arrayInitializer": lang.arrayInitializer,
"hasPrefix": strings.HasPrefix,
"stringContains": strings.Contains,
"imports": lang.imports,
"dict": dict,
})
}
func init() {
templates = NewRepository(FuncMap)
func defaultAssets() map[string][]byte {
return map[string][]byte{
// schema validation templates
"validation/primitive.gotmpl": MustAsset("templates/validation/primitive.gotmpl"),
"validation/customformat.gotmpl": MustAsset("templates/validation/customformat.gotmpl"),
"validation/structfield.gotmpl": MustAsset("templates/validation/structfield.gotmpl"),
"structfield.gotmpl": MustAsset("templates/structfield.gotmpl"),
"schemavalidator.gotmpl": MustAsset("templates/schemavalidator.gotmpl"),
"schemapolymorphic.gotmpl": MustAsset("templates/schemapolymorphic.gotmpl"),
"schemaembedded.gotmpl": MustAsset("templates/schemaembedded.gotmpl"),
// schema serialization templates
"additionalpropertiesserializer.gotmpl": MustAsset("templates/serializers/additionalpropertiesserializer.gotmpl"),
"aliasedserializer.gotmpl": MustAsset("templates/serializers/aliasedserializer.gotmpl"),
"allofserializer.gotmpl": MustAsset("templates/serializers/allofserializer.gotmpl"),
"basetypeserializer.gotmpl": MustAsset("templates/serializers/basetypeserializer.gotmpl"),
"marshalbinaryserializer.gotmpl": MustAsset("templates/serializers/marshalbinaryserializer.gotmpl"),
"schemaserializer.gotmpl": MustAsset("templates/serializers/schemaserializer.gotmpl"),
"subtypeserializer.gotmpl": MustAsset("templates/serializers/subtypeserializer.gotmpl"),
"tupleserializer.gotmpl": MustAsset("templates/serializers/tupleserializer.gotmpl"),
// schema generation template
"docstring.gotmpl": MustAsset("templates/docstring.gotmpl"),
"schematype.gotmpl": MustAsset("templates/schematype.gotmpl"),
"schemabody.gotmpl": MustAsset("templates/schemabody.gotmpl"),
"schema.gotmpl": MustAsset("templates/schema.gotmpl"),
"model.gotmpl": MustAsset("templates/model.gotmpl"),
"header.gotmpl": MustAsset("templates/header.gotmpl"),
"swagger_json_embed.gotmpl": MustAsset("templates/swagger_json_embed.gotmpl"),
// server templates
"server/parameter.gotmpl": MustAsset("templates/server/parameter.gotmpl"),
"server/urlbuilder.gotmpl": MustAsset("templates/server/urlbuilder.gotmpl"),
"server/responses.gotmpl": MustAsset("templates/server/responses.gotmpl"),
"server/operation.gotmpl": MustAsset("templates/server/operation.gotmpl"),
"server/builder.gotmpl": MustAsset("templates/server/builder.gotmpl"),
"server/server.gotmpl": MustAsset("templates/server/server.gotmpl"),
"server/configureapi.gotmpl": MustAsset("templates/server/configureapi.gotmpl"),
"server/main.gotmpl": MustAsset("templates/server/main.gotmpl"),
"server/doc.gotmpl": MustAsset("templates/server/doc.gotmpl"),
// client templates
"client/parameter.gotmpl": MustAsset("templates/client/parameter.gotmpl"),
"client/response.gotmpl": MustAsset("templates/client/response.gotmpl"),
"client/client.gotmpl": MustAsset("templates/client/client.gotmpl"),
"client/facade.gotmpl": MustAsset("templates/client/facade.gotmpl"),
}
}
var assets = map[string][]byte{
"validation/primitive.gotmpl": MustAsset("templates/validation/primitive.gotmpl"),
"validation/customformat.gotmpl": MustAsset("templates/validation/customformat.gotmpl"),
"docstring.gotmpl": MustAsset("templates/docstring.gotmpl"),
"validation/structfield.gotmpl": MustAsset("templates/validation/structfield.gotmpl"),
"modelvalidator.gotmpl": MustAsset("templates/modelvalidator.gotmpl"),
"structfield.gotmpl": MustAsset("templates/structfield.gotmpl"),
"tupleserializer.gotmpl": MustAsset("templates/tupleserializer.gotmpl"),
"additionalpropertiesserializer.gotmpl": MustAsset("templates/additionalpropertiesserializer.gotmpl"),
"schematype.gotmpl": MustAsset("templates/schematype.gotmpl"),
"schemabody.gotmpl": MustAsset("templates/schemabody.gotmpl"),
"schema.gotmpl": MustAsset("templates/schema.gotmpl"),
"schemavalidator.gotmpl": MustAsset("templates/schemavalidator.gotmpl"),
"model.gotmpl": MustAsset("templates/model.gotmpl"),
"header.gotmpl": MustAsset("templates/header.gotmpl"),
"swagger_json_embed.gotmpl": MustAsset("templates/swagger_json_embed.gotmpl"),
func defaultProtectedTemplates() map[string]bool {
return map[string]bool{
"dereffedSchemaType": true,
"docstring": true,
"header": true,
"mapvalidator": true,
"model": true,
"modelvalidator": true,
"objectvalidator": true,
"primitivefieldvalidator": true,
"privstructfield": true,
"privtuplefield": true,
"propertyValidationDocString": true,
"propertyvalidator": true,
"schema": true,
"schemaBody": true,
"schemaType": true,
"schemabody": true,
"schematype": true,
"schemavalidator": true,
"serverDoc": true,
"slicevalidator": true,
"structfield": true,
"structfieldIface": true,
"subTypeBody": true,
"swaggerJsonEmbed": true,
"tuplefield": true,
"tuplefieldIface": true,
"typeSchemaType": true,
"validationCustomformat": true,
"validationPrimitive": true,
"validationStructfield": true,
"withBaseTypeBody": true,
"withoutBaseTypeBody": true,
"server/parameter.gotmpl": MustAsset("templates/server/parameter.gotmpl"),
"server/urlbuilder.gotmpl": MustAsset("templates/server/urlbuilder.gotmpl"),
"server/responses.gotmpl": MustAsset("templates/server/responses.gotmpl"),
"server/operation.gotmpl": MustAsset("templates/server/operation.gotmpl"),
"server/builder.gotmpl": MustAsset("templates/server/builder.gotmpl"),
"server/server.gotmpl": MustAsset("templates/server/server.gotmpl"),
"server/configureapi.gotmpl": MustAsset("templates/server/configureapi.gotmpl"),
"server/main.gotmpl": MustAsset("templates/server/main.gotmpl"),
"server/doc.gotmpl": MustAsset("templates/server/doc.gotmpl"),
"client/parameter.gotmpl": MustAsset("templates/client/parameter.gotmpl"),
"client/response.gotmpl": MustAsset("templates/client/response.gotmpl"),
"client/client.gotmpl": MustAsset("templates/client/client.gotmpl"),
"client/facade.gotmpl": MustAsset("templates/client/facade.gotmpl"),
}
var protectedTemplates = map[string]bool{
"schemabody": true,
"privtuplefield": true,
"withoutBaseTypeBody": true,
"swaggerJsonEmbed": true,
"validationCustomformat": true,
"tuplefield": true,
"header": true,
"withBaseTypeBody": true,
"primitivefieldvalidator": true,
"mapvalidator": true,
"propertyValidationDocString": true,
"typeSchemaType": true,
"docstring": true,
"dereffedSchemaType": true,
"model": true,
"modelvalidator": true,
"privstructfield": true,
"schemavalidator": true,
"tuplefieldIface": true,
"tupleSerializer": true,
"tupleserializer": true,
"schemaSerializer": true,
"propertyvalidator": true,
"structfieldIface": true,
"schemaBody": true,
"objectvalidator": true,
"schematype": true,
"additionalpropertiesserializer": true,
"slicevalidator": true,
"validationStructfield": true,
"validationPrimitive": true,
"schemaType": true,
"subTypeBody": true,
"schema": true,
"additionalPropertiesSerializer": true,
"serverDoc": true,
"structfield": true,
"hasDiscriminatedSerializer": true,
"discriminatedSerializer": true,
// all serializers TODO(fred)
"additionalPropertiesSerializer": true,
"tupleSerializer": true,
"schemaSerializer": true,
"hasDiscriminatedSerializer": true,
"discriminatedSerializer": true,
}
}
// AddFile adds a file to the default repository. It will create a new template based on the filename.
@ -202,36 +192,6 @@ func AddFile(name, data string) error {
return templates.addFile(name, data, false)
}
func asJSON(data interface{}) (string, error) {
b, err := json.Marshal(data)
if err != nil {
return "", err
}
return string(b), nil
}
func asPrettyJSON(data interface{}) (string, error) {
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return "", err
}
return string(b), nil
}
func goSliceInitializer(data interface{}) (string, error) {
// goSliceInitializer constructs a Go literal initializer from interface{} literals.
// e.g. []interface{}{"a", "b"} is transformed in {"a","b",}
// e.g. map[string]interface{}{ "a": "x", "b": "y"} is transformed in {"a":"x","b":"y",}.
//
// NOTE: this is currently used to construct simple slice intializers for default values.
// This allows for nicer slice initializers for slices of primitive types and avoid systematic use for json.Unmarshal().
b, err := json.Marshal(data)
if err != nil {
return "", err
}
return strings.Replace(strings.Replace(strings.Replace(string(b), "}", ",}", -1), "[", "{", -1), "]", ",}", -1), nil
}
// NewRepository creates a new template repository with the provided functions defined
func NewRepository(funcs template.FuncMap) *Repository {
repo := Repository{
@ -273,8 +233,6 @@ func (t *Repository) LoadDir(templatePath string) error {
if assetName, e := filepath.Rel(templatePath, path); e == nil {
if data, e := ioutil.ReadFile(path); e == nil {
if ee := t.AddFile(assetName, string(data)); ee != nil {
// Fatality is decided by caller
// log.Fatal(ee)
return fmt.Errorf("could not add template: %v", ee)
}
}
@ -515,3 +473,110 @@ func (t *Repository) DumpTemplates() {
}
log.Println(buf.String())
}
// FuncMap functions
func asJSON(data interface{}) (string, error) {
b, err := json.Marshal(data)
if err != nil {
return "", err
}
return string(b), nil
}
func asPrettyJSON(data interface{}) (string, error) {
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return "", err
}
return string(b), nil
}
func pluralizeFirstWord(arg string) string {
sentence := strings.Split(arg, " ")
if len(sentence) == 1 {
return inflect.Pluralize(arg)
}
return inflect.Pluralize(sentence[0]) + " " + strings.Join(sentence[1:], " ")
}
func dropPackage(str string) string {
parts := strings.Split(str, ".")
return parts[len(parts)-1]
}
func padSurround(entry, padWith string, i, ln int) string {
var res []string
if i > 0 {
for j := 0; j < i; j++ {
res = append(res, padWith)
}
}
res = append(res, entry)
tot := ln - i - 1
for j := 0; j < tot; j++ {
res = append(res, padWith)
}
return strings.Join(res, ",")
}
func padComment(str string, pads ...string) string {
// pads specifes padding to indent multi line comments.Defaults to one space
pad := " "
lines := strings.Split(str, "\n")
if len(pads) > 0 {
pad = strings.Join(pads, "")
}
return (strings.Join(lines, "\n//"+pad))
}
func blockComment(str string) string {
return strings.Replace(str, "*/", "[*]/", -1)
}
func pascalize(arg string) string {
runes := []rune(arg)
switch len(runes) {
case 0:
return "Empty"
case 1: // handle special case when we have a single rune that is not handled by swag.ToGoName
switch runes[0] {
case '+', '-', '#', '_': // those cases are handled differently than swag utility
return prefixForName(arg)
}
}
return swag.ToGoName(swag.ToGoName(arg)) // want to remove spaces
}
func prefixForName(arg string) string {
first := []rune(arg)[0]
if len(arg) == 0 || unicode.IsLetter(first) {
return ""
}
switch first {
case '+':
return "Plus"
case '-':
return "Minus"
case '#':
return "HashTag"
// other cases ($,@ etc..) handled by swag.ToGoName
}
return "Nr"
}
func dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("expected even number of arguments, got %d", len(values))
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, fmt.Errorf("expected string key, got %+v", values[i])
}
dict[key] = values[i+1]
}
return dict, nil
}

View file

@ -25,6 +25,7 @@ import (
"github.com/go-openapi/spec"
"github.com/go-openapi/swag"
"github.com/kr/pretty"
"github.com/mitchellh/mapstructure"
)
const (
@ -37,27 +38,31 @@ const (
str = "string"
object = "object"
binary = "binary"
sHTTP = "http"
body = "body"
b64 = "byte"
)
// Extensions supported by go-swagger
const (
xClass = "x-class" // class name used by discriminator
xGoCustomTag = "x-go-custom-tag" // additional tag for serializers on struct fields
xGoName = "x-go-name" // name of the generated go variable
xGoType = "x-go-type" // reuse existing type (do not generate)
xIsNullable = "x-isnullable"
xNullable = "x-nullable" // turns the schema into a pointer
xOmitEmpty = "x-omitempty"
xSchemes = "x-schemes" // additional schemes supported for operations (server generation)
xOrder = "x-order" // sort order for properties (or any schema)
xClass = "x-class" // class name used by discriminator
xGoCustomTag = "x-go-custom-tag" // additional tag for serializers on struct fields
xGoName = "x-go-name" // name of the generated go variable
xGoType = "x-go-type" // reuse existing type (do not generate)
xIsNullable = "x-isnullable"
xNullable = "x-nullable" // turns the schema into a pointer
xOmitEmpty = "x-omitempty"
xSchemes = "x-schemes" // additional schemes supported for operations (server generation)
xOrder = "x-order" // sort order for properties (or any schema)
xGoJSONString = "x-go-json-string"
xGoEnumCI = "x-go-enum-ci" // make string enumeration case-insensitive
xGoOperationTag = "x-go-operation-tag" // additional tag to override generation in operation groups
)
// swaggerTypeMapping contains a mapping from go type to swagger type or format
// swaggerTypeName contains a mapping from go type to swagger type or format
var swaggerTypeName map[string]string
func init() {
func initTypes() {
swaggerTypeName = make(map[string]string)
for k, v := range typeMapping {
swaggerTypeName[v] = k
@ -86,6 +91,8 @@ func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType)
// special case of swagger format "binary", rendered as io.ReadCloser interface
// TODO(fredbi): should set IsCustomFormatter=false when binary
result.IsStream = fmt == binary
// special case of swagger format "byte", rendered as a strfmt.Base64 type: no validation
result.IsBase64 = fmt == b64
return
}
}
@ -134,16 +141,15 @@ func newTypeResolver(pkg string, doc *loads.Document) *typeResolver {
func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (string, string, string) {
debugLog("known def type: %q", def)
ext := schema.Extensions
if nm, ok := ext.GetString(xGoName); ok {
if clear == nil {
debugLog("known def type %s no clear: %q", xGoName, nm)
return nm, "", ""
}
debugLog("known def type %s clear: %q -> %q", xGoName, nm, clear(nm))
return clear(nm), "", ""
nm, hasGoName := ext.GetString(xGoName)
if hasGoName {
debugLog("known def type %s named from %s as %q", def, xGoName, nm)
def = nm
}
v, ok := ext[xGoType]
if !ok {
extType, isExternalType := hasExternalType(ext)
if !isExternalType || extType.Embedded {
if clear == nil {
debugLog("known def type no clear: %q", def)
return def, "", ""
@ -151,25 +157,51 @@ func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (
debugLog("known def type clear: %q -> %q", def, clear(def))
return clear(def), "", ""
}
xt := v.(map[string]interface{})
t := xt["type"].(string)
impIface, ok := xt["import"]
// external type definition trumps regular type resolution
log.Printf("type %s imported as external type %s.%s", def, extType.Import.Package, extType.Type)
return extType.Import.Alias + "." + extType.Type, extType.Import.Package, extType.Import.Alias
}
// x-go-type:
// type: mytype
// import:
// package:
// alias:
// hints:
// kind: map|object|array|interface|primitive|stream|tuple
// nullable: true|false
// embedded: true
type externalTypeDefinition struct {
Type string
Import struct {
Package string
Alias string
}
Hints struct {
Kind string
Nullable bool
}
Embedded bool
}
func hasExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) {
v, ok := ext[xGoType]
if !ok {
return t, "", ""
return nil, false
}
imp := impIface.(map[string]interface{})
pkg := imp["package"].(string)
al, ok := imp["alias"]
var alias string
if ok {
alias = al.(string)
} else {
alias = path.Base(pkg)
var extType externalTypeDefinition
err := mapstructure.Decode(v, &extType)
if err != nil {
log.Printf("warning: x-go-type extension could not be decoded (%v). Skipped", v)
return nil, false
}
debugLog("known def type %s no clear: %q: pkg=%s, alias=%s", xGoType, alias+"."+t, pkg, alias)
return alias + "." + t, pkg, alias
if extType.Import.Package != "" && extType.Import.Alias == "" {
// NOTE(fred): possible name conflict here (TODO(fred): deconflict this default alias)
extType.Import.Alias = path.Base(extType.Import.Package)
}
debugLogAsJSON("known def external %s type", xGoType, extType)
return &extType, true
}
type typeResolver struct {
@ -218,40 +250,39 @@ func (t *typeResolver) IsNullable(schema *spec.Schema) bool {
}
func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (returns bool, result resolvedType, err error) {
if schema.Ref.String() != "" {
debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String())
returns = true
var ref *spec.Schema
var er error
ref, er = spec.ResolveRef(t.Doc.Spec(), &schema.Ref)
if er != nil {
debugLog("error resolving ref %s: %v", schema.Ref.String(), er)
err = er
return
}
res, er := t.ResolveSchema(ref, false, isRequired)
if er != nil {
err = er
return
}
result = res
tn := filepath.Base(schema.Ref.GetURL().Fragment)
tpe, pkg, alias := knownDefGoType(tn, *ref, t.goTypeName)
debugLog("type name %s, package %s, alias %s", tpe, pkg, alias)
if tpe != "" {
result.GoType = tpe
result.Pkg = pkg
result.PkgAlias = alias
}
result.HasDiscriminator = res.HasDiscriminator
result.IsBaseType = result.HasDiscriminator
result.IsNullable = t.IsNullable(ref)
//result.IsAliased = true
if schema.Ref.String() == "" {
return
}
debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String())
returns = true
var ref *spec.Schema
var er error
ref, er = spec.ResolveRef(t.Doc.Spec(), &schema.Ref)
if er != nil {
debugLog("error resolving ref %s: %v", schema.Ref.String(), er)
err = er
return
}
res, er := t.ResolveSchema(ref, false, isRequired)
if er != nil {
err = er
return
}
result = res
tn := filepath.Base(schema.Ref.GetURL().Fragment)
tpe, pkg, alias := knownDefGoType(tn, *ref, t.goTypeName)
debugLog("type name %s, package %s, alias %s", tpe, pkg, alias)
if tpe != "" {
result.GoType = tpe
result.Pkg = pkg
result.PkgAlias = alias
}
result.HasDiscriminator = res.HasDiscriminator
result.IsBaseType = result.HasDiscriminator
result.IsNullable = t.IsNullable(ref)
result.IsEnumCI = false
return
}
@ -293,6 +324,7 @@ func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRe
// TODO: should set IsCustomFormatter=false in this case.
result.IsPrimitive = schFmt != binary
result.IsStream = schFmt == binary
result.IsBase64 = schFmt == b64
// propagate extensions in resolvedType
result.Extensions = schema.Extensions
@ -324,21 +356,6 @@ func (t *typeResolver) isNullable(schema *spec.Schema) bool {
return len(schema.Properties) > 0
}
func setIsEmptyOmitted(result *resolvedType, schema *spec.Schema, tpe string) {
defaultValue := true
if tpe == array {
defaultValue = false
}
v, found := schema.Extensions[xOmitEmpty]
if !found {
result.IsEmptyOmitted = defaultValue
return
}
omitted, cast := v.(bool)
result.IsEmptyOmitted = omitted && cast
}
func (t *typeResolver) firstType(schema *spec.Schema) string {
if len(schema.Type) == 0 || schema.Type[0] == "" {
return object
@ -396,6 +413,7 @@ func (t *typeResolver) resolveArray(schema *spec.Schema, isAnonymous, isRequired
result.ElemType = &rt
result.SwaggerType = array
result.SwaggerFormat = ""
result.IsEnumCI = hasEnumCI(schema.Extensions)
t.inferAliasing(&result, schema, isAnonymous, isRequired)
result.Extensions = schema.Extensions
@ -627,29 +645,97 @@ func boolExtension(ext spec.Extensions, key string) *bool {
return nil
}
func hasEnumCI(ve spec.Extensions) bool {
v, ok := ve[xGoEnumCI]
if !ok {
return false
}
isEnumCI, ok := v.(bool)
// All enumeration types are case-sensitive by default
return ok && isEnumCI
}
func (t *typeResolver) shortCircuitResolveExternal(tpe, pkg, alias string, extType *externalTypeDefinition, schema *spec.Schema) resolvedType {
// short circuit type resolution for external types
var result resolvedType
result.Extensions = schema.Extensions
result.GoType = tpe
result.Pkg = pkg
result.PkgAlias = alias
result.setKind(extType.Hints.Kind)
result.IsNullable = t.IsNullable(schema)
// other extensions
if result.IsArray {
result.IsEmptyOmitted = false
tpe = "array"
}
result.setExtensions(schema, tpe)
return result
}
func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) {
debugLog("resolving schema (anon: %t, req: %t) %s", isAnonymous, isRequired, t.ModelName)
defer func() {
debugLog("returning after resolve schema: %s", pretty.Sprint(result))
}()
if schema == nil {
result.IsInterface = true
result.GoType = iface
return
}
tpe := t.firstType(schema)
defer setIsEmptyOmitted(&result, schema, tpe)
extType, isExternalType := hasExternalType(schema.Extensions)
if isExternalType {
tpe, pkg, alias := knownDefGoType(t.ModelName, *schema, t.goTypeName)
debugLog("found type declared as external, imported from %s as %s. Has type hints? %t, rendered has embedded? %t",
pkg, tpe, extType.Hints.Kind != "", extType.Embedded)
if extType.Hints.Kind != "" && !extType.Embedded {
// use hint to qualify type
debugLog("short circuits external type resolution with hint for %s", tpe)
result = t.shortCircuitResolveExternal(tpe, pkg, alias, extType, schema)
return
}
// use spec to qualify type
debugLog("marking type %s as external embedded: %t", tpe, extType.Embedded)
// mark this type as an embedded external definition if requested
defer func() {
result.IsEmbedded = extType.Embedded
if result.IsEmbedded {
result.ElemType = &resolvedType{
GoType: extType.Import.Alias + "." + extType.Type,
Pkg: extType.Import.Package,
PkgAlias: extType.Import.Alias,
IsNullable: extType.Hints.Nullable,
}
result.setKind(extType.Hints.Kind)
}
}()
}
tpe := t.firstType(schema)
var returns bool
returns, result, err = t.resolveSchemaRef(schema, isRequired)
if returns {
if !isAnonymous {
result.IsMap = false
result.IsComplexObject = true
debugLog("not anonymous ref")
}
debugLog("returning after ref")
debugLog("anonymous after ref")
return
}
defer func() {
result.setExtensions(schema, tpe)
}()
// special case of swagger type "file", rendered as io.ReadCloser interface
if t.firstType(schema) == file {
result.SwaggerType = file
@ -662,7 +748,6 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
returns, result, err = t.resolveFormat(schema, isAnonymous, isRequired)
if returns {
debugLog("returning after resolve format: %s", pretty.Sprint(result))
return
}
@ -671,7 +756,6 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
switch tpe {
case array:
result, err = t.resolveArray(schema, isAnonymous, false)
return
case file, number, integer, boolean:
result.Extensions = schema.Extensions
@ -690,7 +774,6 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
result.IsNullable = nullableNumber(schema, isRequired)
case file:
}
return
case str:
result.GoType = str
@ -704,23 +787,21 @@ func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequire
case object:
result, err = t.resolveObject(schema, isAnonymous)
if err != nil {
return resolvedType{}, err
result = resolvedType{}
break
}
result.HasDiscriminator = schema.Discriminator != ""
return
case "null":
result.GoType = iface
result.SwaggerType = object
result.IsNullable = false
result.IsInterface = true
return
default:
err = fmt.Errorf("unresolvable: %v (format %q)", schema.Type, schema.Format)
return
}
return result, err
return
}
// resolvedType is a swagger type that has been resolved and analyzed for usage
@ -736,6 +817,9 @@ type resolvedType struct {
IsNullable bool
IsStream bool
IsEmptyOmitted bool
IsJSONString bool
IsEnumCI bool
IsBase64 bool
// A tuple gets rendered as an anonymous struct with P{index} as property name
IsTuple bool
@ -766,6 +850,11 @@ type resolvedType struct {
// IsSuperAlias indicates that the aliased type is really the same type,
// e.g. in golang, this translates to: type A = B
IsSuperAlias bool
// IsEmbedded applies to externally defined types. When embedded, a type
// is generated in models that embeds the external type, with the Validate
// method.
IsEmbedded bool
}
func (rt *resolvedType) Zero() string {
@ -799,3 +888,102 @@ func (rt *resolvedType) Zero() string {
return ""
}
func (rt *resolvedType) setExtensions(schema *spec.Schema, origType string) {
rt.IsEnumCI = hasEnumCI(schema.Extensions)
rt.setIsEmptyOmitted(schema, origType)
rt.setIsJSONString(schema, origType)
}
func (rt *resolvedType) setIsEmptyOmitted(schema *spec.Schema, tpe string) {
if v, found := schema.Extensions[xOmitEmpty]; found {
omitted, cast := v.(bool)
rt.IsEmptyOmitted = omitted && cast
return
}
// array of primitives are by default not empty-omitted, but arrays of aliased type are
rt.IsEmptyOmitted = (tpe != array) || (tpe == array && rt.IsAliased)
}
func (rt *resolvedType) setIsJSONString(schema *spec.Schema, tpe string) {
_, found := schema.Extensions[xGoJSONString]
if !found {
rt.IsJSONString = false
return
}
rt.IsJSONString = true
}
func (rt *resolvedType) setKind(kind string) {
if kind != "" {
debugLog("overriding kind for %s as %s", rt.GoType, kind)
}
switch kind {
case "map":
rt.IsMap = true
rt.IsArray = false
rt.IsComplexObject = false
rt.IsInterface = false
rt.IsStream = false
rt.IsTuple = false
rt.IsPrimitive = false
rt.SwaggerType = object
case "array":
rt.IsMap = false
rt.IsArray = true
rt.IsComplexObject = false
rt.IsInterface = false
rt.IsStream = false
rt.IsTuple = false
rt.IsPrimitive = false
rt.SwaggerType = array
case "object":
rt.IsMap = false
rt.IsArray = false
rt.IsComplexObject = true
rt.IsInterface = false
rt.IsStream = false
rt.IsTuple = false
rt.IsPrimitive = false
rt.SwaggerType = object
case "interface", "null":
rt.IsMap = false
rt.IsArray = false
rt.IsComplexObject = false
rt.IsInterface = true
rt.IsStream = false
rt.IsTuple = false
rt.IsPrimitive = false
rt.SwaggerType = iface
case "stream":
rt.IsMap = false
rt.IsArray = false
rt.IsComplexObject = false
rt.IsInterface = false
rt.IsStream = true
rt.IsTuple = false
rt.IsPrimitive = false
rt.SwaggerType = file
case "tuple":
rt.IsMap = false
rt.IsArray = false
rt.IsComplexObject = false
rt.IsInterface = false
rt.IsStream = false
rt.IsTuple = true
rt.IsPrimitive = false
rt.SwaggerType = array
case "primitive":
rt.IsMap = false
rt.IsArray = false
rt.IsComplexObject = false
rt.IsInterface = false
rt.IsStream = false
rt.IsTuple = false
rt.IsPrimitive = true
case "":
break
default:
log.Printf("warning: unsupported hint value for external type: %q. Skipped", kind)
}
}

83
vendor/github.com/go-swagger/go-swagger/scan/enum.go generated vendored Normal file
View file

@ -0,0 +1,83 @@
// +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

@ -40,6 +40,10 @@ 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
}
@ -83,6 +87,10 @@ 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
}

View file

@ -38,6 +38,10 @@ 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
@ -85,6 +89,7 @@ func (ht responseTypable) Schema() *spec.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)
}

View file

@ -471,6 +471,7 @@ type swaggerTypable interface {
Typed(string, string)
SetRef(spec.Ref)
Items() swaggerTypable
WithEnum(...interface{})
Schema() *spec.Schema
Level() int
}

View file

@ -81,8 +81,13 @@ func (st schemaTypable) AdditionalProperties() swaggerTypable {
st.schema.Typed("object", "")
return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1}
}
func (st schemaTypable) Level() int { return st.level }
func (st schemaTypable) WithEnum(values ...interface{}) {
st.schema.WithEnum(values...)
}
type schemaValidations struct {
current *spec.Schema
}
@ -248,6 +253,18 @@ func (scp *schemaParser) parseDecl(definitions map[string]spec.Schema, decl *sch
return err
}
}
if enumName, ok := enumName(decl.Decl.Doc); ok {
var enumValues = getEnumValues(decl.File, enumName)
if len(enumValues) > 0 {
var typeName = reflect.TypeOf(enumValues[0]).String()
prop.WithEnum(enumValues...)
err := swaggerSchemaForType(typeName, prop)
if err != nil {
return fmt.Errorf("file %s, error is: %v", decl.File.Name, err)
}
}
}
case *ast.SelectorExpr:
prop := &schemaTypable{schPtr, 0}
if strfmtName, ok := strfmtName(decl.Decl.Doc); ok {
@ -1025,8 +1042,15 @@ func (scp *schemaParser) parseIdentProperty(pkg *loader.PackageInfo, expr *ast.I
}
if enumName, ok := enumName(gd.Doc); ok {
log.Println(enumName)
return nil
var enumValues = getEnumValues(file, enumName)
if len(enumValues) > 0 {
prop.WithEnum(enumValues...)
var typeName = reflect.TypeOf(enumValues[0]).String()
err := swaggerSchemaForType(typeName, prop)
if err != nil {
return fmt.Errorf("file %s, error is: %v", file.Name, err)
}
}
}
if defaultName, ok := defaultName(gd.Doc); ok {