github.com/6543-forks/go-swagger@v0.26.0/generator/types.go (about) 1 // Copyright 2015 go-swagger maintainers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package generator 16 17 import ( 18 "fmt" 19 "log" 20 "path" 21 "path/filepath" 22 "strings" 23 24 "github.com/go-openapi/loads" 25 "github.com/go-openapi/spec" 26 "github.com/go-openapi/swag" 27 "github.com/kr/pretty" 28 "github.com/mitchellh/mapstructure" 29 ) 30 31 const ( 32 iface = "interface{}" 33 array = "array" 34 file = "file" 35 number = "number" 36 integer = "integer" 37 boolean = "boolean" 38 str = "string" 39 object = "object" 40 binary = "binary" 41 body = "body" 42 b64 = "byte" 43 ) 44 45 // Extensions supported by go-swagger 46 const ( 47 xClass = "x-class" // class name used by discriminator 48 xGoCustomTag = "x-go-custom-tag" // additional tag for serializers on struct fields 49 xGoName = "x-go-name" // name of the generated go variable 50 xGoType = "x-go-type" // reuse existing type (do not generate) 51 xIsNullable = "x-isnullable" 52 xNullable = "x-nullable" // turns the schema into a pointer 53 xOmitEmpty = "x-omitempty" 54 xSchemes = "x-schemes" // additional schemes supported for operations (server generation) 55 xOrder = "x-order" // sort order for properties (or any schema) 56 xGoJSONString = "x-go-json-string" 57 xGoEnumCI = "x-go-enum-ci" // make string enumeration case-insensitive 58 59 xGoOperationTag = "x-go-operation-tag" // additional tag to override generation in operation groups 60 ) 61 62 // swaggerTypeName contains a mapping from go type to swagger type or format 63 var swaggerTypeName map[string]string 64 65 func initTypes() { 66 swaggerTypeName = make(map[string]string) 67 for k, v := range typeMapping { 68 swaggerTypeName[v] = k 69 } 70 } 71 72 func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType) { 73 result.SwaggerType = tn 74 result.SwaggerFormat = fmt 75 76 if tn == file { 77 // special case of swagger type "file", rendered as io.ReadCloser interface 78 result.IsPrimitive = true 79 result.GoType = formatMapping[str][binary] 80 result.IsStream = true 81 return 82 } 83 84 if fmt != "" { 85 fmtn := strings.Replace(fmt, "-", "", -1) 86 if fmm, ok := formatMapping[tn]; ok { 87 if tpe, ok := fmm[fmtn]; ok { 88 result.GoType = tpe 89 result.IsPrimitive = true 90 _, result.IsCustomFormatter = customFormatters[tpe] 91 // special case of swagger format "binary", rendered as io.ReadCloser interface 92 // TODO(fredbi): should set IsCustomFormatter=false when binary 93 result.IsStream = fmt == binary 94 // special case of swagger format "byte", rendered as a strfmt.Base64 type: no validation 95 result.IsBase64 = fmt == b64 96 return 97 } 98 } 99 } 100 101 if tpe, ok := typeMapping[tn]; ok { 102 result.GoType = tpe 103 _, result.IsPrimitive = primitives[tpe] 104 result.IsPrimitive = ok 105 return 106 } 107 108 if tn == array { 109 result.IsArray = true 110 result.IsPrimitive = false 111 result.IsCustomFormatter = false 112 result.IsNullable = false 113 if items == nil { 114 result.GoType = "[]" + iface 115 return 116 } 117 res := simpleResolvedType(items.Type, items.Format, items.Items) 118 result.GoType = "[]" + res.GoType 119 return 120 } 121 result.GoType = tn 122 _, result.IsPrimitive = primitives[tn] 123 return 124 } 125 126 func typeForHeader(header spec.Header) resolvedType { 127 return simpleResolvedType(header.Type, header.Format, header.Items) 128 } 129 130 func newTypeResolver(pkg string, doc *loads.Document) *typeResolver { 131 resolver := typeResolver{ModelsPackage: pkg, Doc: doc} 132 resolver.KnownDefs = make(map[string]struct{}, len(doc.Spec().Definitions)) 133 for k, sch := range doc.Spec().Definitions { 134 tpe, _, _ := knownDefGoType(k, sch, nil) 135 resolver.KnownDefs[tpe] = struct{}{} 136 } 137 return &resolver 138 } 139 140 // knownDefGoType returns go type, package and package alias for definition 141 func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (string, string, string) { 142 debugLog("known def type: %q", def) 143 ext := schema.Extensions 144 nm, hasGoName := ext.GetString(xGoName) 145 146 if hasGoName { 147 debugLog("known def type %s named from %s as %q", def, xGoName, nm) 148 def = nm 149 } 150 extType, isExternalType := hasExternalType(ext) 151 152 if !isExternalType || extType.Embedded { 153 if clear == nil { 154 debugLog("known def type no clear: %q", def) 155 return def, "", "" 156 } 157 debugLog("known def type clear: %q -> %q", def, clear(def)) 158 return clear(def), "", "" 159 } 160 161 // external type definition trumps regular type resolution 162 log.Printf("type %s imported as external type %s.%s", def, extType.Import.Package, extType.Type) 163 return extType.Import.Alias + "." + extType.Type, extType.Import.Package, extType.Import.Alias 164 } 165 166 // x-go-type: 167 // type: mytype 168 // import: 169 // package: 170 // alias: 171 // hints: 172 // kind: map|object|array|interface|primitive|stream|tuple 173 // nullable: true|false 174 // embedded: true 175 type externalTypeDefinition struct { 176 Type string 177 Import struct { 178 Package string 179 Alias string 180 } 181 Hints struct { 182 Kind string 183 Nullable bool 184 } 185 Embedded bool 186 } 187 188 func hasExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) { 189 v, ok := ext[xGoType] 190 if !ok { 191 return nil, false 192 } 193 var extType externalTypeDefinition 194 err := mapstructure.Decode(v, &extType) 195 if err != nil { 196 log.Printf("warning: x-go-type extension could not be decoded (%v). Skipped", v) 197 return nil, false 198 } 199 if extType.Import.Package != "" && extType.Import.Alias == "" { 200 // NOTE(fred): possible name conflict here (TODO(fred): deconflict this default alias) 201 extType.Import.Alias = path.Base(extType.Import.Package) 202 } 203 debugLogAsJSON("known def external %s type", xGoType, extType) 204 return &extType, true 205 } 206 207 type typeResolver struct { 208 Doc *loads.Document 209 ModelsPackage string 210 ModelName string 211 KnownDefs map[string]struct{} 212 // unexported fields 213 keepDefinitionsPkg string 214 knownDefsKept map[string]struct{} 215 } 216 217 // NewWithModelName clones a type resolver and specifies a new model name 218 func (t *typeResolver) NewWithModelName(name string) *typeResolver { 219 tt := newTypeResolver(t.ModelsPackage, t.Doc) 220 tt.ModelName = name 221 222 // propagates kept definitions 223 tt.keepDefinitionsPkg = t.keepDefinitionsPkg 224 tt.knownDefsKept = t.knownDefsKept 225 return tt 226 } 227 228 // withKeepDefinitionsPackage instructs the type resolver to keep previously resolved package name for 229 // definitions known at the moment it is first called. 230 func (t *typeResolver) withKeepDefinitionsPackage(definitionsPackage string) *typeResolver { 231 t.keepDefinitionsPkg = definitionsPackage 232 t.knownDefsKept = make(map[string]struct{}, len(t.KnownDefs)) 233 for k := range t.KnownDefs { 234 t.knownDefsKept[k] = struct{}{} 235 } 236 return t 237 } 238 239 func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (returns bool, result resolvedType, err error) { 240 if schema.Ref.String() == "" { 241 return 242 } 243 debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String()) 244 returns = true 245 var ref *spec.Schema 246 var er error 247 248 ref, er = spec.ResolveRef(t.Doc.Spec(), &schema.Ref) 249 if er != nil { 250 debugLog("error resolving ref %s: %v", schema.Ref.String(), er) 251 err = er 252 return 253 } 254 res, er := t.ResolveSchema(ref, false, isRequired) 255 if er != nil { 256 err = er 257 return 258 } 259 result = res 260 261 tn := filepath.Base(schema.Ref.GetURL().Fragment) 262 tpe, pkg, alias := knownDefGoType(tn, *ref, t.goTypeName) 263 debugLog("type name %s, package %s, alias %s", tpe, pkg, alias) 264 if tpe != "" { 265 result.GoType = tpe 266 result.Pkg = pkg 267 result.PkgAlias = alias 268 } 269 result.HasDiscriminator = res.HasDiscriminator 270 result.IsBaseType = result.HasDiscriminator 271 result.IsNullable = t.isNullable(ref) 272 result.IsEnumCI = false 273 return 274 } 275 276 func (t *typeResolver) inferAliasing(result *resolvedType, schema *spec.Schema, isAnonymous bool, isRequired bool) { 277 if !isAnonymous && t.ModelName != "" { 278 result.AliasedType = result.GoType 279 result.IsAliased = true 280 result.GoType = t.goTypeName(t.ModelName) 281 } 282 } 283 284 func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRequired bool) (returns bool, result resolvedType, err error) { 285 286 if schema.Format != "" { 287 // defaults to string 288 result.SwaggerType = str 289 if len(schema.Type) > 0 { 290 result.SwaggerType = schema.Type[0] 291 } 292 293 debugLog("resolving format (anon: %t, req: %t)", isAnonymous, isRequired) 294 schFmt := strings.Replace(schema.Format, "-", "", -1) 295 if fmm, ok := formatMapping[result.SwaggerType]; ok { 296 if tpe, ok := fmm[schFmt]; ok { 297 returns = true 298 result.GoType = tpe 299 _, result.IsCustomFormatter = customFormatters[tpe] 300 } 301 } 302 if tpe, ok := typeMapping[schFmt]; !returns && ok { 303 returns = true 304 result.GoType = tpe 305 _, result.IsCustomFormatter = customFormatters[tpe] 306 } 307 308 result.SwaggerFormat = schema.Format 309 t.inferAliasing(&result, schema, isAnonymous, isRequired) 310 // special case of swagger format "binary", rendered as io.ReadCloser interface and is therefore not a primitive type 311 // TODO: should set IsCustomFormatter=false in this case. 312 result.IsPrimitive = schFmt != binary 313 result.IsStream = schFmt == binary 314 result.IsBase64 = schFmt == b64 315 // propagate extensions in resolvedType 316 result.Extensions = schema.Extensions 317 318 switch result.SwaggerType { 319 case str: 320 result.IsNullable = nullableStrfmt(schema, isRequired) 321 case number, integer: 322 result.IsNullable = nullableNumber(schema, isRequired) 323 default: 324 result.IsNullable = t.isNullable(schema) 325 } 326 } 327 return 328 } 329 330 // isNullable hints the generator as to render the type with a pointer or not. 331 // 332 // A schema is deemed nullable (i.e. rendered by a pointer) when: 333 // - a custom extension says it has to be so 334 // - it is an object with properties 335 // - it is a composed object (allOf) 336 // 337 // The interpretation of Required as a mean to make a type nullable is carried on elsewhere. 338 func (t *typeResolver) isNullable(schema *spec.Schema) bool { 339 check := func(extension string) (bool, bool) { 340 v, found := schema.Extensions[extension] 341 nullable, cast := v.(bool) 342 return nullable, found && cast 343 } 344 345 if nullable, ok := check(xIsNullable); ok { 346 return nullable 347 } 348 if nullable, ok := check(xNullable); ok { 349 return nullable 350 } 351 return len(schema.Properties) > 0 || len(schema.AllOf) > 0 352 } 353 354 func (t *typeResolver) firstType(schema *spec.Schema) string { 355 if len(schema.Type) == 0 || schema.Type[0] == "" { 356 return object 357 } 358 if len(schema.Type) > 1 { 359 // JSON-Schema multiple types, e.g. {"type": [ "object", "array" ]} are not supported. 360 // TODO: should keep the first _supported_ type, e.g. skip null 361 log.Printf("warning: JSON-Schema type definition as array with several types is not supported in %#v. Taking the first type: %s", schema.Type, schema.Type[0]) 362 } 363 return schema.Type[0] 364 } 365 366 func (t *typeResolver) resolveArray(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) { 367 debugLog("resolving array (anon: %t, req: %t)", isAnonymous, isRequired) 368 369 result.IsArray = true 370 result.IsNullable = false 371 372 if schema.AdditionalItems != nil { 373 result.HasAdditionalItems = (schema.AdditionalItems.Allows || schema.AdditionalItems.Schema != nil) 374 } 375 376 if schema.Items == nil { 377 result.GoType = "[]" + iface 378 result.SwaggerType = array 379 result.SwaggerFormat = "" 380 t.inferAliasing(&result, schema, isAnonymous, isRequired) 381 382 return 383 } 384 385 if len(schema.Items.Schemas) > 0 { 386 result.IsArray = false 387 result.IsTuple = true 388 result.SwaggerType = array 389 result.SwaggerFormat = "" 390 t.inferAliasing(&result, schema, isAnonymous, isRequired) 391 392 return 393 } 394 395 rt, er := t.ResolveSchema(schema.Items.Schema, true, false) 396 if er != nil { 397 err = er 398 return 399 } 400 // override the general nullability rule from ResolveSchema(): 401 // only complex items are nullable (when not discriminated, not forced by x-nullable) 402 rt.IsNullable = t.isNullable(schema.Items.Schema) && !rt.HasDiscriminator 403 result.GoType = "[]" + rt.GoType 404 if rt.IsNullable && !strings.HasPrefix(rt.GoType, "*") { 405 result.GoType = "[]*" + rt.GoType 406 } 407 408 result.ElemType = &rt 409 result.SwaggerType = array 410 result.SwaggerFormat = "" 411 result.IsEnumCI = hasEnumCI(schema.Extensions) 412 t.inferAliasing(&result, schema, isAnonymous, isRequired) 413 result.Extensions = schema.Extensions 414 415 return 416 } 417 418 func (t *typeResolver) goTypeName(nm string) string { 419 if len(t.knownDefsKept) > 0 { 420 // if a definitions package has been defined, already resolved definitions are 421 // always resolved against their original package (e.g. "models"), and not the 422 // current package. 423 // This allows complex anonymous extra schemas to reuse known definitions generated in another package. 424 if _, ok := t.knownDefsKept[nm]; ok { 425 return strings.Join([]string{t.keepDefinitionsPkg, swag.ToGoName(nm)}, ".") 426 } 427 } 428 429 if t.ModelsPackage == "" { 430 return swag.ToGoName(nm) 431 } 432 if _, ok := t.KnownDefs[nm]; ok { 433 return strings.Join([]string{t.ModelsPackage, swag.ToGoName(nm)}, ".") 434 } 435 return swag.ToGoName(nm) 436 } 437 438 func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (result resolvedType, err error) { 439 debugLog("resolving object %s (anon: %t, req: %t)", t.ModelName, isAnonymous, false) 440 441 result.IsAnonymous = isAnonymous 442 443 result.IsBaseType = schema.Discriminator != "" 444 if !isAnonymous { 445 result.SwaggerType = object 446 tpe, pkg, alias := knownDefGoType(t.ModelName, *schema, t.goTypeName) 447 result.GoType = tpe 448 result.Pkg = pkg 449 result.PkgAlias = alias 450 } 451 if len(schema.AllOf) > 0 { 452 result.GoType = t.goTypeName(t.ModelName) 453 result.IsComplexObject = true 454 var isNullable bool 455 for _, p := range schema.AllOf { 456 if t.isNullable(&p) { 457 isNullable = true 458 } 459 } 460 result.IsNullable = isNullable 461 result.SwaggerType = object 462 return 463 } 464 465 // if this schema has properties, build a map of property name to 466 // resolved type, this should also flag the object as anonymous, 467 // when a ref is found, the anonymous flag will be reset 468 if len(schema.Properties) > 0 { 469 result.IsNullable = t.isNullable(schema) 470 result.IsComplexObject = true 471 // no return here, still need to check for additional properties 472 } 473 474 // account for additional properties 475 if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { 476 sch := schema.AdditionalProperties.Schema 477 et, er := t.ResolveSchema(sch, sch.Ref.String() == "", false) 478 if er != nil { 479 err = er 480 return 481 } 482 483 result.IsMap = !result.IsComplexObject 484 485 result.SwaggerType = object 486 487 // only complex map elements are nullable (when not forced by x-nullable) 488 // TODO: figure out if required to check when not discriminated like arrays? 489 et.IsNullable = t.isNullable(schema.AdditionalProperties.Schema) 490 if et.IsNullable { 491 result.GoType = "map[string]*" + et.GoType 492 } else { 493 result.GoType = "map[string]" + et.GoType 494 } 495 496 // Resolving nullability conflicts for: 497 // - map[][]...[]{items} 498 // - map[]{aliased type} 499 // 500 // when IsMap is true and the type is a distinct definition, 501 // aliased type or anonymous construct generated independently. 502 // 503 // IsMapNullOverride is to be handled by the generator for special cases 504 // where the map element is considered non nullable and the element itself is. 505 // 506 // This allows to appreciate nullability according to the context 507 needsOverride := result.IsMap && (et.IsArray || (sch.Ref.String() != "" || et.IsAliased || et.IsAnonymous)) 508 509 if needsOverride { 510 var er error 511 if et.IsArray { 512 var it resolvedType 513 s := sch 514 // resolve the last items after nested arrays 515 for s.Items != nil && s.Items.Schema != nil { 516 it, er = t.ResolveSchema(s.Items.Schema, sch.Ref.String() == "", false) 517 if er != nil { 518 return 519 } 520 s = s.Items.Schema 521 } 522 // mark an override when nullable status conflicts, i.e. when the original type is not already nullable 523 if !it.IsAnonymous || it.IsAnonymous && it.IsNullable { 524 result.IsMapNullOverride = true 525 } 526 } else { 527 // this locks the generator on the local nullability status 528 result.IsMapNullOverride = true 529 } 530 } 531 532 t.inferAliasing(&result, schema, isAnonymous, false) 533 result.ElemType = &et 534 return 535 } 536 537 if len(schema.Properties) > 0 { 538 return 539 } 540 541 // an object without property and without AdditionalProperties schema is rendered as interface{} 542 result.GoType = iface 543 result.IsMap = true 544 result.SwaggerType = object 545 result.IsNullable = false 546 result.IsInterface = len(schema.Properties) == 0 547 return 548 } 549 550 // nullableBool makes a boolean a pointer when we want to distinguish the zero value from no value set. 551 // This is the case when: 552 // - a x-nullable extension says so in the spec 553 // - it is **not** a read-only property 554 // - it is a required property 555 // - it has a default value 556 func nullableBool(schema *spec.Schema, isRequired bool) bool { 557 if nullable := nullableExtension(schema.Extensions); nullable != nil { 558 return *nullable 559 } 560 required := isRequired && schema.Default == nil && !schema.ReadOnly 561 optional := !isRequired && (schema.Default != nil || schema.ReadOnly) 562 563 return required || optional 564 } 565 566 // nullableNumber makes a number a pointer when we want to distinguish the zero value from no value set. 567 // This is the case when: 568 // - a x-nullable extension says so in the spec 569 // - it is **not** a read-only property 570 // - it is a required property 571 // - boundaries defines the zero value as a valid value: 572 // - there is a non-exclusive boundary set at the zero value of the type 573 // - the [min,max] range crosses the zero value of the type 574 func nullableNumber(schema *spec.Schema, isRequired bool) bool { 575 if nullable := nullableExtension(schema.Extensions); nullable != nil { 576 return *nullable 577 } 578 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 579 580 isMin := schema.Minimum != nil && (*schema.Minimum != 0 || schema.ExclusiveMinimum) 581 bcMin := schema.Minimum != nil && *schema.Minimum == 0 && !schema.ExclusiveMinimum 582 isMax := schema.Minimum == nil && (schema.Maximum != nil && (*schema.Maximum != 0 || schema.ExclusiveMaximum)) 583 bcMax := schema.Maximum != nil && *schema.Maximum == 0 && !schema.ExclusiveMaximum 584 isMinMax := (schema.Minimum != nil && schema.Maximum != nil && *schema.Minimum < *schema.Maximum) 585 bcMinMax := (schema.Minimum != nil && schema.Maximum != nil && (*schema.Minimum < 0 && 0 < *schema.Maximum)) 586 587 nullable := !schema.ReadOnly && (isRequired || (hasDefault && !(isMin || isMax || isMinMax)) || bcMin || bcMax || bcMinMax) 588 return nullable 589 } 590 591 // nullableString makes a string nullable when we want to distinguish the zero value from no value set. 592 // This is the case when: 593 // - a x-nullable extension says so in the spec 594 // - it is **not** a read-only property 595 // - it is a required property 596 // - it has a MinLength property set to 0 597 // - it has a default other than "" (the zero for strings) and no MinLength or zero MinLength 598 func nullableString(schema *spec.Schema, isRequired bool) bool { 599 if nullable := nullableExtension(schema.Extensions); nullable != nil { 600 return *nullable 601 } 602 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 603 604 isMin := schema.MinLength != nil && *schema.MinLength != 0 605 bcMin := schema.MinLength != nil && *schema.MinLength == 0 606 607 nullable := !schema.ReadOnly && (isRequired || (hasDefault && !isMin) || bcMin) 608 return nullable 609 } 610 611 func nullableStrfmt(schema *spec.Schema, isRequired bool) bool { 612 notBinary := schema.Format != binary 613 if nullable := nullableExtension(schema.Extensions); nullable != nil && notBinary { 614 return *nullable 615 } 616 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 617 618 nullable := !schema.ReadOnly && (isRequired || hasDefault) 619 return notBinary && nullable 620 } 621 622 func nullableExtension(ext spec.Extensions) *bool { 623 if ext == nil { 624 return nil 625 } 626 627 if boolPtr := boolExtension(ext, xNullable); boolPtr != nil { 628 return boolPtr 629 } 630 631 return boolExtension(ext, xIsNullable) 632 } 633 634 func boolExtension(ext spec.Extensions, key string) *bool { 635 if v, ok := ext[key]; ok { 636 if bb, ok := v.(bool); ok { 637 return &bb 638 } 639 } 640 return nil 641 } 642 643 func hasEnumCI(ve spec.Extensions) bool { 644 v, ok := ve[xGoEnumCI] 645 if !ok { 646 return false 647 } 648 649 isEnumCI, ok := v.(bool) 650 // All enumeration types are case-sensitive by default 651 return ok && isEnumCI 652 } 653 654 func (t *typeResolver) shortCircuitResolveExternal(tpe, pkg, alias string, extType *externalTypeDefinition, schema *spec.Schema) resolvedType { 655 // short circuit type resolution for external types 656 var result resolvedType 657 result.Extensions = schema.Extensions 658 result.GoType = tpe 659 result.Pkg = pkg 660 result.PkgAlias = alias 661 result.setKind(extType.Hints.Kind) 662 result.IsNullable = t.isNullable(schema) 663 664 // other extensions 665 if result.IsArray { 666 result.IsEmptyOmitted = false 667 tpe = "array" 668 } 669 result.setExtensions(schema, tpe) 670 return result 671 } 672 673 func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) { 674 debugLog("resolving schema (anon: %t, req: %t) %s", isAnonymous, isRequired, t.ModelName) 675 defer func() { 676 debugLog("returning after resolve schema: %s", pretty.Sprint(result)) 677 }() 678 679 if schema == nil { 680 result.IsInterface = true 681 result.GoType = iface 682 return 683 } 684 685 extType, isExternalType := hasExternalType(schema.Extensions) 686 if isExternalType { 687 tpe, pkg, alias := knownDefGoType(t.ModelName, *schema, t.goTypeName) 688 debugLog("found type declared as external, imported from %s as %s. Has type hints? %t, rendered has embedded? %t", 689 pkg, tpe, extType.Hints.Kind != "", extType.Embedded) 690 691 if extType.Hints.Kind != "" && !extType.Embedded { 692 // use hint to qualify type 693 debugLog("short circuits external type resolution with hint for %s", tpe) 694 result = t.shortCircuitResolveExternal(tpe, pkg, alias, extType, schema) 695 return 696 } 697 698 // use spec to qualify type 699 debugLog("marking type %s as external embedded: %t", tpe, extType.Embedded) 700 // mark this type as an embedded external definition if requested 701 defer func() { 702 result.IsEmbedded = extType.Embedded 703 if result.IsEmbedded { 704 result.ElemType = &resolvedType{ 705 GoType: extType.Import.Alias + "." + extType.Type, 706 Pkg: extType.Import.Package, 707 PkgAlias: extType.Import.Alias, 708 IsNullable: extType.Hints.Nullable, 709 } 710 result.setKind(extType.Hints.Kind) 711 } 712 }() 713 } 714 715 tpe := t.firstType(schema) 716 var returns bool 717 718 returns, result, err = t.resolveSchemaRef(schema, isRequired) 719 720 if returns { 721 if !isAnonymous { 722 result.IsMap = false 723 result.IsComplexObject = true 724 debugLog("not anonymous ref") 725 } 726 debugLog("anonymous after ref") 727 return 728 } 729 730 defer func() { 731 result.setExtensions(schema, tpe) 732 }() 733 734 // special case of swagger type "file", rendered as io.ReadCloser interface 735 if t.firstType(schema) == file { 736 result.SwaggerType = file 737 result.IsPrimitive = true 738 result.IsNullable = false 739 result.GoType = formatMapping[str][binary] 740 result.IsStream = true 741 return 742 } 743 744 returns, result, err = t.resolveFormat(schema, isAnonymous, isRequired) 745 if returns { 746 return 747 } 748 749 result.IsNullable = t.isNullable(schema) || isRequired 750 751 switch tpe { 752 case array: 753 result, err = t.resolveArray(schema, isAnonymous, false) 754 755 case file, number, integer, boolean: 756 result.Extensions = schema.Extensions 757 result.GoType = typeMapping[tpe] 758 result.SwaggerType = tpe 759 t.inferAliasing(&result, schema, isAnonymous, isRequired) 760 761 switch tpe { 762 case boolean: 763 result.IsPrimitive = true 764 result.IsCustomFormatter = false 765 result.IsNullable = nullableBool(schema, isRequired) 766 case number, integer: 767 result.IsPrimitive = true 768 result.IsCustomFormatter = false 769 result.IsNullable = nullableNumber(schema, isRequired) 770 case file: 771 } 772 773 case str: 774 result.GoType = str 775 result.SwaggerType = str 776 t.inferAliasing(&result, schema, isAnonymous, isRequired) 777 778 result.IsPrimitive = true 779 result.IsNullable = nullableString(schema, isRequired) 780 result.Extensions = schema.Extensions 781 782 case object: 783 result, err = t.resolveObject(schema, isAnonymous) 784 if err != nil { 785 result = resolvedType{} 786 break 787 } 788 result.HasDiscriminator = schema.Discriminator != "" 789 790 case "null": 791 result.GoType = iface 792 result.SwaggerType = object 793 result.IsNullable = false 794 result.IsInterface = true 795 796 default: 797 err = fmt.Errorf("unresolvable: %v (format %q)", schema.Type, schema.Format) 798 } 799 return 800 } 801 802 // resolvedType is a swagger type that has been resolved and analyzed for usage 803 // in a template 804 type resolvedType struct { 805 IsAnonymous bool 806 IsArray bool 807 IsMap bool 808 IsInterface bool 809 IsPrimitive bool 810 IsCustomFormatter bool 811 IsAliased bool 812 IsNullable bool 813 IsStream bool 814 IsEmptyOmitted bool 815 IsJSONString bool 816 IsEnumCI bool 817 IsBase64 bool 818 819 // A tuple gets rendered as an anonymous struct with P{index} as property name 820 IsTuple bool 821 HasAdditionalItems bool 822 823 // A complex object gets rendered as a struct 824 IsComplexObject bool 825 826 // A polymorphic type 827 IsBaseType bool 828 HasDiscriminator bool 829 830 GoType string 831 Pkg string 832 PkgAlias string 833 AliasedType string 834 SwaggerType string 835 SwaggerFormat string 836 Extensions spec.Extensions 837 838 // The type of the element in a slice or map 839 ElemType *resolvedType 840 841 // IsMapNullOverride indicates that a nullable object is used within an 842 // aliased map. In this case, the reference is not rendered with a pointer 843 IsMapNullOverride bool 844 845 // IsSuperAlias indicates that the aliased type is really the same type, 846 // e.g. in golang, this translates to: type A = B 847 IsSuperAlias bool 848 849 // IsEmbedded applies to externally defined types. When embedded, a type 850 // is generated in models that embeds the external type, with the Validate 851 // method. 852 IsEmbedded bool 853 } 854 855 func (rt *resolvedType) Zero() string { 856 // if type is aliased, provide zero from the aliased type 857 if rt.IsAliased { 858 if zr, ok := zeroes[rt.AliasedType]; ok { 859 return rt.GoType + "(" + zr + ")" 860 } 861 } 862 // zero function provided as native or by strfmt function 863 if zr, ok := zeroes[rt.GoType]; ok { 864 return zr 865 } 866 // map and slice initializer 867 if rt.IsMap { 868 return "make(" + rt.GoType + ", 50)" 869 } else if rt.IsArray { 870 return "make(" + rt.GoType + ", 0, 50)" 871 } 872 // object initializer 873 if rt.IsTuple || rt.IsComplexObject { 874 if rt.IsNullable { 875 return "new(" + rt.GoType + ")" 876 } 877 return rt.GoType + "{}" 878 } 879 // interface initializer 880 if rt.IsInterface { 881 return "nil" 882 } 883 884 return "" 885 } 886 887 func (rt *resolvedType) setExtensions(schema *spec.Schema, origType string) { 888 rt.IsEnumCI = hasEnumCI(schema.Extensions) 889 rt.setIsEmptyOmitted(schema, origType) 890 rt.setIsJSONString(schema, origType) 891 } 892 893 func (rt *resolvedType) setIsEmptyOmitted(schema *spec.Schema, tpe string) { 894 if v, found := schema.Extensions[xOmitEmpty]; found { 895 omitted, cast := v.(bool) 896 rt.IsEmptyOmitted = omitted && cast 897 return 898 } 899 // array of primitives are by default not empty-omitted, but arrays of aliased type are 900 rt.IsEmptyOmitted = (tpe != array) || (tpe == array && rt.IsAliased) 901 } 902 903 func (rt *resolvedType) setIsJSONString(schema *spec.Schema, tpe string) { 904 _, found := schema.Extensions[xGoJSONString] 905 if !found { 906 rt.IsJSONString = false 907 return 908 } 909 rt.IsJSONString = true 910 } 911 912 func (rt *resolvedType) setKind(kind string) { 913 if kind != "" { 914 debugLog("overriding kind for %s as %s", rt.GoType, kind) 915 } 916 switch kind { 917 case "map": 918 rt.IsMap = true 919 rt.IsArray = false 920 rt.IsComplexObject = false 921 rt.IsInterface = false 922 rt.IsStream = false 923 rt.IsTuple = false 924 rt.IsPrimitive = false 925 rt.SwaggerType = object 926 case "array": 927 rt.IsMap = false 928 rt.IsArray = true 929 rt.IsComplexObject = false 930 rt.IsInterface = false 931 rt.IsStream = false 932 rt.IsTuple = false 933 rt.IsPrimitive = false 934 rt.SwaggerType = array 935 case "object": 936 rt.IsMap = false 937 rt.IsArray = false 938 rt.IsComplexObject = true 939 rt.IsInterface = false 940 rt.IsStream = false 941 rt.IsTuple = false 942 rt.IsPrimitive = false 943 rt.SwaggerType = object 944 case "interface", "null": 945 rt.IsMap = false 946 rt.IsArray = false 947 rt.IsComplexObject = false 948 rt.IsInterface = true 949 rt.IsStream = false 950 rt.IsTuple = false 951 rt.IsPrimitive = false 952 rt.SwaggerType = iface 953 case "stream": 954 rt.IsMap = false 955 rt.IsArray = false 956 rt.IsComplexObject = false 957 rt.IsInterface = false 958 rt.IsStream = true 959 rt.IsTuple = false 960 rt.IsPrimitive = false 961 rt.SwaggerType = file 962 case "tuple": 963 rt.IsMap = false 964 rt.IsArray = false 965 rt.IsComplexObject = false 966 rt.IsInterface = false 967 rt.IsStream = false 968 rt.IsTuple = true 969 rt.IsPrimitive = false 970 rt.SwaggerType = array 971 case "primitive": 972 rt.IsMap = false 973 rt.IsArray = false 974 rt.IsComplexObject = false 975 rt.IsInterface = false 976 rt.IsStream = false 977 rt.IsTuple = false 978 rt.IsPrimitive = true 979 case "": 980 break 981 default: 982 log.Printf("warning: unsupported hint value for external type: %q. Skipped", kind) 983 } 984 }