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