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