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