1package generator
2
3import (
4	"bytes"
5	"encoding/json"
6	"fmt"
7	"io/ioutil"
8	"math"
9	"os"
10	"path"
11	"path/filepath"
12	"reflect"
13	"strconv"
14	"strings"
15	"sync"
16	"text/template"
17	"text/template/parse"
18	"unicode"
19
20	"log"
21
22	"github.com/go-openapi/inflect"
23	"github.com/go-openapi/runtime"
24	"github.com/go-openapi/swag"
25	"github.com/kr/pretty"
26)
27
28var (
29	assets             map[string][]byte
30	protectedTemplates map[string]bool
31
32	// FuncMapFunc yields a map with all functions for templates
33	FuncMapFunc func(*LanguageOpts) template.FuncMap
34
35	templates *Repository
36
37	docFormat map[string]string
38)
39
40func initTemplateRepo() {
41	FuncMapFunc = DefaultFuncMap
42
43	// this makes the ToGoName func behave with the special
44	// prefixing rule above
45	swag.GoNamePrefixFunc = prefixForName
46
47	assets = defaultAssets()
48	protectedTemplates = defaultProtectedTemplates()
49	templates = NewRepository(FuncMapFunc(DefaultLanguageFunc()))
50
51	docFormat = map[string]string{
52		"binary": "binary (byte stream)",
53		"byte":   "byte (base64 string)",
54	}
55}
56
57// DefaultFuncMap yields a map with default functions for use n the templates.
58// These are available in every template
59func DefaultFuncMap(lang *LanguageOpts) template.FuncMap {
60	return template.FuncMap(map[string]interface{}{
61		"pascalize": pascalize,
62		"camelize":  swag.ToJSONName,
63		"varname":   lang.MangleVarName,
64		"humanize":  swag.ToHumanNameLower,
65		"snakize":   lang.MangleFileName,
66		"toPackagePath": func(name string) string {
67			return filepath.FromSlash(lang.ManglePackagePath(name, ""))
68		},
69		"toPackage": func(name string) string {
70			return lang.ManglePackagePath(name, "")
71		},
72		"toPackageName": func(name string) string {
73			return lang.ManglePackageName(name, "")
74		},
75		"dasherize":          swag.ToCommandName,
76		"pluralizeFirstWord": pluralizeFirstWord,
77		"json":               asJSON,
78		"prettyjson":         asPrettyJSON,
79		"hasInsecure": func(arg []string) bool {
80			return swag.ContainsStringsCI(arg, "http") || swag.ContainsStringsCI(arg, "ws")
81		},
82		"hasSecure": func(arg []string) bool {
83			return swag.ContainsStringsCI(arg, "https") || swag.ContainsStringsCI(arg, "wss")
84		},
85		"dropPackage":      dropPackage,
86		"upper":            strings.ToUpper,
87		"lower":            strings.ToLower,
88		"contains":         swag.ContainsStrings,
89		"padSurround":      padSurround,
90		"joinFilePath":     filepath.Join,
91		"joinPath":         path.Join,
92		"comment":          padComment,
93		"blockcomment":     blockComment,
94		"inspect":          pretty.Sprint,
95		"cleanPath":        path.Clean,
96		"mediaTypeName":    mediaMime,
97		"arrayInitializer": lang.arrayInitializer,
98		"hasPrefix":        strings.HasPrefix,
99		"stringContains":   strings.Contains,
100		"imports":          lang.imports,
101		"dict":             dict,
102		"isInteger":        isInteger,
103		"escapeBackticks": func(arg string) string {
104			return strings.ReplaceAll(arg, "`", "`+\"`\"+`")
105		},
106		"paramDocType": func(param GenParameter) string {
107			return resolvedDocType(param.SwaggerType, param.SwaggerFormat, param.Child)
108		},
109		"headerDocType": func(header GenHeader) string {
110			return resolvedDocType(header.SwaggerType, header.SwaggerFormat, header.Child)
111		},
112		"schemaDocType": func(in interface{}) string {
113			switch schema := in.(type) {
114			case GenSchema:
115				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
116			case *GenSchema:
117				if schema == nil {
118					return ""
119				}
120				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
121			case GenDefinition:
122				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
123			case *GenDefinition:
124				if schema == nil {
125					return ""
126				}
127				return resolvedDocSchemaType(schema.SwaggerType, schema.SwaggerFormat, schema.Items)
128			default:
129				panic("dev error: schemaDocType should be called with GenSchema or GenDefinition")
130			}
131		},
132		"schemaDocMapType": func(schema GenSchema) string {
133			return resolvedDocElemType("object", schema.SwaggerFormat, &schema.resolvedType)
134		},
135		"docCollectionFormat": resolvedDocCollectionFormat,
136		"trimSpace":           strings.TrimSpace,
137		"httpStatus":          httpStatus,
138		"cleanupEnumVariant":  cleanupEnumVariant,
139		"gt0":                 gt0,
140		"hasfield":            hasField,
141	})
142}
143
144func defaultAssets() map[string][]byte {
145	return map[string][]byte{
146		// schema validation templates
147		"validation/primitive.gotmpl":    MustAsset("templates/validation/primitive.gotmpl"),
148		"validation/customformat.gotmpl": MustAsset("templates/validation/customformat.gotmpl"),
149		"validation/structfield.gotmpl":  MustAsset("templates/validation/structfield.gotmpl"),
150		"structfield.gotmpl":             MustAsset("templates/structfield.gotmpl"),
151		"schemavalidator.gotmpl":         MustAsset("templates/schemavalidator.gotmpl"),
152		"schemapolymorphic.gotmpl":       MustAsset("templates/schemapolymorphic.gotmpl"),
153		"schemaembedded.gotmpl":          MustAsset("templates/schemaembedded.gotmpl"),
154		"validation/minimum.gotmpl":      MustAsset("templates/validation/minimum.gotmpl"),
155		"validation/maximum.gotmpl":      MustAsset("templates/validation/maximum.gotmpl"),
156		"validation/multipleOf.gotmpl":   MustAsset("templates/validation/multipleOf.gotmpl"),
157
158		// schema serialization templates
159		"additionalpropertiesserializer.gotmpl": MustAsset("templates/serializers/additionalpropertiesserializer.gotmpl"),
160		"aliasedserializer.gotmpl":              MustAsset("templates/serializers/aliasedserializer.gotmpl"),
161		"allofserializer.gotmpl":                MustAsset("templates/serializers/allofserializer.gotmpl"),
162		"basetypeserializer.gotmpl":             MustAsset("templates/serializers/basetypeserializer.gotmpl"),
163		"marshalbinaryserializer.gotmpl":        MustAsset("templates/serializers/marshalbinaryserializer.gotmpl"),
164		"schemaserializer.gotmpl":               MustAsset("templates/serializers/schemaserializer.gotmpl"),
165		"subtypeserializer.gotmpl":              MustAsset("templates/serializers/subtypeserializer.gotmpl"),
166		"tupleserializer.gotmpl":                MustAsset("templates/serializers/tupleserializer.gotmpl"),
167
168		// schema generation template
169		"docstring.gotmpl":  MustAsset("templates/docstring.gotmpl"),
170		"schematype.gotmpl": MustAsset("templates/schematype.gotmpl"),
171		"schemabody.gotmpl": MustAsset("templates/schemabody.gotmpl"),
172		"schema.gotmpl":     MustAsset("templates/schema.gotmpl"),
173		"model.gotmpl":      MustAsset("templates/model.gotmpl"),
174		"header.gotmpl":     MustAsset("templates/header.gotmpl"),
175
176		// simple schema generation helpers templates
177		"simpleschema/defaultsvar.gotmpl":  MustAsset("templates/simpleschema/defaultsvar.gotmpl"),
178		"simpleschema/defaultsinit.gotmpl": MustAsset("templates/simpleschema/defaultsinit.gotmpl"),
179
180		"swagger_json_embed.gotmpl": MustAsset("templates/swagger_json_embed.gotmpl"),
181
182		// server templates
183		"server/parameter.gotmpl":        MustAsset("templates/server/parameter.gotmpl"),
184		"server/urlbuilder.gotmpl":       MustAsset("templates/server/urlbuilder.gotmpl"),
185		"server/responses.gotmpl":        MustAsset("templates/server/responses.gotmpl"),
186		"server/operation.gotmpl":        MustAsset("templates/server/operation.gotmpl"),
187		"server/builder.gotmpl":          MustAsset("templates/server/builder.gotmpl"),
188		"server/server.gotmpl":           MustAsset("templates/server/server.gotmpl"),
189		"server/configureapi.gotmpl":     MustAsset("templates/server/configureapi.gotmpl"),
190		"server/autoconfigureapi.gotmpl": MustAsset("templates/server/autoconfigureapi.gotmpl"),
191		"server/main.gotmpl":             MustAsset("templates/server/main.gotmpl"),
192		"server/doc.gotmpl":              MustAsset("templates/server/doc.gotmpl"),
193
194		// client templates
195		"client/parameter.gotmpl": MustAsset("templates/client/parameter.gotmpl"),
196		"client/response.gotmpl":  MustAsset("templates/client/response.gotmpl"),
197		"client/client.gotmpl":    MustAsset("templates/client/client.gotmpl"),
198		"client/facade.gotmpl":    MustAsset("templates/client/facade.gotmpl"),
199
200		"markdown/docs.gotmpl": MustAsset("templates/markdown/docs.gotmpl"),
201
202		// cli templates
203		"cli/cli.gotmpl":          MustAsset("templates/cli/cli.gotmpl"),
204		"cli/main.gotmpl":         MustAsset("templates/cli/main.gotmpl"),
205		"cli/modelcli.gotmpl":     MustAsset("templates/cli/modelcli.gotmpl"),
206		"cli/operation.gotmpl":    MustAsset("templates/cli/operation.gotmpl"),
207		"cli/registerflag.gotmpl": MustAsset("templates/cli/registerflag.gotmpl"),
208		"cli/retrieveflag.gotmpl": MustAsset("templates/cli/retrieveflag.gotmpl"),
209		"cli/schema.gotmpl":       MustAsset("templates/cli/schema.gotmpl"),
210	}
211}
212
213func defaultProtectedTemplates() map[string]bool {
214	return map[string]bool{
215		"dereffedSchemaType":          true,
216		"docstring":                   true,
217		"header":                      true,
218		"mapvalidator":                true,
219		"model":                       true,
220		"modelvalidator":              true,
221		"objectvalidator":             true,
222		"primitivefieldvalidator":     true,
223		"privstructfield":             true,
224		"privtuplefield":              true,
225		"propertyValidationDocString": true,
226		"propertyvalidator":           true,
227		"schema":                      true,
228		"schemaBody":                  true,
229		"schemaType":                  true,
230		"schemabody":                  true,
231		"schematype":                  true,
232		"schemavalidator":             true,
233		"serverDoc":                   true,
234		"slicevalidator":              true,
235		"structfield":                 true,
236		"structfieldIface":            true,
237		"subTypeBody":                 true,
238		"swaggerJsonEmbed":            true,
239		"tuplefield":                  true,
240		"tuplefieldIface":             true,
241		"typeSchemaType":              true,
242		"simpleschemaDefaultsvar":     true,
243		"simpleschemaDefaultsinit":    true,
244
245		// validation helpers
246		"validationCustomformat": true,
247		"validationPrimitive":    true,
248		"validationStructfield":  true,
249		"withBaseTypeBody":       true,
250		"withoutBaseTypeBody":    true,
251		"validationMinimum":      true,
252		"validationMaximum":      true,
253		"validationMultipleOf":   true,
254
255		// all serializers
256		"additionalPropertiesSerializer": true,
257		"tupleSerializer":                true,
258		"schemaSerializer":               true,
259		"hasDiscriminatedSerializer":     true,
260		"discriminatedSerializer":        true,
261	}
262}
263
264// AddFile adds a file to the default repository. It will create a new template based on the filename.
265// It trims the .gotmpl from the end and converts the name using swag.ToJSONName. This will strip
266// directory separators and Camelcase the next letter.
267// e.g validation/primitive.gotmpl will become validationPrimitive
268//
269// If the file contains a definition for a template that is protected the whole file will not be added
270func AddFile(name, data string) error {
271	return templates.addFile(name, data, false)
272}
273
274// NewRepository creates a new template repository with the provided functions defined
275func NewRepository(funcs template.FuncMap) *Repository {
276	repo := Repository{
277		files:     make(map[string]string),
278		templates: make(map[string]*template.Template),
279		funcs:     funcs,
280	}
281
282	if repo.funcs == nil {
283		repo.funcs = make(template.FuncMap)
284	}
285
286	return &repo
287}
288
289// Repository is the repository for the generator templates
290type Repository struct {
291	files         map[string]string
292	templates     map[string]*template.Template
293	funcs         template.FuncMap
294	allowOverride bool
295	mux           sync.Mutex
296}
297
298// ShallowClone a repository.
299//
300// Clones the maps of files and templates, so as to be able to use
301// the cloned repo concurrently.
302func (t *Repository) ShallowClone() *Repository {
303	clone := &Repository{
304		files:         make(map[string]string, len(t.files)),
305		templates:     make(map[string]*template.Template, len(t.templates)),
306		funcs:         t.funcs,
307		allowOverride: t.allowOverride,
308	}
309
310	t.mux.Lock()
311	defer t.mux.Unlock()
312
313	for k, file := range t.files {
314		clone.files[k] = file
315	}
316	for k, tpl := range t.templates {
317		clone.templates[k] = tpl
318	}
319	return clone
320}
321
322// LoadDefaults will load the embedded templates
323func (t *Repository) LoadDefaults() {
324
325	for name, asset := range assets {
326		if err := t.addFile(name, string(asset), true); err != nil {
327			log.Fatal(err)
328		}
329	}
330}
331
332// LoadDir will walk the specified path and add each .gotmpl file it finds to the repository
333func (t *Repository) LoadDir(templatePath string) error {
334	err := filepath.Walk(templatePath, func(path string, info os.FileInfo, err error) error {
335
336		if strings.HasSuffix(path, ".gotmpl") {
337			if assetName, e := filepath.Rel(templatePath, path); e == nil {
338				if data, e := ioutil.ReadFile(path); e == nil {
339					if ee := t.AddFile(assetName, string(data)); ee != nil {
340						return fmt.Errorf("could not add template: %v", ee)
341					}
342				}
343				// Non-readable files are skipped
344			}
345		}
346		if err != nil {
347			return err
348		}
349		// Non-template files are skipped
350		return nil
351	})
352	if err != nil {
353		return fmt.Errorf("could not complete template processing in directory \"%s\": %v", templatePath, err)
354	}
355	return nil
356}
357
358// LoadContrib loads template from contrib directory
359func (t *Repository) LoadContrib(name string) error {
360	log.Printf("loading contrib %s", name)
361	const pathPrefix = "templates/contrib/"
362	basePath := pathPrefix + name
363	filesAdded := 0
364	for _, aname := range AssetNames() {
365		if !strings.HasSuffix(aname, ".gotmpl") {
366			continue
367		}
368		if strings.HasPrefix(aname, basePath) {
369			target := aname[len(basePath)+1:]
370			err := t.addFile(target, string(MustAsset(aname)), true)
371			if err != nil {
372				return err
373			}
374			log.Printf("added contributed template %s from %s", target, aname)
375			filesAdded++
376		}
377	}
378	if filesAdded == 0 {
379		return fmt.Errorf("no files added from template: %s", name)
380	}
381	return nil
382}
383
384func (t *Repository) addFile(name, data string, allowOverride bool) error {
385	fileName := name
386	name = swag.ToJSONName(strings.TrimSuffix(name, ".gotmpl"))
387
388	templ, err := template.New(name).Funcs(t.funcs).Parse(data)
389
390	if err != nil {
391		return fmt.Errorf("failed to load template %s: %v", name, err)
392	}
393
394	// check if any protected templates are defined
395	if !allowOverride && !t.allowOverride {
396		for _, template := range templ.Templates() {
397			if protectedTemplates[template.Name()] {
398				return fmt.Errorf("cannot overwrite protected template %s", template.Name())
399			}
400		}
401	}
402
403	// Add each defined template into the cache
404	for _, template := range templ.Templates() {
405
406		t.files[template.Name()] = fileName
407		t.templates[template.Name()] = template.Lookup(template.Name())
408	}
409
410	return nil
411}
412
413// MustGet a template by name, panics when fails
414func (t *Repository) MustGet(name string) *template.Template {
415	tpl, err := t.Get(name)
416	if err != nil {
417		panic(err)
418	}
419	return tpl
420}
421
422// AddFile adds a file to the repository. It will create a new template based on the filename.
423// It trims the .gotmpl from the end and converts the name using swag.ToJSONName. This will strip
424// directory separators and Camelcase the next letter.
425// e.g validation/primitive.gotmpl will become validationPrimitive
426//
427// If the file contains a definition for a template that is protected the whole file will not be added
428func (t *Repository) AddFile(name, data string) error {
429	return t.addFile(name, data, false)
430}
431
432// SetAllowOverride allows setting allowOverride after the Repository was initialized
433func (t *Repository) SetAllowOverride(value bool) {
434	t.allowOverride = value
435}
436
437func findDependencies(n parse.Node) []string {
438
439	var deps []string
440	depMap := make(map[string]bool)
441
442	if n == nil {
443		return deps
444	}
445
446	switch node := n.(type) {
447	case *parse.ListNode:
448		if node != nil && node.Nodes != nil {
449			for _, nn := range node.Nodes {
450				for _, dep := range findDependencies(nn) {
451					depMap[dep] = true
452				}
453			}
454		}
455	case *parse.IfNode:
456		for _, dep := range findDependencies(node.BranchNode.List) {
457			depMap[dep] = true
458		}
459		for _, dep := range findDependencies(node.BranchNode.ElseList) {
460			depMap[dep] = true
461		}
462
463	case *parse.RangeNode:
464		for _, dep := range findDependencies(node.BranchNode.List) {
465			depMap[dep] = true
466		}
467		for _, dep := range findDependencies(node.BranchNode.ElseList) {
468			depMap[dep] = true
469		}
470
471	case *parse.WithNode:
472		for _, dep := range findDependencies(node.BranchNode.List) {
473			depMap[dep] = true
474		}
475		for _, dep := range findDependencies(node.BranchNode.ElseList) {
476			depMap[dep] = true
477		}
478
479	case *parse.TemplateNode:
480		depMap[node.Name] = true
481	}
482
483	for dep := range depMap {
484		deps = append(deps, dep)
485	}
486
487	return deps
488
489}
490
491func (t *Repository) flattenDependencies(templ *template.Template, dependencies map[string]bool) map[string]bool {
492	if dependencies == nil {
493		dependencies = make(map[string]bool)
494	}
495
496	deps := findDependencies(templ.Tree.Root)
497
498	for _, d := range deps {
499		if _, found := dependencies[d]; !found {
500
501			dependencies[d] = true
502
503			if tt := t.templates[d]; tt != nil {
504				dependencies = t.flattenDependencies(tt, dependencies)
505			}
506		}
507
508		dependencies[d] = true
509
510	}
511
512	return dependencies
513
514}
515
516func (t *Repository) addDependencies(templ *template.Template) (*template.Template, error) {
517
518	name := templ.Name()
519
520	deps := t.flattenDependencies(templ, nil)
521
522	for dep := range deps {
523
524		if dep == "" {
525			continue
526		}
527
528		tt := templ.Lookup(dep)
529
530		// Check if we have it
531		if tt == nil {
532			tt = t.templates[dep]
533
534			// Still don't have it, return an error
535			if tt == nil {
536				return templ, fmt.Errorf("could not find template %s", dep)
537			}
538			var err error
539
540			// Add it to the parse tree
541			templ, err = templ.AddParseTree(dep, tt.Tree)
542
543			if err != nil {
544				return templ, fmt.Errorf("dependency error: %v", err)
545			}
546
547		}
548	}
549	return templ.Lookup(name), nil
550}
551
552// Get will return the named template from the repository, ensuring that all dependent templates are loaded.
553// It will return an error if a dependent template is not defined in the repository.
554func (t *Repository) Get(name string) (*template.Template, error) {
555	templ, found := t.templates[name]
556
557	if !found {
558		return templ, fmt.Errorf("template doesn't exist %s", name)
559	}
560
561	return t.addDependencies(templ)
562}
563
564// DumpTemplates prints out a dump of all the defined templates, where they are defined and what their dependencies are.
565func (t *Repository) DumpTemplates() {
566	buf := bytes.NewBuffer(nil)
567	fmt.Fprintln(buf, "\n# Templates")
568	for name, templ := range t.templates {
569		fmt.Fprintf(buf, "## %s\n", name)
570		fmt.Fprintf(buf, "Defined in `%s`\n", t.files[name])
571
572		if deps := findDependencies(templ.Tree.Root); len(deps) > 0 {
573
574			fmt.Fprintf(buf, "####requires \n - %v\n\n\n", strings.Join(deps, "\n - "))
575		}
576		fmt.Fprintln(buf, "\n---")
577	}
578	log.Println(buf.String())
579}
580
581// FuncMap functions
582
583func asJSON(data interface{}) (string, error) {
584	b, err := json.Marshal(data)
585	if err != nil {
586		return "", err
587	}
588	return string(b), nil
589}
590
591func asPrettyJSON(data interface{}) (string, error) {
592	b, err := json.MarshalIndent(data, "", "  ")
593	if err != nil {
594		return "", err
595	}
596	return string(b), nil
597}
598
599func pluralizeFirstWord(arg string) string {
600	sentence := strings.Split(arg, " ")
601	if len(sentence) == 1 {
602		return inflect.Pluralize(arg)
603	}
604
605	return inflect.Pluralize(sentence[0]) + " " + strings.Join(sentence[1:], " ")
606}
607
608func dropPackage(str string) string {
609	parts := strings.Split(str, ".")
610	return parts[len(parts)-1]
611}
612
613func padSurround(entry, padWith string, i, ln int) string {
614	var res []string
615	if i > 0 {
616		for j := 0; j < i; j++ {
617			res = append(res, padWith)
618		}
619	}
620	res = append(res, entry)
621	tot := ln - i - 1
622	for j := 0; j < tot; j++ {
623		res = append(res, padWith)
624	}
625	return strings.Join(res, ",")
626}
627
628func padComment(str string, pads ...string) string {
629	// pads specifes padding to indent multi line comments.Defaults to one space
630	pad := " "
631	lines := strings.Split(str, "\n")
632	if len(pads) > 0 {
633		pad = strings.Join(pads, "")
634	}
635	return (strings.Join(lines, "\n//"+pad))
636}
637
638func blockComment(str string) string {
639	return strings.ReplaceAll(str, "*/", "[*]/")
640}
641
642func pascalize(arg string) string {
643	runes := []rune(arg)
644	switch len(runes) {
645	case 0:
646		return "Empty"
647	case 1: // handle special case when we have a single rune that is not handled by swag.ToGoName
648		switch runes[0] {
649		case '+', '-', '#', '_', '*', '/', '=': // those cases are handled differently than swag utility
650			return prefixForName(arg)
651		}
652	}
653	return swag.ToGoName(swag.ToGoName(arg)) // want to remove spaces
654}
655
656func prefixForName(arg string) string {
657	first := []rune(arg)[0]
658	if len(arg) == 0 || unicode.IsLetter(first) {
659		return ""
660	}
661	switch first {
662	case '+':
663		return "Plus"
664	case '-':
665		return "Minus"
666	case '#':
667		return "HashTag"
668	case '*':
669		return "Asterisk"
670	case '/':
671		return "ForwardSlash"
672	case '=':
673		return "EqualSign"
674		// other cases ($,@ etc..) handled by swag.ToGoName
675	}
676	return "Nr"
677}
678
679func replaceSpecialChar(in rune) string {
680	switch in {
681	case '.':
682		return "-Dot-"
683	case '+':
684		return "-Plus-"
685	case '-':
686		return "-Dash-"
687	case '#':
688		return "-Hashtag-"
689	}
690	return string(in)
691}
692
693func cleanupEnumVariant(in string) string {
694	replaced := ""
695	for _, char := range in {
696		replaced += replaceSpecialChar(char)
697	}
698	return replaced
699}
700
701func dict(values ...interface{}) (map[string]interface{}, error) {
702	if len(values)%2 != 0 {
703		return nil, fmt.Errorf("expected even number of arguments, got %d", len(values))
704	}
705	dict := make(map[string]interface{}, len(values)/2)
706	for i := 0; i < len(values); i += 2 {
707		key, ok := values[i].(string)
708		if !ok {
709			return nil, fmt.Errorf("expected string key, got %+v", values[i])
710		}
711		dict[key] = values[i+1]
712	}
713	return dict, nil
714}
715
716func isInteger(arg interface{}) bool {
717	// is integer determines if a value may be represented by an integer
718	switch val := arg.(type) {
719	case int8, int16, int32, int, int64, uint8, uint16, uint32, uint, uint64:
720		return true
721	case *int8, *int16, *int32, *int, *int64, *uint8, *uint16, *uint32, *uint, *uint64:
722		v := reflect.ValueOf(arg)
723		return !v.IsNil()
724	case float64:
725		return math.Round(val) == val
726	case *float64:
727		return val != nil && math.Round(*val) == *val
728	case float32:
729		return math.Round(float64(val)) == float64(val)
730	case *float32:
731		return val != nil && math.Round(float64(*val)) == float64(*val)
732	case string:
733		_, err := strconv.ParseInt(val, 10, 64)
734		return err == nil
735	case *string:
736		if val == nil {
737			return false
738		}
739		_, err := strconv.ParseInt(*val, 10, 64)
740		return err == nil
741	default:
742		return false
743	}
744}
745
746func resolvedDocCollectionFormat(cf string, child *GenItems) string {
747	if child == nil {
748		return cf
749	}
750	ccf := cf
751	if ccf == "" {
752		ccf = "csv"
753	}
754	rcf := resolvedDocCollectionFormat(child.CollectionFormat, child.Child)
755	if rcf == "" {
756		return ccf
757	}
758	return ccf + "|" + rcf
759}
760
761func resolvedDocType(tn, ft string, child *GenItems) string {
762	if tn == "array" {
763		if child == nil {
764			return "[]any"
765		}
766		return "[]" + resolvedDocType(child.SwaggerType, child.SwaggerFormat, child.Child)
767	}
768
769	if ft != "" {
770		if doc, ok := docFormat[ft]; ok {
771			return doc
772		}
773		return fmt.Sprintf("%s (formatted %s)", ft, tn)
774	}
775
776	return tn
777}
778
779func resolvedDocSchemaType(tn, ft string, child *GenSchema) string {
780	if tn == "array" {
781		if child == nil {
782			return "[]any"
783		}
784		return "[]" + resolvedDocSchemaType(child.SwaggerType, child.SwaggerFormat, child.Items)
785	}
786
787	if tn == "object" {
788		if child == nil || child.ElemType == nil {
789			return "map of any"
790		}
791		if child.IsMap {
792			return "map of " + resolvedDocElemType(child.SwaggerType, child.SwaggerFormat, &child.resolvedType)
793		}
794
795		return child.GoType
796	}
797
798	if ft != "" {
799		if doc, ok := docFormat[ft]; ok {
800			return doc
801		}
802		return fmt.Sprintf("%s (formatted %s)", ft, tn)
803	}
804
805	return tn
806}
807
808func resolvedDocElemType(tn, ft string, schema *resolvedType) string {
809	if schema == nil {
810		return ""
811	}
812	if schema.IsMap {
813		return "map of " + resolvedDocElemType(schema.ElemType.SwaggerType, schema.ElemType.SwaggerFormat, schema.ElemType)
814	}
815
816	if schema.IsArray {
817		return "[]" + resolvedDocElemType(schema.ElemType.SwaggerType, schema.ElemType.SwaggerFormat, schema.ElemType)
818	}
819
820	if ft != "" {
821		if doc, ok := docFormat[ft]; ok {
822			return doc
823		}
824		return fmt.Sprintf("%s (formatted %s)", ft, tn)
825	}
826
827	return tn
828}
829
830func httpStatus(code int) string {
831	if name, ok := runtime.Statuses[code]; ok {
832		return name
833	}
834	// non-standard codes deserve some name
835	return fmt.Sprintf("Status %d", code)
836}
837
838func gt0(in *int64) bool {
839	// gt0 returns true if the *int64 points to a value > 0
840	// NOTE: plain {{ gt .MinProperties 0 }} just refuses to work normally
841	// with a pointer
842	return in != nil && *in > 0
843}
844
845// returns struct v has field of name
846func hasField(v interface{}, name string) bool {
847	rv := reflect.ValueOf(v)
848	if rv.Kind() == reflect.Ptr {
849		rv = rv.Elem()
850	}
851	if rv.Kind() != reflect.Struct {
852		return false
853	}
854	return rv.FieldByName(name).IsValid()
855}
856