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