github.com/go-swagger/go-swagger@v0.31.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/filepath" 21 "reflect" 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, v *spec.CommonValidations) (result resolvedType) { 73 result.SwaggerType = tn 74 result.SwaggerFormat = fmt 75 76 defer func() { 77 guardValidations(result.SwaggerType, v) 78 }() 79 80 if tn == file { 81 // special case of swagger type "file", rendered as io.ReadCloser interface 82 result.IsPrimitive = true 83 result.GoType = formatMapping[str][binary] 84 result.IsStream = true 85 return 86 } 87 88 if fmt != "" { 89 defer func() { 90 guardFormatConflicts(result.SwaggerFormat, v) 91 }() 92 93 fmtn := strings.ReplaceAll(fmt, "-", "") 94 if fmm, ok := formatMapping[tn]; ok { 95 if tpe, ok := fmm[fmtn]; ok { 96 result.GoType = tpe 97 result.IsPrimitive = true 98 _, result.IsCustomFormatter = customFormatters[tpe] 99 // special case of swagger format "binary", rendered as io.ReadCloser interface 100 // TODO(fredbi): should set IsCustomFormatter=false when binary 101 result.IsStream = fmt == binary 102 // special case of swagger format "byte", rendered as a strfmt.Base64 type: no validation 103 result.IsBase64 = fmt == b64 104 return 105 } 106 } 107 } 108 109 if tpe, ok := typeMapping[tn]; ok { 110 result.GoType = tpe 111 _, result.IsPrimitive = primitives[tpe] 112 result.IsPrimitive = ok 113 return 114 } 115 116 if tn == array { 117 result.IsArray = true 118 result.IsPrimitive = false 119 result.IsCustomFormatter = false 120 result.IsNullable = false 121 if items == nil { 122 result.GoType = "[]" + iface 123 return 124 } 125 res := simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations) 126 result.GoType = "[]" + res.GoType 127 return 128 } 129 result.GoType = tn 130 _, result.IsPrimitive = primitives[tn] 131 return 132 } 133 134 func newTypeResolver(pkg, _ string, doc *loads.Document) *typeResolver { 135 resolver := typeResolver{ModelsPackage: pkg, Doc: doc} 136 resolver.KnownDefs = make(map[string]struct{}, len(doc.Spec().Definitions)) 137 for k, sch := range doc.Spec().Definitions { 138 tpe, _, _ := resolver.knownDefGoType(k, sch, nil) 139 resolver.KnownDefs[tpe] = struct{}{} 140 } 141 return &resolver 142 } 143 144 // knownDefGoType returns go type, package and package alias for definition 145 func (t typeResolver) knownDefGoType(def string, schema spec.Schema, clear func(string) string) (string, string, string) { 146 debugLog("known def type: %q", def) 147 ext := schema.Extensions 148 nm, hasGoName := ext.GetString(xGoName) 149 150 if hasGoName { 151 debugLog("known def type %s named from %s as %q", def, xGoName, nm) 152 def = nm 153 } 154 extType, isExternalType := t.resolveExternalType(ext) 155 if !isExternalType || extType.Embedded { 156 if clear == nil { 157 debugLog("known def type no clear: %q", def) 158 return def, t.definitionPkg, "" 159 } 160 debugLog("known def type clear: %q -> %q", def, clear(def)) 161 return clear(def), t.definitionPkg, "" 162 } 163 164 // external type definition trumps regular type resolution 165 if extType.Import.Alias == "" { 166 debugLog("type %s imported as external type %s, assumed in current package", def, extType.Type) 167 return extType.Type, extType.Import.Package, extType.Import.Alias 168 } 169 debugLog("type %s imported as external type from %s as %s.%s", def, extType.Import.Package, extType.Import.Alias, extType.Type) 170 return extType.Import.Alias + "." + extType.Type, extType.Import.Package, extType.Import.Alias 171 } 172 173 // x-go-type: 174 // 175 // type: mytype 176 // import: 177 // package: 178 // alias: 179 // hints: 180 // kind: map|object|array|interface|primitive|stream|tuple 181 // nullable: true|false 182 // embedded: true 183 type externalTypeDefinition struct { 184 Type string 185 Import struct { 186 Package string 187 Alias string 188 } 189 Hints struct { 190 Kind string 191 Nullable *bool 192 NoValidation *bool 193 } 194 Embedded bool 195 } 196 197 func hasExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) { 198 v, ok := ext[xGoType] 199 if !ok { 200 return nil, false 201 } 202 203 var extType externalTypeDefinition 204 err := mapstructure.Decode(v, &extType) 205 if err != nil { 206 log.Printf("warning: x-go-type extension could not be decoded (%v). Skipped", v) 207 return nil, false 208 } 209 210 return &extType, true 211 } 212 213 func (t typeResolver) resolveExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) { 214 extType, hasExt := hasExternalType(ext) 215 if !hasExt { 216 return nil, false 217 } 218 219 // NOTE: 220 // * basic deconfliction of the default alias 221 // * if no package is specified, defaults to models (as provided from CLI or defaut generation location for models) 222 toAlias := func(pkg string) string { 223 mangled := GoLangOpts().ManglePackageName(pkg, "") 224 return deconflictPkg(mangled, func(in string) string { 225 return in + "ext" 226 }) 227 } 228 229 switch { 230 case extType.Import.Package != "" && extType.Import.Alias == "": 231 extType.Import.Alias = toAlias(extType.Import.Package) 232 case extType.Import.Package == "" && extType.Import.Alias != "": 233 extType.Import.Package = t.ModelsFullPkg 234 case extType.Import.Package == "" && extType.Import.Alias == "": 235 // in this case, the external type is assumed to be present in the current package. 236 // For completion, whenever this type is used in anonymous types declared by operations, 237 // we assume this is the package where models are expected to be found. 238 extType.Import.Package = t.ModelsFullPkg 239 if extType.Import.Package != "" { 240 extType.Import.Alias = toAlias(extType.Import.Package) 241 } 242 } 243 244 debugLogAsJSON("known def external %s type", xGoType, extType) 245 246 return extType, true 247 } 248 249 type typeResolver struct { 250 Doc *loads.Document 251 ModelsPackage string // package alias (e.g. "models") 252 ModelsFullPkg string // fully qualified package (e.g. "github.com/example/models") 253 ModelName string 254 KnownDefs map[string]struct{} 255 // unexported fields 256 keepDefinitionsPkg string 257 knownDefsKept map[string]struct{} 258 definitionPkg string // pkg alias to fill in GenSchema.Pkg 259 } 260 261 // NewWithModelName clones a type resolver and specifies a new model name 262 func (t *typeResolver) NewWithModelName(name string) *typeResolver { 263 tt := newTypeResolver(t.ModelsPackage, t.ModelsFullPkg, t.Doc) 264 tt.ModelName = name 265 266 // propagates kept definitions 267 tt.keepDefinitionsPkg = t.keepDefinitionsPkg 268 tt.knownDefsKept = t.knownDefsKept 269 tt.definitionPkg = t.definitionPkg 270 return tt 271 } 272 273 // withKeepDefinitionsPackage instructs the type resolver to keep previously resolved package name for 274 // definitions known at the moment it is first called. 275 func (t *typeResolver) withKeepDefinitionsPackage(definitionsPackage string) *typeResolver { 276 t.keepDefinitionsPkg = definitionsPackage 277 t.knownDefsKept = make(map[string]struct{}, len(t.KnownDefs)) 278 for k := range t.KnownDefs { 279 t.knownDefsKept[k] = struct{}{} 280 } 281 return t 282 } 283 284 // withDefinitionPackage sets the definition pkg that object/struct types to be generated 285 // in GenSchema.Pkg field. 286 // ModelsPackage field can not replace definitionPkg since ModelsPackage will be prepend to .GoType, 287 // while definitionPkg is just used to fill the .Pkg in GenSchema 288 func (t *typeResolver) withDefinitionPackage(pkg string) *typeResolver { 289 t.definitionPkg = pkg 290 return t 291 } 292 293 func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (returns bool, result resolvedType, err error) { 294 if schema.Ref.String() == "" { 295 return 296 } 297 debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String()) 298 299 returns = true 300 var ref *spec.Schema 301 var er error 302 303 ref, er = spec.ResolveRef(t.Doc.Spec(), &schema.Ref) 304 if er != nil { 305 debugLog("error resolving ref %s: %v", schema.Ref.String(), er) 306 err = er 307 return 308 } 309 310 extType, isExternalType := t.resolveExternalType(schema.Extensions) 311 if isExternalType { 312 // deal with validations for an aliased external type 313 result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 314 } 315 316 res, er := t.ResolveSchema(ref, false, isRequired) 317 if er != nil { 318 err = er 319 return 320 } 321 result = res 322 323 tn := filepath.Base(schema.Ref.GetURL().Fragment) 324 tpe, pkg, alias := t.knownDefGoType(tn, *ref, t.goTypeName) 325 debugLog("type name %s, package %s, alias %s", tpe, pkg, alias) 326 if tpe != "" { 327 result.GoType = tpe 328 result.Pkg = pkg 329 result.PkgAlias = alias 330 } 331 result.HasDiscriminator = res.HasDiscriminator 332 result.IsBaseType = result.HasDiscriminator 333 result.IsNullable = result.IsNullable || t.isNullable(ref) // this has to be overridden for slices and maps 334 result.IsEnumCI = false 335 return 336 } 337 338 func (t *typeResolver) inferAliasing(result *resolvedType, _ *spec.Schema, isAnonymous bool, _ bool) { 339 if !isAnonymous && t.ModelName != "" { 340 result.AliasedType = result.GoType 341 result.IsAliased = true 342 result.GoType = t.goTypeName(t.ModelName) 343 result.Pkg = t.definitionPkg 344 } 345 } 346 347 func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRequired bool) (returns bool, result resolvedType, err error) { 348 if schema.Format != "" { 349 // defaults to string 350 result.SwaggerType = str 351 if len(schema.Type) > 0 { 352 result.SwaggerType = schema.Type[0] 353 } 354 355 debugLog("resolving format (anon: %t, req: %t)", isAnonymous, isRequired) 356 schFmt := strings.ReplaceAll(schema.Format, "-", "") 357 if fmm, ok := formatMapping[result.SwaggerType]; ok { 358 if tpe, ok := fmm[schFmt]; ok { 359 returns = true 360 result.GoType = tpe 361 _, result.IsCustomFormatter = customFormatters[tpe] 362 } 363 } 364 if tpe, ok := typeMapping[schFmt]; !returns && ok { 365 returns = true 366 result.GoType = tpe 367 _, result.IsCustomFormatter = customFormatters[tpe] 368 } 369 370 result.SwaggerFormat = schema.Format 371 t.inferAliasing(&result, schema, isAnonymous, isRequired) 372 // special case of swagger format "binary", rendered as io.ReadCloser interface and is therefore not a primitive type 373 // TODO: should set IsCustomFormatter=false in this case. 374 result.IsPrimitive = schFmt != binary 375 result.IsStream = schFmt == binary 376 result.IsBase64 = schFmt == b64 377 // propagate extensions in resolvedType 378 result.Extensions = schema.Extensions 379 380 switch result.SwaggerType { 381 case str: 382 result.IsNullable = nullableStrfmt(schema, isRequired) 383 case number, integer: 384 result.IsNullable = nullableNumber(schema, isRequired) 385 default: 386 result.IsNullable = t.isNullable(schema) 387 } 388 } 389 390 guardFormatConflicts(schema.Format, schema) 391 return 392 } 393 394 // isNullable hints the generator as to render the type with a pointer or not. 395 // 396 // A schema is deemed nullable (i.e. rendered by a pointer) when: 397 // - a custom extension says it has to be so 398 // - it is an object with properties 399 // - it is a composed object (allOf) 400 // 401 // The interpretation of Required as a mean to make a type nullable is carried out elsewhere. 402 func (t *typeResolver) isNullable(schema *spec.Schema) bool { 403 if nullable, ok := t.isNullableOverride(schema); ok { 404 return nullable 405 } 406 407 return len(schema.Properties) > 0 || len(schema.AllOf) > 0 408 } 409 410 // isNullableOverride determines a nullable flag forced by an extension 411 func (t *typeResolver) isNullableOverride(schema *spec.Schema) (bool, bool) { 412 check := func(extension string) (bool, bool) { 413 v, found := schema.Extensions[extension] 414 nullable, cast := v.(bool) 415 return nullable, found && cast 416 } 417 418 if nullable, ok := check(xIsNullable); ok { 419 return nullable, ok 420 } 421 422 if nullable, ok := check(xNullable); ok { 423 return nullable, ok 424 } 425 426 return false, false 427 } 428 429 func (t *typeResolver) firstType(schema *spec.Schema) string { 430 if len(schema.Type) == 0 || schema.Type[0] == "" { 431 return object 432 } 433 if len(schema.Type) > 1 { 434 // JSON-Schema multiple types, e.g. {"type": [ "object", "array" ]} are not supported. 435 // TODO: should keep the first _supported_ type, e.g. skip null 436 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]) 437 } 438 return schema.Type[0] 439 } 440 441 func (t *typeResolver) resolveArray(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) { 442 debugLog("resolving array (anon: %t, req: %t)", isAnonymous, isRequired) 443 444 result.IsArray = true 445 result.IsNullable = false 446 447 if schema.AdditionalItems != nil { 448 result.HasAdditionalItems = (schema.AdditionalItems.Allows || schema.AdditionalItems.Schema != nil) 449 } 450 451 if schema.Items == nil { 452 result.GoType = "[]" + iface 453 result.SwaggerType = array 454 result.SwaggerFormat = "" 455 t.inferAliasing(&result, schema, isAnonymous, isRequired) 456 457 return 458 } 459 460 if len(schema.Items.Schemas) > 0 { 461 result.IsArray = false 462 result.IsTuple = true 463 result.SwaggerType = array 464 result.SwaggerFormat = "" 465 t.inferAliasing(&result, schema, isAnonymous, isRequired) 466 467 return 468 } 469 470 rt, er := t.ResolveSchema(schema.Items.Schema, true, false) 471 if er != nil { 472 err = er 473 return 474 } 475 476 // Override the general nullability rule from ResolveSchema() in array elements: 477 // - only complex items are nullable (when not discriminated, not forced by x-nullable) 478 // - arrays of allOf have non nullable elements when not forced by x-nullable 479 elem := schema.Items.Schema 480 if elem.Ref.String() != "" { 481 // drill into $ref to figure out whether we want the element type to nullable or not 482 resolved, erf := spec.ResolveRef(t.Doc.Spec(), &elem.Ref) 483 if erf != nil { 484 debugLog("error resolving ref %s: %v", schema.Ref.String(), erf) 485 } 486 elem = resolved 487 } 488 489 debugLogAsJSON("resolved item for %s", rt.GoType, elem) 490 if nullable, ok := t.isNullableOverride(elem); ok { 491 debugLog("found nullable override in element %s: %t", rt.GoType, nullable) 492 rt.IsNullable = nullable 493 } else { 494 // this differs from isNullable for elements with AllOf 495 debugLog("no nullable override in element %s: Properties: %t, HasDiscriminator: %t", rt.GoType, len(elem.Properties) > 0, rt.HasDiscriminator) 496 rt.IsNullable = len(elem.Properties) > 0 && !rt.HasDiscriminator 497 } 498 499 result.GoType = "[]" + rt.GoType 500 if rt.IsNullable && !strings.HasPrefix(rt.GoType, "*") { 501 result.GoType = "[]*" + rt.GoType 502 } 503 504 result.ElemType = &rt 505 result.SwaggerType = array 506 result.SwaggerFormat = "" 507 result.IsEnumCI = hasEnumCI(schema.Extensions) 508 t.inferAliasing(&result, schema, isAnonymous, isRequired) 509 result.Extensions = schema.Extensions 510 511 return 512 } 513 514 func (t *typeResolver) goTypeName(nm string) string { 515 if len(t.knownDefsKept) > 0 { 516 // if a definitions package has been defined, already resolved definitions are 517 // always resolved against their original package (e.g. "models"), and not the 518 // current package. 519 // This allows complex anonymous extra schemas to reuse known definitions generated in another package. 520 if _, ok := t.knownDefsKept[nm]; ok { 521 return strings.Join([]string{t.keepDefinitionsPkg, swag.ToGoName(nm)}, ".") 522 } 523 } 524 525 if t.ModelsPackage == "" { 526 return swag.ToGoName(nm) 527 } 528 if _, ok := t.KnownDefs[nm]; ok { 529 return strings.Join([]string{t.ModelsPackage, swag.ToGoName(nm)}, ".") 530 } 531 return swag.ToGoName(nm) 532 } 533 534 func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (result resolvedType, err error) { 535 debugLog("resolving object %s (anon: %t, req: %t)", t.ModelName, isAnonymous, false) 536 537 result.IsAnonymous = isAnonymous 538 539 result.IsBaseType = schema.Discriminator != "" 540 if !isAnonymous { 541 result.SwaggerType = object 542 tpe, pkg, alias := t.knownDefGoType(t.ModelName, *schema, t.goTypeName) 543 result.GoType = tpe 544 result.Pkg = pkg 545 result.PkgAlias = alias 546 } 547 if len(schema.AllOf) > 0 { 548 result.GoType = t.goTypeName(t.ModelName) 549 result.IsComplexObject = true 550 var isNullable bool 551 for _, sch := range schema.AllOf { 552 p := sch 553 if t.isNullable(&p) { 554 isNullable = true 555 } 556 } 557 if override, ok := t.isNullableOverride(schema); ok { 558 // prioritize x-nullable extensions 559 result.IsNullable = override 560 } else { 561 result.IsNullable = isNullable 562 } 563 result.SwaggerType = object 564 return 565 } 566 567 // if this schema has properties, build a map of property name to 568 // resolved type, this should also flag the object as anonymous, 569 // when a ref is found, the anonymous flag will be reset 570 if len(schema.Properties) > 0 { 571 result.IsNullable = t.isNullable(schema) 572 result.IsComplexObject = true 573 // no return here, still need to check for additional properties 574 } 575 576 // account for additional properties 577 if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { 578 sch := schema.AdditionalProperties.Schema 579 et, er := t.ResolveSchema(sch, sch.Ref.String() == "", false) 580 if er != nil { 581 err = er 582 return 583 } 584 585 result.IsMap = !result.IsComplexObject 586 587 result.SwaggerType = object 588 589 if et.IsExternal { 590 // external AdditionalProperties are a special case because we look ahead into schemas 591 extType, _, _ := t.knownDefGoType(t.ModelName, *sch, t.goTypeName) 592 et.GoType = extType 593 } 594 595 // only complex map elements are nullable (when not forced by x-nullable) 596 // TODO: figure out if required to check when not discriminated like arrays? 597 et.IsNullable = !et.IsArray && t.isNullable(schema.AdditionalProperties.Schema) 598 if et.IsNullable { 599 result.GoType = "map[string]*" + et.GoType 600 } else { 601 result.GoType = "map[string]" + et.GoType 602 } 603 604 // Resolving nullability conflicts for: 605 // - map[][]...[]{items} 606 // - map[]{aliased type} 607 // 608 // when IsMap is true and the type is a distinct definition, 609 // aliased type or anonymous construct generated independently. 610 // 611 // IsMapNullOverride is to be handled by the generator for special cases 612 // where the map element is considered non nullable and the element itself is. 613 // 614 // This allows to appreciate nullability according to the context 615 needsOverride := result.IsMap && (et.IsArray || (sch.Ref.String() != "" || et.IsAliased || et.IsAnonymous)) 616 617 if needsOverride { 618 var er error 619 if et.IsArray { 620 var it resolvedType 621 s := sch 622 // resolve the last items after nested arrays 623 for s.Items != nil && s.Items.Schema != nil { 624 it, er = t.ResolveSchema(s.Items.Schema, sch.Ref.String() == "", false) 625 if er != nil { 626 return 627 } 628 s = s.Items.Schema 629 } 630 // mark an override when nullable status conflicts, i.e. when the original type is not already nullable 631 if !it.IsAnonymous || it.IsAnonymous && it.IsNullable { 632 result.IsMapNullOverride = true 633 } 634 } else { 635 // this locks the generator on the local nullability status 636 result.IsMapNullOverride = true 637 } 638 } 639 640 t.inferAliasing(&result, schema, isAnonymous, false) 641 result.ElemType = &et 642 return 643 } 644 645 if len(schema.Properties) > 0 { 646 return 647 } 648 649 // an object without property and without AdditionalProperties schema is rendered as interface{} 650 result.IsMap = true 651 result.SwaggerType = object 652 result.IsNullable = false 653 // an object without properties but with MinProperties or MaxProperties is rendered as map[string]interface{} 654 result.IsInterface = len(schema.Properties) == 0 && !schema.Validations().HasObjectValidations() 655 if result.IsInterface { 656 result.GoType = iface 657 } else { 658 result.GoType = "map[string]interface{}" 659 } 660 return 661 } 662 663 // nullableBool makes a boolean a pointer when we want to distinguish the zero value from no value set. 664 // This is the case when: 665 // - a x-nullable extension says so in the spec 666 // - it is **not** a read-only property 667 // - it is a required property 668 // - it has a default value 669 func nullableBool(schema *spec.Schema, isRequired bool) bool { 670 if nullable := nullableExtension(schema.Extensions); nullable != nil { 671 return *nullable 672 } 673 required := isRequired && schema.Default == nil && !schema.ReadOnly 674 optional := !isRequired && (schema.Default != nil || schema.ReadOnly) 675 676 return required || optional 677 } 678 679 // nullableNumber makes a number a pointer when we want to distinguish the zero value from no value set. 680 // This is the case when: 681 // - a x-nullable extension says so in the spec 682 // - it is **not** a read-only property 683 // - it is a required property 684 // - boundaries defines the zero value as a valid value: 685 // - there is a non-exclusive boundary set at the zero value of the type 686 // - the [min,max] range crosses the zero value of the type 687 func nullableNumber(schema *spec.Schema, isRequired bool) bool { 688 if nullable := nullableExtension(schema.Extensions); nullable != nil { 689 return *nullable 690 } 691 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 692 693 isMin := schema.Minimum != nil && (*schema.Minimum != 0 || schema.ExclusiveMinimum) 694 bcMin := schema.Minimum != nil && *schema.Minimum == 0 && !schema.ExclusiveMinimum 695 isMax := schema.Minimum == nil && (schema.Maximum != nil && (*schema.Maximum != 0 || schema.ExclusiveMaximum)) 696 bcMax := schema.Maximum != nil && *schema.Maximum == 0 && !schema.ExclusiveMaximum 697 isMinMax := (schema.Minimum != nil && schema.Maximum != nil && *schema.Minimum < *schema.Maximum) 698 bcMinMax := (schema.Minimum != nil && schema.Maximum != nil && (*schema.Minimum < 0 && 0 < *schema.Maximum)) 699 700 nullable := !schema.ReadOnly && (isRequired || (hasDefault && !(isMin || isMax || isMinMax)) || bcMin || bcMax || bcMinMax) 701 return nullable 702 } 703 704 // nullableString makes a string nullable when we want to distinguish the zero value from no value set. 705 // This is the case when: 706 // - a x-nullable extension says so in the spec 707 // - it is **not** a read-only property 708 // - it is a required property 709 // - it has a MinLength property set to 0 710 // - it has a default other than "" (the zero for strings) and no MinLength or zero MinLength 711 func nullableString(schema *spec.Schema, isRequired bool) bool { 712 if nullable := nullableExtension(schema.Extensions); nullable != nil { 713 return *nullable 714 } 715 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 716 717 isMin := schema.MinLength != nil && *schema.MinLength != 0 718 bcMin := schema.MinLength != nil && *schema.MinLength == 0 719 720 nullable := !schema.ReadOnly && (isRequired || (hasDefault && !isMin) || bcMin) 721 return nullable 722 } 723 724 func nullableStrfmt(schema *spec.Schema, isRequired bool) bool { 725 notBinary := schema.Format != binary 726 if nullable := nullableExtension(schema.Extensions); nullable != nil && notBinary { 727 return *nullable 728 } 729 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 730 731 nullable := !schema.ReadOnly && (isRequired || hasDefault) 732 return notBinary && nullable 733 } 734 735 func nullableExtension(ext spec.Extensions) *bool { 736 if ext == nil { 737 return nil 738 } 739 740 if boolPtr := boolExtension(ext, xNullable); boolPtr != nil { 741 return boolPtr 742 } 743 744 return boolExtension(ext, xIsNullable) 745 } 746 747 func boolExtension(ext spec.Extensions, key string) *bool { 748 if v, ok := ext[key]; ok { 749 if bb, ok := v.(bool); ok { 750 return &bb 751 } 752 } 753 return nil 754 } 755 756 func hasEnumCI(ve spec.Extensions) bool { 757 v, ok := ve[xGoEnumCI] 758 if !ok { 759 return false 760 } 761 762 isEnumCI, ok := v.(bool) 763 // All enumeration types are case-sensitive by default 764 return ok && isEnumCI 765 } 766 767 func (t *typeResolver) shortCircuitResolveExternal(tpe, pkg, alias string, extType *externalTypeDefinition, schema *spec.Schema, isRequired bool) resolvedType { 768 // short circuit type resolution for external types 769 debugLogAsJSON("shortCircuitResolveExternal", extType) 770 771 var result resolvedType 772 result.Extensions = schema.Extensions 773 result.GoType = tpe 774 result.Pkg = pkg 775 result.PkgAlias = alias 776 result.IsInterface = false 777 // by default consider that we have a type with validations. Use hint "interface" or "noValidation" to disable validations 778 result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 779 result.IsNullable = isRequired 780 781 result.setKind(extType.Hints.Kind) 782 if result.IsInterface || result.IsStream { 783 result.IsNullable = false 784 } 785 if extType.Hints.Nullable != nil { 786 result.IsNullable = swag.BoolValue(extType.Hints.Nullable) 787 } 788 789 if nullable, ok := t.isNullableOverride(schema); ok { 790 result.IsNullable = nullable // x-nullable directive rules them all 791 } 792 793 // other extensions 794 if result.IsArray { 795 result.IsEmptyOmitted = false 796 tpe = "array" 797 } 798 799 result.setExtensions(schema, tpe) 800 return result 801 } 802 803 func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) { 804 debugLog("resolving schema (anon: %t, req: %t) %s", isAnonymous, isRequired, t.ModelName) 805 defer func() { 806 debugLog("returning after resolve schema: %s", pretty.Sprint(result)) 807 }() 808 809 if schema == nil { 810 result.IsInterface = true 811 result.GoType = iface 812 return 813 } 814 815 extType, isExternalType := t.resolveExternalType(schema.Extensions) 816 if isExternalType { 817 tpe, pkg, alias := t.knownDefGoType(t.ModelName, *schema, t.goTypeName) 818 debugLog("found type %s declared as external, imported from %s as %s. Has type hints? %t, rendered has embedded? %t", 819 t.ModelName, pkg, tpe, extType.Hints.Kind != "", extType.Embedded) 820 821 if extType.Hints.Kind != "" && !extType.Embedded { 822 // use hint to qualify type 823 debugLog("short circuits external type resolution with hint for %s", tpe) 824 result = t.shortCircuitResolveExternal(tpe, pkg, alias, extType, schema, isRequired) 825 result.IsExternal = isAnonymous // mark anonymous external types only, not definitions 826 return 827 } 828 829 // use spec to qualify type 830 debugLog("marking type %s as external embedded: %t", tpe, extType.Embedded) 831 defer func() { // enforce bubbling up decisions taken about being an external type 832 // mark this type as an embedded external definition if requested 833 result.IsEmbedded = extType.Embedded 834 result.IsExternal = isAnonymous // for non-embedded, mark anonymous external types only, not definitions 835 836 result.IsAnonymous = false 837 result.IsAliased = true 838 result.IsNullable = isRequired 839 if extType.Hints.Nullable != nil { 840 result.IsNullable = swag.BoolValue(extType.Hints.Nullable) 841 } 842 843 result.IsMap = false 844 result.AliasedType = result.GoType 845 result.IsInterface = false 846 847 if result.IsEmbedded { 848 result.ElemType = &resolvedType{ 849 IsExternal: isAnonymous, // mark anonymous external types only, not definitions 850 IsInterface: false, 851 Pkg: extType.Import.Package, 852 PkgAlias: extType.Import.Alias, 853 SkipExternalValidation: swag.BoolValue(extType.Hints.NoValidation), 854 } 855 if extType.Import.Alias != "" { 856 result.ElemType.GoType = extType.Import.Alias + "." + extType.Type 857 } else { 858 result.ElemType.GoType = extType.Type 859 } 860 result.ElemType.setKind(extType.Hints.Kind) 861 if result.IsInterface || result.IsStream { 862 result.ElemType.IsNullable = false 863 } 864 if extType.Hints.Nullable != nil { 865 result.ElemType.IsNullable = swag.BoolValue(extType.Hints.Nullable) 866 } 867 // embedded external: by default consider validation is skipped for the external type 868 // 869 // NOTE: at this moment the template generates a type assertion, so this setting does not really matter 870 // for embedded types. 871 if extType.Hints.NoValidation != nil { 872 result.ElemType.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 873 } else { 874 result.ElemType.SkipExternalValidation = true 875 } 876 } else { 877 // non-embedded external type: by default consider that validation is enabled (SkipExternalValidation: false) 878 result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 879 } 880 881 if nullable, ok := t.isNullableOverride(schema); ok { 882 result.IsNullable = nullable 883 } 884 }() 885 } 886 887 tpe := t.firstType(schema) 888 var returns bool 889 890 guardValidations(tpe, schema, schema.Type...) 891 892 returns, result, err = t.resolveSchemaRef(schema, isRequired) 893 894 if returns { 895 if !isAnonymous { 896 result.IsMap = false 897 result.IsComplexObject = true 898 } 899 900 return 901 } 902 903 defer func() { 904 result.setExtensions(schema, tpe) 905 }() 906 907 // special case of swagger type "file", rendered as io.ReadCloser interface 908 if t.firstType(schema) == file { 909 result.SwaggerType = file 910 result.IsPrimitive = true 911 result.IsNullable = false 912 result.GoType = formatMapping[str][binary] 913 result.IsStream = true 914 return 915 } 916 917 returns, result, err = t.resolveFormat(schema, isAnonymous, isRequired) 918 if returns { 919 return 920 } 921 922 result.IsNullable = t.isNullable(schema) || isRequired 923 924 switch tpe { 925 case array: 926 result, err = t.resolveArray(schema, isAnonymous, false) 927 928 case file, number, integer, boolean: 929 result.Extensions = schema.Extensions 930 result.GoType = typeMapping[tpe] 931 result.SwaggerType = tpe 932 t.inferAliasing(&result, schema, isAnonymous, isRequired) 933 934 switch tpe { 935 case boolean: 936 result.IsPrimitive = true 937 result.IsCustomFormatter = false 938 result.IsNullable = nullableBool(schema, isRequired) 939 case number, integer: 940 result.IsPrimitive = true 941 result.IsCustomFormatter = false 942 result.IsNullable = nullableNumber(schema, isRequired) 943 case file: 944 } 945 946 case str: 947 result.GoType = str 948 result.SwaggerType = str 949 t.inferAliasing(&result, schema, isAnonymous, isRequired) 950 951 result.IsPrimitive = true 952 result.IsNullable = nullableString(schema, isRequired) 953 result.Extensions = schema.Extensions 954 955 case object: 956 result, err = t.resolveObject(schema, isAnonymous) 957 if err != nil { 958 result = resolvedType{} 959 break 960 } 961 result.HasDiscriminator = schema.Discriminator != "" 962 963 case "null": 964 if schema.Validations().HasObjectValidations() { 965 // no explicit object type, but inferred from object validations: 966 // this makes the type a map[string]interface{} instead of interface{} 967 result, err = t.resolveObject(schema, isAnonymous) 968 if err != nil { 969 result = resolvedType{} 970 break 971 } 972 result.HasDiscriminator = schema.Discriminator != "" 973 break 974 } 975 976 result.GoType = iface 977 result.SwaggerType = object 978 result.IsNullable = false 979 result.IsInterface = true 980 981 default: 982 err = fmt.Errorf("unresolvable: %v (format %q)", schema.Type, schema.Format) 983 } 984 985 return 986 } 987 988 func warnSkipValidation(types interface{}) func(string, interface{}) { 989 return func(validation string, value interface{}) { 990 value = reflect.Indirect(reflect.ValueOf(value)).Interface() 991 log.Printf("warning: validation %s (value: %v) not compatible with type %v. Skipped", validation, value, types) 992 } 993 } 994 995 // guardValidations removes (with a warning) validations that don't fit with the schema type. 996 // 997 // Notice that the "enum" validation is allowed on any type but file. 998 func guardValidations(tpe string, schema interface { 999 Validations() spec.SchemaValidations 1000 SetValidations(spec.SchemaValidations) 1001 }, types ...string, 1002 ) { 1003 v := schema.Validations() 1004 if len(types) == 0 { 1005 types = []string{tpe} 1006 } 1007 defer func() { 1008 schema.SetValidations(v) 1009 }() 1010 1011 if tpe != array { 1012 v.ClearArrayValidations(warnSkipValidation(types)) 1013 } 1014 1015 if tpe != str && tpe != file { 1016 v.ClearStringValidations(warnSkipValidation(types)) 1017 } 1018 1019 if tpe != object { 1020 v.ClearObjectValidations(warnSkipValidation(types)) 1021 } 1022 1023 if tpe != number && tpe != integer { 1024 v.ClearNumberValidations(warnSkipValidation(types)) 1025 } 1026 1027 if tpe == file { 1028 // keep MinLength/MaxLength on file 1029 if v.Pattern != "" { 1030 warnSkipValidation(types)("pattern", v.Pattern) 1031 v.Pattern = "" 1032 } 1033 if v.HasEnum() { 1034 warnSkipValidation(types)("enum", v.Enum) 1035 v.Enum = nil 1036 } 1037 } 1038 1039 // other cases: mapped as interface{}: no validations allowed but Enum 1040 } 1041 1042 // guardFormatConflicts handles all conflicting properties 1043 // (for schema model or simple schema) when a format is set. 1044 // 1045 // At this moment, validation guards already handle all known conflicts, but for the 1046 // special case of binary (i.e. io.Reader). 1047 func guardFormatConflicts(format string, schema interface { 1048 Validations() spec.SchemaValidations 1049 SetValidations(spec.SchemaValidations) 1050 }, 1051 ) { 1052 v := schema.Validations() 1053 msg := fmt.Sprintf("for format %q", format) 1054 1055 // for this format, no additional validations are supported 1056 if format == "binary" { 1057 // no validations supported on binary fields at this moment (io.Reader) 1058 v.ClearStringValidations(warnSkipValidation(msg)) 1059 if v.HasEnum() { 1060 warnSkipValidation(msg) 1061 v.Enum = nil 1062 } 1063 schema.SetValidations(v) 1064 } 1065 // more cases should be inserted here if they arise 1066 } 1067 1068 // resolvedType is a swagger type that has been resolved and analyzed for usage 1069 // in a template 1070 type resolvedType struct { 1071 IsAnonymous bool 1072 IsArray bool 1073 IsMap bool 1074 IsInterface bool 1075 IsPrimitive bool 1076 IsCustomFormatter bool 1077 IsAliased bool 1078 IsNullable bool 1079 IsStream bool 1080 IsEmptyOmitted bool 1081 IsJSONString bool 1082 IsEnumCI bool 1083 IsBase64 bool 1084 IsExternal bool 1085 1086 // A tuple gets rendered as an anonymous struct with P{index} as property name 1087 IsTuple bool 1088 HasAdditionalItems bool 1089 1090 // A complex object gets rendered as a struct 1091 IsComplexObject bool 1092 1093 // A polymorphic type 1094 IsBaseType bool 1095 HasDiscriminator bool 1096 1097 GoType string 1098 Pkg string 1099 PkgAlias string 1100 AliasedType string 1101 SwaggerType string 1102 SwaggerFormat string 1103 Extensions spec.Extensions 1104 1105 // The type of the element in a slice or map 1106 ElemType *resolvedType 1107 1108 // IsMapNullOverride indicates that a nullable object is used within an 1109 // aliased map. In this case, the reference is not rendered with a pointer 1110 IsMapNullOverride bool 1111 1112 // IsSuperAlias indicates that the aliased type is really the same type, 1113 // e.g. in golang, this translates to: type A = B 1114 IsSuperAlias bool 1115 1116 // IsEmbedded applies to externally defined types. When embedded, a type 1117 // is generated in models that embeds the external type, with the Validate 1118 // method. 1119 IsEmbedded bool 1120 1121 SkipExternalValidation bool 1122 } 1123 1124 // Zero returns an initializer for the type 1125 func (rt resolvedType) Zero() string { 1126 // if type is aliased, provide zero from the aliased type 1127 if rt.IsAliased { 1128 if zr, ok := zeroes[rt.AliasedType]; ok { 1129 return rt.GoType + "(" + zr + ")" 1130 } 1131 } 1132 // zero function provided as native or by strfmt function 1133 if zr, ok := zeroes[rt.GoType]; ok { 1134 return zr 1135 } 1136 // map and slice initializer 1137 if rt.IsMap { 1138 return "make(" + rt.GoType + ", 50)" 1139 } else if rt.IsArray { 1140 return "make(" + rt.GoType + ", 0, 50)" 1141 } 1142 // object initializer 1143 if rt.IsTuple || rt.IsComplexObject { 1144 if rt.IsNullable { 1145 return "new(" + rt.GoType + ")" 1146 } 1147 return rt.GoType + "{}" 1148 } 1149 // interface initializer 1150 if rt.IsInterface { 1151 return "nil" 1152 } 1153 1154 return "" 1155 } 1156 1157 // ToString returns a string conversion for a type akin to a string 1158 func (rt resolvedType) ToString(value string) string { 1159 if !rt.IsPrimitive || rt.SwaggerType != "string" || rt.IsStream { 1160 return "" 1161 } 1162 if rt.IsCustomFormatter { 1163 if rt.IsAliased { 1164 return fmt.Sprintf("%s(%s).String()", rt.AliasedType, value) 1165 } 1166 return fmt.Sprintf("%s.String()", value) 1167 } 1168 var deref string 1169 if rt.IsNullable { 1170 deref = "*" 1171 } 1172 if rt.GoType == "string" || rt.GoType == "*string" { 1173 return fmt.Sprintf("%s%s", deref, value) 1174 } 1175 1176 return fmt.Sprintf("string(%s%s)", deref, value) 1177 } 1178 1179 func (rt *resolvedType) setExtensions(schema *spec.Schema, origType string) { 1180 rt.IsEnumCI = hasEnumCI(schema.Extensions) 1181 rt.setIsEmptyOmitted(schema, origType) 1182 rt.setIsJSONString(schema, origType) 1183 1184 if customTag, found := schema.Extensions[xGoCustomTag]; found { 1185 if rt.Extensions == nil { 1186 rt.Extensions = make(spec.Extensions) 1187 } 1188 rt.Extensions[xGoCustomTag] = customTag 1189 } 1190 } 1191 1192 func (rt *resolvedType) setIsEmptyOmitted(schema *spec.Schema, tpe string) { 1193 if v, found := schema.Extensions[xOmitEmpty]; found { 1194 omitted, cast := v.(bool) 1195 rt.IsEmptyOmitted = omitted && cast 1196 return 1197 } 1198 // array of primitives are by default not empty-omitted, but arrays of aliased type are 1199 rt.IsEmptyOmitted = (tpe != array) || (tpe == array && rt.IsAliased) 1200 } 1201 1202 func (rt *resolvedType) setIsJSONString(schema *spec.Schema, _ string) { 1203 _, found := schema.Extensions[xGoJSONString] 1204 if !found { 1205 rt.IsJSONString = false 1206 return 1207 } 1208 rt.IsJSONString = true 1209 } 1210 1211 func (rt *resolvedType) setKind(kind string) { 1212 if kind != "" { 1213 debugLog("overriding kind for %s as %s", rt.GoType, kind) 1214 } 1215 switch kind { 1216 case "map": 1217 rt.IsMap = true 1218 rt.IsArray = false 1219 rt.IsComplexObject = false 1220 rt.IsInterface = false 1221 rt.IsStream = false 1222 rt.IsTuple = false 1223 rt.IsPrimitive = false 1224 rt.SwaggerType = object 1225 case "array": 1226 rt.IsMap = false 1227 rt.IsArray = true 1228 rt.IsComplexObject = false 1229 rt.IsInterface = false 1230 rt.IsStream = false 1231 rt.IsTuple = false 1232 rt.IsPrimitive = false 1233 rt.SwaggerType = array 1234 case "object": 1235 rt.IsMap = false 1236 rt.IsArray = false 1237 rt.IsComplexObject = true 1238 rt.IsInterface = false 1239 rt.IsStream = false 1240 rt.IsTuple = false 1241 rt.IsPrimitive = false 1242 rt.SwaggerType = object 1243 case "interface", "null": 1244 rt.IsMap = false 1245 rt.IsArray = false 1246 rt.IsComplexObject = false 1247 rt.IsInterface = true 1248 rt.IsStream = false 1249 rt.IsTuple = false 1250 rt.IsPrimitive = false 1251 rt.SwaggerType = iface 1252 case "stream": 1253 rt.IsMap = false 1254 rt.IsArray = false 1255 rt.IsComplexObject = false 1256 rt.IsInterface = false 1257 rt.IsStream = true 1258 rt.IsTuple = false 1259 rt.IsPrimitive = false 1260 rt.SwaggerType = file 1261 case "tuple": 1262 rt.IsMap = false 1263 rt.IsArray = false 1264 rt.IsComplexObject = false 1265 rt.IsInterface = false 1266 rt.IsStream = false 1267 rt.IsTuple = true 1268 rt.IsPrimitive = false 1269 rt.SwaggerType = array 1270 case "primitive": 1271 rt.IsMap = false 1272 rt.IsArray = false 1273 rt.IsComplexObject = false 1274 rt.IsInterface = false 1275 rt.IsStream = false 1276 rt.IsTuple = false 1277 rt.IsPrimitive = true 1278 case "": 1279 break 1280 default: 1281 log.Printf("warning: unsupported hint value for external type: %q. Skipped", kind) 1282 } 1283 }