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