github.com/AngusLu/go-swagger@v0.28.0/generator/shared.go (about)

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package generator
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"log"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"reflect"
    28  	"sort"
    29  	"strings"
    30  	"text/template"
    31  
    32  	"github.com/go-openapi/analysis"
    33  	"github.com/go-openapi/loads"
    34  	"github.com/go-openapi/runtime"
    35  	"github.com/go-openapi/spec"
    36  	"github.com/go-openapi/swag"
    37  )
    38  
    39  //go:generate go-bindata -mode 420 -modtime 1482416923 -pkg=generator -ignore=.*\.sw? -ignore=.*\.md ./templates/...
    40  
    41  const (
    42  	// default generation targets structure
    43  	defaultModelsTarget         = "models"
    44  	defaultServerTarget         = "restapi"
    45  	defaultClientTarget         = "client"
    46  	defaultOperationsTarget     = "operations"
    47  	defaultClientName           = "rest"
    48  	defaultServerName           = "swagger"
    49  	defaultScheme               = "http"
    50  	defaultImplementationTarget = "implementation"
    51  )
    52  
    53  func init() {
    54  	// all initializations for the generator package
    55  	debugOptions()
    56  	initLanguage()
    57  	initTemplateRepo()
    58  	initTypes()
    59  }
    60  
    61  // DefaultSectionOpts for a given opts, this is used when no config file is passed
    62  // and uses the embedded templates when no local override can be found
    63  func DefaultSectionOpts(gen *GenOpts) {
    64  	sec := gen.Sections
    65  	if len(sec.Models) == 0 {
    66  		opts := []TemplateOpts{
    67  			{
    68  				Name:     "definition",
    69  				Source:   "asset:model",
    70  				Target:   "{{ joinFilePath .Target (toPackagePath .ModelPackage) }}",
    71  				FileName: "{{ (snakize (pascalize .Name)) }}.go",
    72  			},
    73  		}
    74  		if gen.IncludeCLi {
    75  			opts = append(opts, TemplateOpts{
    76  				Name:     "clidefinitionhook",
    77  				Source:   "asset:cliModelcli",
    78  				Target:   "{{ joinFilePath .Target (toPackagePath .CliPackage) }}",
    79  				FileName: "{{ (snakize (pascalize .Name)) }}_model.go",
    80  			})
    81  		}
    82  		sec.Models = opts
    83  	}
    84  
    85  	if len(sec.Operations) == 0 {
    86  		if gen.IsClient {
    87  			opts := []TemplateOpts{
    88  				{
    89  					Name:     "parameters",
    90  					Source:   "asset:clientParameter",
    91  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}",
    92  					FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
    93  				},
    94  				{
    95  					Name:     "responses",
    96  					Source:   "asset:clientResponse",
    97  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}",
    98  					FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
    99  				},
   100  			}
   101  			if gen.IncludeCLi {
   102  				opts = append(opts, TemplateOpts{
   103  					Name:     "clioperation",
   104  					Source:   "asset:cliOperation",
   105  					Target:   "{{ joinFilePath .Target (toPackagePath .CliPackage) }}",
   106  					FileName: "{{ (snakize (pascalize .Name)) }}_operation.go",
   107  				})
   108  			}
   109  			sec.Operations = opts
   110  		} else {
   111  			ops := []TemplateOpts{}
   112  			if gen.IncludeParameters {
   113  				ops = append(ops, TemplateOpts{
   114  					Name:     "parameters",
   115  					Source:   "asset:serverParameter",
   116  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package)  }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   117  					FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
   118  				})
   119  			}
   120  			if gen.IncludeURLBuilder {
   121  				ops = append(ops, TemplateOpts{
   122  					Name:     "urlbuilder",
   123  					Source:   "asset:serverUrlbuilder",
   124  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   125  					FileName: "{{ (snakize (pascalize .Name)) }}_urlbuilder.go",
   126  				})
   127  			}
   128  			if gen.IncludeResponses {
   129  				ops = append(ops, TemplateOpts{
   130  					Name:     "responses",
   131  					Source:   "asset:serverResponses",
   132  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   133  					FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
   134  				})
   135  			}
   136  			if gen.IncludeHandler {
   137  				ops = append(ops, TemplateOpts{
   138  					Name:     "handler",
   139  					Source:   "asset:serverOperation",
   140  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   141  					FileName: "{{ (snakize (pascalize .Name)) }}.go",
   142  				})
   143  			}
   144  			sec.Operations = ops
   145  		}
   146  	}
   147  
   148  	if len(sec.OperationGroups) == 0 {
   149  		if gen.IsClient {
   150  			sec.OperationGroups = []TemplateOpts{
   151  				{
   152  					Name:     "client",
   153  					Source:   "asset:clientClient",
   154  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Name)}}",
   155  					FileName: "{{ (snakize (pascalize .Name)) }}_client.go",
   156  				},
   157  			}
   158  		} else {
   159  			sec.OperationGroups = []TemplateOpts{}
   160  		}
   161  	}
   162  
   163  	if len(sec.Application) == 0 {
   164  		if gen.IsClient {
   165  			opts := []TemplateOpts{
   166  				{
   167  					Name:     "facade",
   168  					Source:   "asset:clientFacade",
   169  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) }}",
   170  					FileName: "{{ snakize .Name }}Client.go",
   171  				},
   172  			}
   173  			if gen.IncludeCLi {
   174  				// include a commandline tool app
   175  				opts = append(opts, []TemplateOpts{{
   176  					Name:     "commandline",
   177  					Source:   "asset:cliCli",
   178  					Target:   "{{ joinFilePath .Target (toPackagePath .CliPackage) }}",
   179  					FileName: "cli.go",
   180  				}, {
   181  					Name:     "climain",
   182  					Source:   "asset:cliMain",
   183  					Target:   "{{ joinFilePath .Target \"cmd\" (toPackagePath .CliAppName) }}",
   184  					FileName: "main.go",
   185  				}, {
   186  					Name:     "cliAutoComplete",
   187  					Source:   "asset:cliCompletion",
   188  					Target:   "{{ joinFilePath .Target (toPackagePath .CliPackage) }}",
   189  					FileName: "autocomplete.go",
   190  				}}...)
   191  			}
   192  			sec.Application = opts
   193  		} else {
   194  			opts := []TemplateOpts{
   195  				{
   196  					Name:     "main",
   197  					Source:   "asset:serverMain",
   198  					Target:   "{{ joinFilePath .Target \"cmd\" .MainPackage }}",
   199  					FileName: "main.go",
   200  				},
   201  				{
   202  					Name:     "embedded_spec",
   203  					Source:   "asset:swaggerJsonEmbed",
   204  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   205  					FileName: "embedded_spec.go",
   206  				},
   207  				{
   208  					Name:     "server",
   209  					Source:   "asset:serverServer",
   210  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   211  					FileName: "server.go",
   212  				},
   213  				{
   214  					Name:     "builder",
   215  					Source:   "asset:serverBuilder",
   216  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) }}",
   217  					FileName: "{{ snakize (pascalize .Name) }}_api.go",
   218  				},
   219  				{
   220  					Name:     "doc",
   221  					Source:   "asset:serverDoc",
   222  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   223  					FileName: "doc.go",
   224  				},
   225  			}
   226  			if gen.ImplementationPackage != "" {
   227  				// Use auto configure template
   228  				opts = append(opts, TemplateOpts{
   229  					Name:     "autoconfigure",
   230  					Source:   "asset:serverAutoconfigureapi",
   231  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   232  					FileName: "auto_configure_{{ (snakize (pascalize .Name)) }}.go",
   233  				})
   234  
   235  			} else {
   236  				opts = append(opts, TemplateOpts{
   237  					Name:       "configure",
   238  					Source:     "asset:serverConfigureapi",
   239  					Target:     "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   240  					FileName:   "configure_{{ (snakize (pascalize .Name)) }}.go",
   241  					SkipExists: !gen.RegenerateConfigureAPI,
   242  				})
   243  			}
   244  			sec.Application = opts
   245  		}
   246  	}
   247  	gen.Sections = sec
   248  
   249  }
   250  
   251  // MarkdownOpts for rendering a spec as markdown
   252  func MarkdownOpts() *LanguageOpts {
   253  	opts := &LanguageOpts{}
   254  	opts.Init()
   255  	return opts
   256  }
   257  
   258  // MarkdownSectionOpts for a given opts and output file.
   259  func MarkdownSectionOpts(gen *GenOpts, output string) {
   260  	gen.Sections.Models = nil
   261  	gen.Sections.OperationGroups = nil
   262  	gen.Sections.Operations = nil
   263  	gen.LanguageOpts = MarkdownOpts()
   264  	gen.Sections.Application = []TemplateOpts{
   265  		{
   266  			Name:     "markdowndocs",
   267  			Source:   "asset:markdownDocs",
   268  			Target:   filepath.Dir(output),
   269  			FileName: filepath.Base(output),
   270  		},
   271  	}
   272  }
   273  
   274  // TemplateOpts allows for codegen customization
   275  type TemplateOpts struct {
   276  	Name       string `mapstructure:"name"`
   277  	Source     string `mapstructure:"source"`
   278  	Target     string `mapstructure:"target"`
   279  	FileName   string `mapstructure:"file_name"`
   280  	SkipExists bool   `mapstructure:"skip_exists"`
   281  	SkipFormat bool   `mapstructure:"skip_format"`
   282  }
   283  
   284  // SectionOpts allows for specifying options to customize the templates used for generation
   285  type SectionOpts struct {
   286  	Application     []TemplateOpts `mapstructure:"application"`
   287  	Operations      []TemplateOpts `mapstructure:"operations"`
   288  	OperationGroups []TemplateOpts `mapstructure:"operation_groups"`
   289  	Models          []TemplateOpts `mapstructure:"models"`
   290  }
   291  
   292  // GenOpts the options for the generator
   293  type GenOpts struct {
   294  	IncludeModel               bool
   295  	IncludeValidator           bool
   296  	IncludeHandler             bool
   297  	IncludeParameters          bool
   298  	IncludeResponses           bool
   299  	IncludeURLBuilder          bool
   300  	IncludeMain                bool
   301  	IncludeSupport             bool
   302  	IncludeCLi                 bool
   303  	ExcludeSpec                bool
   304  	DumpData                   bool
   305  	ValidateSpec               bool
   306  	FlattenOpts                *analysis.FlattenOpts
   307  	IsClient                   bool
   308  	defaultsEnsured            bool
   309  	PropertiesSpecOrder        bool
   310  	StrictAdditionalProperties bool
   311  	AllowTemplateOverride      bool
   312  
   313  	Spec                   string
   314  	APIPackage             string
   315  	ModelPackage           string
   316  	ServerPackage          string
   317  	ClientPackage          string
   318  	CliPackage             string
   319  	CliAppName             string // name of cli app. For example "dockerctl"
   320  	ImplementationPackage  string
   321  	Principal              string
   322  	PrincipalCustomIface   bool   // user-provided interface for Principal (non-nullable)
   323  	Target                 string // dir location where generated code is written to
   324  	Sections               SectionOpts
   325  	LanguageOpts           *LanguageOpts
   326  	TypeMapping            map[string]string
   327  	Imports                map[string]string
   328  	DefaultScheme          string
   329  	DefaultProduces        string
   330  	DefaultConsumes        string
   331  	WithXML                bool
   332  	TemplateDir            string
   333  	Template               string
   334  	RegenerateConfigureAPI bool
   335  	Operations             []string
   336  	Models                 []string
   337  	Tags                   []string
   338  	StructTags             []string
   339  	Name                   string
   340  	FlagStrategy           string
   341  	CompatibilityMode      string
   342  	ExistingModels         string
   343  	Copyright              string
   344  	SkipTagPackages        bool
   345  	MainPackage            string
   346  	IgnoreOperations       bool
   347  	AllowEnumCI            bool
   348  	StrictResponders       bool
   349  	AcceptDefinitionsOnly  bool
   350  
   351  	templates *Repository // a shallow clone of the global template repository
   352  }
   353  
   354  // CheckOpts carries out some global consistency checks on options.
   355  func (g *GenOpts) CheckOpts() error {
   356  	if g == nil {
   357  		return errors.New("gen opts are required")
   358  	}
   359  
   360  	if !filepath.IsAbs(g.Target) {
   361  		if _, err := filepath.Abs(g.Target); err != nil {
   362  			return fmt.Errorf("could not locate target %s: %v", g.Target, err)
   363  		}
   364  	}
   365  
   366  	if filepath.IsAbs(g.ServerPackage) {
   367  		return fmt.Errorf("you shouldn't specify an absolute path in --server-package: %s", g.ServerPackage)
   368  	}
   369  
   370  	if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") {
   371  		return nil
   372  	}
   373  
   374  	pth, err := findSwaggerSpec(g.Spec)
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	// ensure spec path is absolute
   380  	g.Spec, err = filepath.Abs(pth)
   381  	if err != nil {
   382  		return fmt.Errorf("could not locate spec: %s", g.Spec)
   383  	}
   384  
   385  	return nil
   386  }
   387  
   388  // TargetPath returns the target generation path relative to the server package.
   389  // This method is used by templates, e.g. with {{ .TargetPath }}
   390  //
   391  // Errors cases are prevented by calling CheckOpts beforehand.
   392  //
   393  // Example:
   394  // Target: ${PWD}/tmp
   395  // ServerPackage: abc/efg
   396  //
   397  // Server is generated in ${PWD}/tmp/abc/efg
   398  // relative TargetPath returned: ../../../tmp
   399  //
   400  func (g *GenOpts) TargetPath() string {
   401  	var tgt string
   402  	if g.Target == "" {
   403  		tgt = "." // That's for windows
   404  	} else {
   405  		tgt = g.Target
   406  	}
   407  	tgtAbs, _ := filepath.Abs(tgt)
   408  	srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server"))
   409  	srvrAbs := filepath.Join(tgtAbs, srvPkg)
   410  	tgtRel, _ := filepath.Rel(srvrAbs, filepath.Dir(tgtAbs))
   411  	tgtRel = filepath.Join(tgtRel, filepath.Base(tgtAbs))
   412  	return tgtRel
   413  }
   414  
   415  // SpecPath returns the path to the spec relative to the server package.
   416  // If the spec is remote keep this absolute location.
   417  //
   418  // If spec is not relative to server (e.g. lives on a different drive on windows),
   419  // then the resolved path is absolute.
   420  //
   421  // This method is used by templates, e.g. with {{ .SpecPath }}
   422  //
   423  // Errors cases are prevented by calling CheckOpts beforehand.
   424  func (g *GenOpts) SpecPath() string {
   425  	if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") {
   426  		return g.Spec
   427  	}
   428  	// Local specifications
   429  	specAbs, _ := filepath.Abs(g.Spec)
   430  	var tgt string
   431  	if g.Target == "" {
   432  		tgt = "." // That's for windows
   433  	} else {
   434  		tgt = g.Target
   435  	}
   436  	tgtAbs, _ := filepath.Abs(tgt)
   437  	srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server"))
   438  	srvAbs := filepath.Join(tgtAbs, srvPkg)
   439  	specRel, err := filepath.Rel(srvAbs, specAbs)
   440  	if err != nil {
   441  		return specAbs
   442  	}
   443  	return specRel
   444  }
   445  
   446  // PrincipalIsNullable indicates whether the principal type used for authentication
   447  // may be used as a pointer
   448  func (g *GenOpts) PrincipalIsNullable() bool {
   449  	debugLog("Principal: %s, %t, isnullable: %t", g.Principal, g.PrincipalCustomIface, g.Principal != iface && !g.PrincipalCustomIface)
   450  	return g.Principal != iface && !g.PrincipalCustomIface
   451  }
   452  
   453  // EnsureDefaults for these gen opts
   454  func (g *GenOpts) EnsureDefaults() error {
   455  	if g.defaultsEnsured {
   456  		return nil
   457  	}
   458  
   459  	g.templates = templates.ShallowClone()
   460  
   461  	g.templates.LoadDefaults()
   462  
   463  	if g.LanguageOpts == nil {
   464  		g.LanguageOpts = DefaultLanguageFunc()
   465  	}
   466  
   467  	DefaultSectionOpts(g)
   468  
   469  	// set defaults for flattening options
   470  	if g.FlattenOpts == nil {
   471  		g.FlattenOpts = &analysis.FlattenOpts{
   472  			Minimal:      true,
   473  			Verbose:      true,
   474  			RemoveUnused: false,
   475  			Expand:       false,
   476  		}
   477  	}
   478  
   479  	if g.DefaultScheme == "" {
   480  		g.DefaultScheme = defaultScheme
   481  	}
   482  
   483  	if g.DefaultConsumes == "" {
   484  		g.DefaultConsumes = runtime.JSONMime
   485  	}
   486  
   487  	if g.DefaultProduces == "" {
   488  		g.DefaultProduces = runtime.JSONMime
   489  	}
   490  
   491  	// always include validator with models
   492  	g.IncludeValidator = true
   493  
   494  	if g.Principal == "" {
   495  		g.Principal = iface
   496  		g.PrincipalCustomIface = false
   497  	}
   498  
   499  	g.defaultsEnsured = true
   500  	return nil
   501  }
   502  
   503  func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, error) {
   504  	v := reflect.Indirect(reflect.ValueOf(data))
   505  	fld := v.FieldByName("Name")
   506  	var name string
   507  	if fld.IsValid() {
   508  		log.Println("name field", fld.String())
   509  		name = fld.String()
   510  	}
   511  
   512  	fldpack := v.FieldByName("Package")
   513  	pkg := g.APIPackage
   514  	if fldpack.IsValid() {
   515  		log.Println("package field", fldpack.String())
   516  		pkg = fldpack.String()
   517  	}
   518  
   519  	var tags []string
   520  	tagsF := v.FieldByName("Tags")
   521  	if tagsF.IsValid() {
   522  		if tt, ok := tagsF.Interface().([]string); ok {
   523  			tags = tt
   524  		}
   525  	}
   526  
   527  	var useTags bool
   528  	useTagsF := v.FieldByName("UseTags")
   529  	if useTagsF.IsValid() {
   530  		useTags = useTagsF.Interface().(bool)
   531  	}
   532  
   533  	funcMap := FuncMapFunc(g.LanguageOpts)
   534  
   535  	pthTpl, err := template.New(t.Name + "-target").Funcs(funcMap).Parse(t.Target)
   536  	if err != nil {
   537  		return "", "", err
   538  	}
   539  
   540  	fNameTpl, err := template.New(t.Name + "-filename").Funcs(funcMap).Parse(t.FileName)
   541  	if err != nil {
   542  		return "", "", err
   543  	}
   544  
   545  	d := struct {
   546  		Name, CliAppName, Package, APIPackage, ServerPackage, ClientPackage, CliPackage, ModelPackage, MainPackage, Target string
   547  		Tags                                                                                                               []string
   548  		UseTags                                                                                                            bool
   549  		Context                                                                                                            interface{}
   550  	}{
   551  		Name:          name,
   552  		CliAppName:    g.CliAppName,
   553  		Package:       pkg,
   554  		APIPackage:    g.APIPackage,
   555  		ServerPackage: g.ServerPackage,
   556  		ClientPackage: g.ClientPackage,
   557  		CliPackage:    g.CliPackage,
   558  		ModelPackage:  g.ModelPackage,
   559  		MainPackage:   g.MainPackage,
   560  		Target:        g.Target,
   561  		Tags:          tags,
   562  		UseTags:       useTags,
   563  		Context:       data,
   564  	}
   565  
   566  	var pthBuf bytes.Buffer
   567  	if e := pthTpl.Execute(&pthBuf, d); e != nil {
   568  		return "", "", e
   569  	}
   570  
   571  	var fNameBuf bytes.Buffer
   572  	if e := fNameTpl.Execute(&fNameBuf, d); e != nil {
   573  		return "", "", e
   574  	}
   575  	return pthBuf.String(), fileName(fNameBuf.String()), nil
   576  }
   577  
   578  func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
   579  	var templ *template.Template
   580  
   581  	if strings.HasPrefix(strings.ToLower(t.Source), "asset:") {
   582  		tt, err := g.templates.Get(strings.TrimPrefix(t.Source, "asset:"))
   583  		if err != nil {
   584  			return nil, err
   585  		}
   586  		templ = tt
   587  	}
   588  
   589  	if templ == nil {
   590  		// try to load from repository (and enable dependencies)
   591  		name := swag.ToJSONName(strings.TrimSuffix(t.Source, ".gotmpl"))
   592  		tt, err := g.templates.Get(name)
   593  		if err == nil {
   594  			templ = tt
   595  		}
   596  	}
   597  
   598  	if templ == nil {
   599  		// try to load template from disk, in TemplateDir if specified
   600  		// (dependencies resolution is limited to preloaded assets)
   601  		var templateFile string
   602  		if g.TemplateDir != "" {
   603  			templateFile = filepath.Join(g.TemplateDir, t.Source)
   604  		} else {
   605  			templateFile = t.Source
   606  		}
   607  		content, err := ioutil.ReadFile(templateFile)
   608  		if err != nil {
   609  			return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err)
   610  		}
   611  		tt, err := template.New(t.Source).Funcs(FuncMapFunc(g.LanguageOpts)).Parse(string(content))
   612  		if err != nil {
   613  			return nil, fmt.Errorf("template parsing failed on template %s: %v", t.Name, err)
   614  		}
   615  		templ = tt
   616  	}
   617  
   618  	if templ == nil {
   619  		return nil, fmt.Errorf("template %q not found", t.Source)
   620  	}
   621  
   622  	var tBuf bytes.Buffer
   623  	if err := templ.Execute(&tBuf, data); err != nil {
   624  		return nil, fmt.Errorf("template execution failed for template %s: %v", t.Name, err)
   625  	}
   626  	log.Printf("executed template %s", t.Source)
   627  
   628  	return tBuf.Bytes(), nil
   629  }
   630  
   631  // Render template and write generated source code
   632  // generated code is reformatted ("linted"), which gives an
   633  // additional level of checking. If this step fails, the generated
   634  // code is still dumped, for template debugging purposes.
   635  func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
   636  	dir, fname, err := g.location(t, data)
   637  	if err != nil {
   638  		return fmt.Errorf("failed to resolve template location for template %s: %v", t.Name, err)
   639  	}
   640  
   641  	if t.SkipExists && fileExists(dir, fname) {
   642  		debugLog("skipping generation of %s because it already exists and skip_exist directive is set for %s",
   643  			filepath.Join(dir, fname), t.Name)
   644  		return nil
   645  	}
   646  
   647  	log.Printf("creating generated file %q in %q as %s", fname, dir, t.Name)
   648  	content, err := g.render(t, data)
   649  	if err != nil {
   650  		return fmt.Errorf("failed rendering template data for %s: %v", t.Name, err)
   651  	}
   652  
   653  	if dir != "" {
   654  		_, exists := os.Stat(dir)
   655  		if os.IsNotExist(exists) {
   656  			debugLog("creating directory %q for \"%s\"", dir, t.Name)
   657  			// Directory settings consistent with file privileges.
   658  			// Environment's umask may alter this setup
   659  			if e := os.MkdirAll(dir, 0755); e != nil {
   660  				return e
   661  			}
   662  		}
   663  	}
   664  
   665  	// Conditionally format the code, unless the user wants to skip
   666  	formatted := content
   667  	var writeerr error
   668  
   669  	if !t.SkipFormat {
   670  		formatted, err = g.LanguageOpts.FormatContent(filepath.Join(dir, fname), content)
   671  		if err != nil {
   672  			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)
   673  			writeerr = ioutil.WriteFile(filepath.Join(dir, fname), content, 0644) // #nosec
   674  			if writeerr != nil {
   675  				return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr)
   676  			}
   677  			log.Printf("unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!", fname)
   678  			return fmt.Errorf("source formatting on generated source %q failed: %v", t.Name, err)
   679  		}
   680  	}
   681  
   682  	writeerr = ioutil.WriteFile(filepath.Join(dir, fname), formatted, 0644) // #nosec
   683  	if writeerr != nil {
   684  		return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr)
   685  	}
   686  	return err
   687  }
   688  
   689  func fileName(in string) string {
   690  	ext := filepath.Ext(in)
   691  	return swag.ToFileName(strings.TrimSuffix(in, ext)) + ext
   692  }
   693  
   694  func (g *GenOpts) shouldRenderApp(t *TemplateOpts, app *GenApp) bool {
   695  	switch swag.ToFileName(swag.ToGoName(t.Name)) {
   696  	case "main":
   697  		return g.IncludeMain
   698  	case "embedded_spec":
   699  		return !g.ExcludeSpec
   700  	default:
   701  		return true
   702  	}
   703  }
   704  
   705  func (g *GenOpts) shouldRenderOperations() bool {
   706  	return g.IncludeHandler || g.IncludeParameters || g.IncludeResponses
   707  }
   708  
   709  func (g *GenOpts) renderApplication(app *GenApp) error {
   710  	log.Printf("rendering %d templates for application %s", len(g.Sections.Application), app.Name)
   711  	for _, tp := range g.Sections.Application {
   712  		templ := tp
   713  		if !g.shouldRenderApp(&templ, app) {
   714  			continue
   715  		}
   716  		if err := g.write(&templ, app); err != nil {
   717  			return err
   718  		}
   719  	}
   720  	return nil
   721  }
   722  
   723  func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error {
   724  	log.Printf("rendering %d templates for operation group %s", len(g.Sections.OperationGroups), g.Name)
   725  	for _, tp := range g.Sections.OperationGroups {
   726  		templ := tp
   727  		if !g.shouldRenderOperations() {
   728  			continue
   729  		}
   730  
   731  		if err := g.write(&templ, gg); err != nil {
   732  			return err
   733  		}
   734  	}
   735  	return nil
   736  }
   737  
   738  func (g *GenOpts) renderOperation(gg *GenOperation) error {
   739  	log.Printf("rendering %d templates for operation %s", len(g.Sections.Operations), g.Name)
   740  	for _, tp := range g.Sections.Operations {
   741  		templ := tp
   742  		if !g.shouldRenderOperations() {
   743  			continue
   744  		}
   745  
   746  		if err := g.write(&templ, gg); err != nil {
   747  			return err
   748  		}
   749  	}
   750  	return nil
   751  }
   752  
   753  func (g *GenOpts) renderDefinition(gg *GenDefinition) error {
   754  	log.Printf("rendering %d templates for model %s", len(g.Sections.Models), gg.Name)
   755  	for _, tp := range g.Sections.Models {
   756  		templ := tp
   757  		if !g.IncludeModel {
   758  			continue
   759  		}
   760  
   761  		if err := g.write(&templ, gg); err != nil {
   762  			return err
   763  		}
   764  	}
   765  	return nil
   766  }
   767  
   768  func (g *GenOpts) setTemplates() error {
   769  	if g.Template != "" {
   770  		// set contrib templates
   771  		if err := g.templates.LoadContrib(g.Template); err != nil {
   772  			return err
   773  		}
   774  	}
   775  
   776  	g.templates.SetAllowOverride(g.AllowTemplateOverride)
   777  
   778  	if g.TemplateDir != "" {
   779  		// set custom templates
   780  		if err := g.templates.LoadDir(g.TemplateDir); err != nil {
   781  			return err
   782  		}
   783  	}
   784  	return nil
   785  }
   786  
   787  // defaultImports produces a default map for imports with models
   788  func (g *GenOpts) defaultImports() map[string]string {
   789  	baseImport := g.LanguageOpts.baseImport(g.Target)
   790  	defaultImports := make(map[string]string, 50)
   791  
   792  	var modelsAlias, importPath string
   793  	if g.ExistingModels == "" {
   794  		// generated models
   795  		importPath = path.Join(
   796  			baseImport,
   797  			g.LanguageOpts.ManglePackagePath(g.ModelPackage, defaultModelsTarget))
   798  		modelsAlias = g.LanguageOpts.ManglePackageName(g.ModelPackage, defaultModelsTarget)
   799  	} else {
   800  		// external models
   801  		importPath = g.LanguageOpts.ManglePackagePath(g.ExistingModels, "")
   802  		modelsAlias = path.Base(defaultModelsTarget)
   803  	}
   804  	defaultImports[modelsAlias] = importPath
   805  
   806  	// resolve model representing an authenticated principal
   807  	alias, _, target := g.resolvePrincipal()
   808  	if alias == "" || target == g.ModelPackage || path.Base(target) == modelsAlias {
   809  		// if principal is specified with the models generation package, do not import any extra package
   810  		return defaultImports
   811  	}
   812  
   813  	if pth, _ := path.Split(target); pth != "" {
   814  		// if principal is specified with a path, assume this is a fully qualified package and generate this import
   815  		defaultImports[alias] = target
   816  	} else {
   817  		// if principal is specified with a relative path (no "/", e.g. internal.Principal), assume it is located in generated target
   818  		defaultImports[alias] = path.Join(baseImport, target)
   819  	}
   820  	return defaultImports
   821  }
   822  
   823  // initImports produces a default map for import with the specified root for operations
   824  func (g *GenOpts) initImports(operationsPackage string) map[string]string {
   825  	baseImport := g.LanguageOpts.baseImport(g.Target)
   826  
   827  	imports := make(map[string]string, 50)
   828  	imports[g.LanguageOpts.ManglePackageName(operationsPackage, defaultOperationsTarget)] = path.Join(
   829  		baseImport,
   830  		g.LanguageOpts.ManglePackagePath(operationsPackage, defaultOperationsTarget))
   831  	return imports
   832  }
   833  
   834  // PrincipalAlias returns an aliased type to the principal
   835  func (g *GenOpts) PrincipalAlias() string {
   836  	_, principal, _ := g.resolvePrincipal()
   837  	return principal
   838  }
   839  
   840  func (g *GenOpts) resolvePrincipal() (string, string, string) {
   841  	dotLocation := strings.LastIndex(g.Principal, ".")
   842  	if dotLocation < 0 {
   843  		return "", g.Principal, ""
   844  	}
   845  
   846  	// handle possible conflicts with injected principal package
   847  	// NOTE(fred): we do not check here for conflicts with packages created from operation tags, only standard imports
   848  	alias := deconflictPrincipal(importAlias(g.Principal[:dotLocation]))
   849  	return alias, alias + g.Principal[dotLocation:], g.Principal[:dotLocation]
   850  }
   851  
   852  func fileExists(target, name string) bool {
   853  	_, err := os.Stat(filepath.Join(target, name))
   854  	return !os.IsNotExist(err)
   855  }
   856  
   857  func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec.Schema, error) {
   858  	modelNames = pruneEmpty(modelNames)
   859  	models, mnc := make(map[string]spec.Schema), len(modelNames)
   860  	defs := specDoc.Spec().Definitions
   861  
   862  	if mnc > 0 {
   863  		var unknownModels []string
   864  		for _, m := range modelNames {
   865  			_, ok := defs[m]
   866  			if !ok {
   867  				unknownModels = append(unknownModels, m)
   868  			}
   869  		}
   870  		if len(unknownModels) != 0 {
   871  			return nil, fmt.Errorf("unknown models: %s", strings.Join(unknownModels, ", "))
   872  		}
   873  	}
   874  	for k, v := range defs {
   875  		if mnc == 0 {
   876  			models[k] = v
   877  		}
   878  		for _, nm := range modelNames {
   879  			if k == nm {
   880  				models[k] = v
   881  			}
   882  		}
   883  	}
   884  	return models, nil
   885  }
   886  
   887  // titleOrDefault infers a name for the app from the title of the spec
   888  func titleOrDefault(specDoc *loads.Document, name, defaultName string) string {
   889  	if strings.TrimSpace(name) == "" {
   890  		if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" {
   891  			name = specDoc.Spec().Info.Title
   892  		} else {
   893  			name = defaultName
   894  		}
   895  	}
   896  	return swag.ToGoName(name)
   897  }
   898  
   899  func mainNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
   900  	// *_test won't do as main server name
   901  	return strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test")
   902  }
   903  
   904  func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
   905  	// *_test won't do as app names
   906  	name = strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test")
   907  	if name == "" {
   908  		name = swag.ToGoName(defaultName)
   909  	}
   910  	return name
   911  }
   912  
   913  type opRef struct {
   914  	Method string
   915  	Path   string
   916  	Key    string
   917  	ID     string
   918  	Op     *spec.Operation
   919  }
   920  
   921  type opRefs []opRef
   922  
   923  func (o opRefs) Len() int           { return len(o) }
   924  func (o opRefs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
   925  func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
   926  
   927  func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]opRef {
   928  	operationIDs = pruneEmpty(operationIDs)
   929  	var oprefs opRefs
   930  
   931  	for method, pathItem := range specDoc.Operations() {
   932  		for path, operation := range pathItem {
   933  			vv := *operation
   934  			oprefs = append(oprefs, opRef{
   935  				Key:    swag.ToGoName(strings.ToLower(method) + " " + strings.Title(path)),
   936  				Method: method,
   937  				Path:   path,
   938  				ID:     vv.ID,
   939  				Op:     &vv,
   940  			})
   941  		}
   942  	}
   943  
   944  	sort.Sort(oprefs)
   945  
   946  	operations := make(map[string]opRef)
   947  	for _, opr := range oprefs {
   948  		nm := opr.ID
   949  		if nm == "" {
   950  			nm = opr.Key
   951  		}
   952  
   953  		oo, found := operations[nm]
   954  		if found && oo.Method != opr.Method && oo.Path != opr.Path {
   955  			nm = opr.Key
   956  		}
   957  		if len(operationIDs) == 0 || swag.ContainsStrings(operationIDs, opr.ID) || swag.ContainsStrings(operationIDs, nm) {
   958  			opr.ID = nm
   959  			opr.Op.ID = nm
   960  			operations[nm] = opr
   961  		}
   962  	}
   963  
   964  	return operations
   965  }
   966  
   967  func pruneEmpty(in []string) (out []string) {
   968  	for _, v := range in {
   969  		if v != "" {
   970  			out = append(out, v)
   971  		}
   972  	}
   973  	return
   974  }
   975  
   976  func trimBOM(in string) string {
   977  	return strings.Trim(in, "\xef\xbb\xbf")
   978  }
   979  
   980  // gatherSecuritySchemes produces a sorted representation from a map of spec security schemes
   981  func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string, nullable bool) (security GenSecuritySchemes) {
   982  	for scheme, req := range securitySchemes {
   983  		isOAuth2 := strings.ToLower(req.Type) == "oauth2"
   984  		scopes := make([]string, 0, len(req.Scopes))
   985  		genScopes := make([]GenSecurityScope, 0, len(req.Scopes))
   986  		if isOAuth2 {
   987  			for k, v := range req.Scopes {
   988  				scopes = append(scopes, k)
   989  				genScopes = append(genScopes, GenSecurityScope{Name: k, Description: v})
   990  			}
   991  			sort.Strings(scopes)
   992  		}
   993  
   994  		security = append(security, GenSecurityScheme{
   995  			AppName:      appName,
   996  			ID:           scheme,
   997  			ReceiverName: receiver,
   998  			Name:         req.Name,
   999  			IsBasicAuth:  strings.ToLower(req.Type) == "basic",
  1000  			IsAPIKeyAuth: strings.ToLower(req.Type) == "apikey",
  1001  			IsOAuth2:     isOAuth2,
  1002  			Scopes:       scopes,
  1003  			ScopesDesc:   genScopes,
  1004  			Principal:    principal,
  1005  			Source:       req.In,
  1006  			// from original spec
  1007  			Description:      req.Description,
  1008  			Type:             strings.ToLower(req.Type),
  1009  			In:               req.In,
  1010  			Flow:             req.Flow,
  1011  			AuthorizationURL: req.AuthorizationURL,
  1012  			TokenURL:         req.TokenURL,
  1013  			Extensions:       req.Extensions,
  1014  
  1015  			PrincipalIsNullable: nullable,
  1016  		})
  1017  	}
  1018  	sort.Sort(security)
  1019  	return
  1020  }
  1021  
  1022  // securityRequirements just clones the original SecurityRequirements from either the spec
  1023  // or an operation, without any modification. This is used to generate documentation.
  1024  func securityRequirements(orig []map[string][]string) (result []analysis.SecurityRequirement) {
  1025  	for _, r := range orig {
  1026  		for k, v := range r {
  1027  			result = append(result, analysis.SecurityRequirement{Name: k, Scopes: v})
  1028  		}
  1029  	}
  1030  	// TODO(fred): sort this for stable generation
  1031  	return
  1032  }
  1033  
  1034  // gatherExtraSchemas produces a sorted list of extra schemas.
  1035  //
  1036  // ExtraSchemas are inlined types rendered in the same model file.
  1037  func gatherExtraSchemas(extraMap map[string]GenSchema) (extras GenSchemaList) {
  1038  	var extraKeys []string
  1039  	for k := range extraMap {
  1040  		extraKeys = append(extraKeys, k)
  1041  	}
  1042  	sort.Strings(extraKeys)
  1043  	for _, k := range extraKeys {
  1044  		// figure out if top level validations are needed
  1045  		p := extraMap[k]
  1046  		p.HasValidations = shallowValidationLookup(p)
  1047  		extras = append(extras, p)
  1048  	}
  1049  	return
  1050  }
  1051  
  1052  func getExtraSchemes(ext spec.Extensions) []string {
  1053  	if ess, ok := ext.GetStringSlice(xSchemes); ok {
  1054  		return ess
  1055  	}
  1056  	return nil
  1057  }
  1058  
  1059  func gatherURISchemes(swsp *spec.Swagger, operation spec.Operation) ([]string, []string) {
  1060  	var extraSchemes []string
  1061  	extraSchemes = append(extraSchemes, getExtraSchemes(operation.Extensions)...)
  1062  	extraSchemes = concatUnique(getExtraSchemes(swsp.Extensions), extraSchemes)
  1063  	sort.Strings(extraSchemes)
  1064  
  1065  	schemes := concatUnique(swsp.Schemes, operation.Schemes)
  1066  	sort.Strings(schemes)
  1067  
  1068  	return schemes, extraSchemes
  1069  }
  1070  
  1071  func dumpData(data interface{}) error {
  1072  	bb, err := json.MarshalIndent(data, "", "  ")
  1073  	if err != nil {
  1074  		return err
  1075  	}
  1076  	fmt.Fprintln(os.Stdout, string(bb))
  1077  	return nil
  1078  }
  1079  
  1080  func importAlias(pkg string) string {
  1081  	_, k := path.Split(pkg)
  1082  	return k
  1083  }
  1084  
  1085  // concatUnique concatenate collections of strings with deduplication
  1086  func concatUnique(collections ...[]string) []string {
  1087  	resultSet := make(map[string]struct{})
  1088  	for _, c := range collections {
  1089  		for _, i := range c {
  1090  			if _, ok := resultSet[i]; !ok {
  1091  				resultSet[i] = struct{}{}
  1092  			}
  1093  		}
  1094  	}
  1095  	result := make([]string, 0, len(resultSet))
  1096  	for k := range resultSet {
  1097  		result = append(result, k)
  1098  	}
  1099  	return result
  1100  }