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