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