github.com/circl-dev/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/circl-dev/loads" 25 "github.com/circl-dev/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 // type: mytype 175 // import: 176 // package: 177 // alias: 178 // hints: 179 // kind: map|object|array|interface|primitive|stream|tuple 180 // nullable: true|false 181 // embedded: true 182 type externalTypeDefinition struct { 183 Type string 184 Import struct { 185 Package string 186 Alias string 187 } 188 Hints struct { 189 Kind string 190 Nullable *bool 191 NoValidation *bool 192 } 193 Embedded bool 194 } 195 196 func hasExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) { 197 v, ok := ext[xGoType] 198 if !ok { 199 return nil, false 200 } 201 202 var extType externalTypeDefinition 203 err := mapstructure.Decode(v, &extType) 204 if err != nil { 205 log.Printf("warning: x-go-type extension could not be decoded (%v). Skipped", v) 206 return nil, false 207 } 208 209 return &extType, true 210 } 211 212 func (t typeResolver) resolveExternalType(ext spec.Extensions) (*externalTypeDefinition, bool) { 213 extType, hasExt := hasExternalType(ext) 214 if !hasExt { 215 return nil, false 216 } 217 218 // NOTE: 219 // * basic deconfliction of the default alias 220 // * if no package is specified, defaults to models (as provided from CLI or defaut generation location for models) 221 toAlias := func(pkg string) string { 222 mangled := GoLangOpts().ManglePackageName(pkg, "") 223 return deconflictPkg(mangled, func(in string) string { 224 return in + "ext" 225 }) 226 } 227 228 switch { 229 case extType.Import.Package != "" && extType.Import.Alias == "": 230 extType.Import.Alias = toAlias(extType.Import.Package) 231 case extType.Import.Package == "" && extType.Import.Alias != "": 232 extType.Import.Package = t.ModelsFullPkg 233 case extType.Import.Package == "" && extType.Import.Alias == "": 234 // in this case, the external type is assumed to be present in the current package. 235 // For completion, whenever this type is used in anonymous types declared by operations, 236 // we assume this is the package where models are expected to be found. 237 extType.Import.Package = t.ModelsFullPkg 238 if extType.Import.Package != "" { 239 extType.Import.Alias = toAlias(extType.Import.Package) 240 } 241 } 242 243 debugLogAsJSON("known def external %s type", xGoType, extType) 244 245 return extType, true 246 } 247 248 type typeResolver struct { 249 Doc *loads.Document 250 ModelsPackage string // package alias (e.g. "models") 251 ModelsFullPkg string // fully qualified package (e.g. "github.com/example/models") 252 ModelName string 253 KnownDefs map[string]struct{} 254 // unexported fields 255 keepDefinitionsPkg string 256 knownDefsKept map[string]struct{} 257 definitionPkg string // pkg alias to fill in GenSchema.Pkg 258 } 259 260 // NewWithModelName clones a type resolver and specifies a new model name 261 func (t *typeResolver) NewWithModelName(name string) *typeResolver { 262 tt := newTypeResolver(t.ModelsPackage, t.ModelsFullPkg, t.Doc) 263 tt.ModelName = name 264 265 // propagates kept definitions 266 tt.keepDefinitionsPkg = t.keepDefinitionsPkg 267 tt.knownDefsKept = t.knownDefsKept 268 tt.definitionPkg = t.definitionPkg 269 return tt 270 } 271 272 // withKeepDefinitionsPackage instructs the type resolver to keep previously resolved package name for 273 // definitions known at the moment it is first called. 274 func (t *typeResolver) withKeepDefinitionsPackage(definitionsPackage string) *typeResolver { 275 t.keepDefinitionsPkg = definitionsPackage 276 t.knownDefsKept = make(map[string]struct{}, len(t.KnownDefs)) 277 for k := range t.KnownDefs { 278 t.knownDefsKept[k] = struct{}{} 279 } 280 return t 281 } 282 283 // withDefinitionPackage sets the definition pkg that object/struct types to be generated 284 // in GenSchema.Pkg field. 285 // ModelsPackage field can not replace definitionPkg since ModelsPackage will be prepend to .GoType, 286 // while definitionPkg is just used to fill the .Pkg in GenSchema 287 func (t *typeResolver) withDefinitionPackage(pkg string) *typeResolver { 288 t.definitionPkg = pkg 289 return t 290 } 291 292 func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (returns bool, result resolvedType, err error) { 293 if schema.Ref.String() == "" { 294 return 295 } 296 debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String()) 297 298 returns = true 299 var ref *spec.Schema 300 var er error 301 302 ref, er = spec.ResolveRef(t.Doc.Spec(), &schema.Ref) 303 if er != nil { 304 debugLog("error resolving ref %s: %v", schema.Ref.String(), er) 305 err = er 306 return 307 } 308 309 extType, isExternalType := t.resolveExternalType(schema.Extensions) 310 if isExternalType { 311 // deal with validations for an aliased external type 312 result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 313 } 314 315 res, er := t.ResolveSchema(ref, false, isRequired) 316 if er != nil { 317 err = er 318 return 319 } 320 result = res 321 322 tn := filepath.Base(schema.Ref.GetURL().Fragment) 323 tpe, pkg, alias := t.knownDefGoType(tn, *ref, t.goTypeName) 324 debugLog("type name %s, package %s, alias %s", tpe, pkg, alias) 325 if tpe != "" { 326 result.GoType = tpe 327 result.Pkg = pkg 328 result.PkgAlias = alias 329 } 330 result.HasDiscriminator = res.HasDiscriminator 331 result.IsBaseType = result.HasDiscriminator 332 result.IsNullable = result.IsNullable || t.isNullable(ref) // this has to be overriden for slices and maps 333 result.IsEnumCI = false 334 return 335 } 336 337 func (t *typeResolver) inferAliasing(result *resolvedType, schema *spec.Schema, isAnonymous bool, isRequired bool) { 338 if !isAnonymous && t.ModelName != "" { 339 result.AliasedType = result.GoType 340 result.IsAliased = true 341 result.GoType = t.goTypeName(t.ModelName) 342 result.Pkg = t.definitionPkg 343 } 344 } 345 346 func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRequired bool) (returns bool, result resolvedType, err error) { 347 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 404 if nullable, ok := t.isNullableOverride(schema); ok { 405 return nullable 406 } 407 408 return len(schema.Properties) > 0 || len(schema.AllOf) > 0 409 } 410 411 // isNullableOverride determines a nullable flag forced by an extension 412 func (t *typeResolver) isNullableOverride(schema *spec.Schema) (bool, bool) { 413 check := func(extension string) (bool, bool) { 414 v, found := schema.Extensions[extension] 415 nullable, cast := v.(bool) 416 return nullable, found && cast 417 } 418 419 if nullable, ok := check(xIsNullable); ok { 420 return nullable, ok 421 } 422 423 if nullable, ok := check(xNullable); ok { 424 return nullable, ok 425 } 426 427 return false, false 428 } 429 430 func (t *typeResolver) firstType(schema *spec.Schema) string { 431 if len(schema.Type) == 0 || schema.Type[0] == "" { 432 return object 433 } 434 if len(schema.Type) > 1 { 435 // JSON-Schema multiple types, e.g. {"type": [ "object", "array" ]} are not supported. 436 // TODO: should keep the first _supported_ type, e.g. skip null 437 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]) 438 } 439 return schema.Type[0] 440 } 441 442 func (t *typeResolver) resolveArray(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) { 443 debugLog("resolving array (anon: %t, req: %t)", isAnonymous, isRequired) 444 445 result.IsArray = true 446 result.IsNullable = false 447 448 if schema.AdditionalItems != nil { 449 result.HasAdditionalItems = (schema.AdditionalItems.Allows || schema.AdditionalItems.Schema != nil) 450 } 451 452 if schema.Items == nil { 453 result.GoType = "[]" + iface 454 result.SwaggerType = array 455 result.SwaggerFormat = "" 456 t.inferAliasing(&result, schema, isAnonymous, isRequired) 457 458 return 459 } 460 461 if len(schema.Items.Schemas) > 0 { 462 result.IsArray = false 463 result.IsTuple = true 464 result.SwaggerType = array 465 result.SwaggerFormat = "" 466 t.inferAliasing(&result, schema, isAnonymous, isRequired) 467 468 return 469 } 470 471 rt, er := t.ResolveSchema(schema.Items.Schema, true, false) 472 if er != nil { 473 err = er 474 return 475 } 476 477 // Override the general nullability rule from ResolveSchema() in array elements: 478 // - only complex items are nullable (when not discriminated, not forced by x-nullable) 479 // - arrays of allOf have non nullable elements when not forced by x-nullable 480 elem := schema.Items.Schema 481 if elem.Ref.String() != "" { 482 // drill into $ref to figure out whether we want the element type to nullable or not 483 resolved, erf := spec.ResolveRef(t.Doc.Spec(), &elem.Ref) 484 if erf != nil { 485 debugLog("error resolving ref %s: %v", schema.Ref.String(), erf) 486 } 487 elem = resolved 488 } 489 490 debugLogAsJSON("resolved item for %s", rt.GoType, elem) 491 if nullable, ok := t.isNullableOverride(elem); ok { 492 debugLog("found nullable override in element %s: %t", rt.GoType, nullable) 493 rt.IsNullable = nullable 494 } else { 495 // this differs from isNullable for elements with AllOf 496 debugLog("no nullable override in element %s: Properties: %t, HasDiscriminator: %t", rt.GoType, len(elem.Properties) > 0, rt.HasDiscriminator) 497 rt.IsNullable = len(elem.Properties) > 0 && !rt.HasDiscriminator 498 } 499 500 result.GoType = "[]" + rt.GoType 501 if rt.IsNullable && !strings.HasPrefix(rt.GoType, "*") { 502 result.GoType = "[]*" + rt.GoType 503 } 504 505 result.ElemType = &rt 506 result.SwaggerType = array 507 result.SwaggerFormat = "" 508 result.IsEnumCI = hasEnumCI(schema.Extensions) 509 t.inferAliasing(&result, schema, isAnonymous, isRequired) 510 result.Extensions = schema.Extensions 511 512 return 513 } 514 515 func (t *typeResolver) goTypeName(nm string) string { 516 if len(t.knownDefsKept) > 0 { 517 // if a definitions package has been defined, already resolved definitions are 518 // always resolved against their original package (e.g. "models"), and not the 519 // current package. 520 // This allows complex anonymous extra schemas to reuse known definitions generated in another package. 521 if _, ok := t.knownDefsKept[nm]; ok { 522 return strings.Join([]string{t.keepDefinitionsPkg, swag.ToGoName(nm)}, ".") 523 } 524 } 525 526 if t.ModelsPackage == "" { 527 return swag.ToGoName(nm) 528 } 529 if _, ok := t.KnownDefs[nm]; ok { 530 return strings.Join([]string{t.ModelsPackage, swag.ToGoName(nm)}, ".") 531 } 532 return swag.ToGoName(nm) 533 } 534 535 func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (result resolvedType, err error) { 536 debugLog("resolving object %s (anon: %t, req: %t)", t.ModelName, isAnonymous, false) 537 538 result.IsAnonymous = isAnonymous 539 540 result.IsBaseType = schema.Discriminator != "" 541 if !isAnonymous { 542 result.SwaggerType = object 543 tpe, pkg, alias := t.knownDefGoType(t.ModelName, *schema, t.goTypeName) 544 result.GoType = tpe 545 result.Pkg = pkg 546 result.PkgAlias = alias 547 } 548 if len(schema.AllOf) > 0 { 549 result.GoType = t.goTypeName(t.ModelName) 550 result.IsComplexObject = true 551 var isNullable bool 552 for _, sch := range schema.AllOf { 553 p := sch 554 if t.isNullable(&p) { 555 isNullable = true 556 } 557 } 558 if override, ok := t.isNullableOverride(schema); ok { 559 // prioritize x-nullable extensions 560 result.IsNullable = override 561 } else { 562 result.IsNullable = isNullable 563 } 564 result.SwaggerType = object 565 return 566 } 567 568 // if this schema has properties, build a map of property name to 569 // resolved type, this should also flag the object as anonymous, 570 // when a ref is found, the anonymous flag will be reset 571 if len(schema.Properties) > 0 { 572 result.IsNullable = t.isNullable(schema) 573 result.IsComplexObject = true 574 // no return here, still need to check for additional properties 575 } 576 577 // account for additional properties 578 if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { 579 sch := schema.AdditionalProperties.Schema 580 et, er := t.ResolveSchema(sch, sch.Ref.String() == "", false) 581 if er != nil { 582 err = er 583 return 584 } 585 586 result.IsMap = !result.IsComplexObject 587 588 result.SwaggerType = object 589 590 if et.IsExternal { 591 // external AdditionalProperties are a special case because we look ahead into schemas 592 extType, _, _ := t.knownDefGoType(t.ModelName, *sch, t.goTypeName) 593 et.GoType = extType 594 } 595 596 // only complex map elements are nullable (when not forced by x-nullable) 597 // TODO: figure out if required to check when not discriminated like arrays? 598 et.IsNullable = !et.IsArray && t.isNullable(schema.AdditionalProperties.Schema) 599 if et.IsNullable { 600 result.GoType = "map[string]*" + et.GoType 601 } else { 602 result.GoType = "map[string]" + et.GoType 603 } 604 605 // Resolving nullability conflicts for: 606 // - map[][]...[]{items} 607 // - map[]{aliased type} 608 // 609 // when IsMap is true and the type is a distinct definition, 610 // aliased type or anonymous construct generated independently. 611 // 612 // IsMapNullOverride is to be handled by the generator for special cases 613 // where the map element is considered non nullable and the element itself is. 614 // 615 // This allows to appreciate nullability according to the context 616 needsOverride := result.IsMap && (et.IsArray || (sch.Ref.String() != "" || et.IsAliased || et.IsAnonymous)) 617 618 if needsOverride { 619 var er error 620 if et.IsArray { 621 var it resolvedType 622 s := sch 623 // resolve the last items after nested arrays 624 for s.Items != nil && s.Items.Schema != nil { 625 it, er = t.ResolveSchema(s.Items.Schema, sch.Ref.String() == "", false) 626 if er != nil { 627 return 628 } 629 s = s.Items.Schema 630 } 631 // mark an override when nullable status conflicts, i.e. when the original type is not already nullable 632 if !it.IsAnonymous || it.IsAnonymous && it.IsNullable { 633 result.IsMapNullOverride = true 634 } 635 } else { 636 // this locks the generator on the local nullability status 637 result.IsMapNullOverride = true 638 } 639 } 640 641 t.inferAliasing(&result, schema, isAnonymous, false) 642 result.ElemType = &et 643 return 644 } 645 646 if len(schema.Properties) > 0 { 647 return 648 } 649 650 // an object without property and without AdditionalProperties schema is rendered as interface{} 651 result.IsMap = true 652 result.SwaggerType = object 653 result.IsNullable = false 654 // an object without properties but with MinProperties or MaxProperties is rendered as map[string]interface{} 655 result.IsInterface = len(schema.Properties) == 0 && !schema.Validations().HasObjectValidations() 656 if result.IsInterface { 657 result.GoType = iface 658 } else { 659 result.GoType = "map[string]interface{}" 660 } 661 return 662 } 663 664 // nullableBool makes a boolean a pointer when we want to distinguish the zero value from no value set. 665 // This is the case when: 666 // - a x-nullable extension says so in the spec 667 // - it is **not** a read-only property 668 // - it is a required property 669 // - it has a default value 670 func nullableBool(schema *spec.Schema, isRequired bool) bool { 671 if nullable := nullableExtension(schema.Extensions); nullable != nil { 672 return *nullable 673 } 674 required := isRequired && schema.Default == nil && !schema.ReadOnly 675 optional := !isRequired && (schema.Default != nil || schema.ReadOnly) 676 677 return required || optional 678 } 679 680 // nullableNumber makes a number a pointer when we want to distinguish the zero value from no value set. 681 // This is the case when: 682 // - a x-nullable extension says so in the spec 683 // - it is **not** a read-only property 684 // - it is a required property 685 // - boundaries defines the zero value as a valid value: 686 // - there is a non-exclusive boundary set at the zero value of the type 687 // - the [min,max] range crosses the zero value of the type 688 func nullableNumber(schema *spec.Schema, isRequired bool) bool { 689 if nullable := nullableExtension(schema.Extensions); nullable != nil { 690 return *nullable 691 } 692 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 693 694 isMin := schema.Minimum != nil && (*schema.Minimum != 0 || schema.ExclusiveMinimum) 695 bcMin := schema.Minimum != nil && *schema.Minimum == 0 && !schema.ExclusiveMinimum 696 isMax := schema.Minimum == nil && (schema.Maximum != nil && (*schema.Maximum != 0 || schema.ExclusiveMaximum)) 697 bcMax := schema.Maximum != nil && *schema.Maximum == 0 && !schema.ExclusiveMaximum 698 isMinMax := (schema.Minimum != nil && schema.Maximum != nil && *schema.Minimum < *schema.Maximum) 699 bcMinMax := (schema.Minimum != nil && schema.Maximum != nil && (*schema.Minimum < 0 && 0 < *schema.Maximum)) 700 701 nullable := !schema.ReadOnly && (isRequired || (hasDefault && !(isMin || isMax || isMinMax)) || bcMin || bcMax || bcMinMax) 702 return nullable 703 } 704 705 // nullableString makes a string nullable when we want to distinguish the zero value from no value set. 706 // This is the case when: 707 // - a x-nullable extension says so in the spec 708 // - it is **not** a read-only property 709 // - it is a required property 710 // - it has a MinLength property set to 0 711 // - it has a default other than "" (the zero for strings) and no MinLength or zero MinLength 712 func nullableString(schema *spec.Schema, isRequired bool) bool { 713 if nullable := nullableExtension(schema.Extensions); nullable != nil { 714 return *nullable 715 } 716 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 717 718 isMin := schema.MinLength != nil && *schema.MinLength != 0 719 bcMin := schema.MinLength != nil && *schema.MinLength == 0 720 721 nullable := !schema.ReadOnly && (isRequired || (hasDefault && !isMin) || bcMin) 722 return nullable 723 } 724 725 func nullableStrfmt(schema *spec.Schema, isRequired bool) bool { 726 notBinary := schema.Format != binary 727 if nullable := nullableExtension(schema.Extensions); nullable != nil && notBinary { 728 return *nullable 729 } 730 hasDefault := schema.Default != nil && !swag.IsZero(schema.Default) 731 732 nullable := !schema.ReadOnly && (isRequired || hasDefault) 733 return notBinary && nullable 734 } 735 736 func nullableExtension(ext spec.Extensions) *bool { 737 if ext == nil { 738 return nil 739 } 740 741 if boolPtr := boolExtension(ext, xNullable); boolPtr != nil { 742 return boolPtr 743 } 744 745 return boolExtension(ext, xIsNullable) 746 } 747 748 func boolExtension(ext spec.Extensions, key string) *bool { 749 if v, ok := ext[key]; ok { 750 if bb, ok := v.(bool); ok { 751 return &bb 752 } 753 } 754 return nil 755 } 756 757 func hasEnumCI(ve spec.Extensions) bool { 758 v, ok := ve[xGoEnumCI] 759 if !ok { 760 return false 761 } 762 763 isEnumCI, ok := v.(bool) 764 // All enumeration types are case-sensitive by default 765 return ok && isEnumCI 766 } 767 768 func (t *typeResolver) shortCircuitResolveExternal(tpe, pkg, alias string, extType *externalTypeDefinition, schema *spec.Schema, isRequired bool) resolvedType { 769 // short circuit type resolution for external types 770 debugLogAsJSON("shortCircuitResolveExternal", extType) 771 772 var result resolvedType 773 result.Extensions = schema.Extensions 774 result.GoType = tpe 775 result.Pkg = pkg 776 result.PkgAlias = alias 777 result.IsInterface = false 778 // by default consider that we have a type with validations. Use hint "interface" or "noValidation" to disable validations 779 result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 780 result.IsNullable = isRequired 781 782 result.setKind(extType.Hints.Kind) 783 if result.IsInterface || result.IsStream { 784 result.IsNullable = false 785 } 786 if extType.Hints.Nullable != nil { 787 result.IsNullable = swag.BoolValue(extType.Hints.Nullable) 788 } 789 790 if nullable, ok := t.isNullableOverride(schema); ok { 791 result.IsNullable = nullable // x-nullable directive rules them all 792 } 793 794 // other extensions 795 if result.IsArray { 796 result.IsEmptyOmitted = false 797 tpe = "array" 798 } 799 800 result.setExtensions(schema, tpe) 801 return result 802 } 803 804 func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) { 805 debugLog("resolving schema (anon: %t, req: %t) %s", isAnonymous, isRequired, t.ModelName) 806 defer func() { 807 debugLog("returning after resolve schema: %s", pretty.Sprint(result)) 808 }() 809 810 if schema == nil { 811 result.IsInterface = true 812 result.GoType = iface 813 return 814 } 815 816 extType, isExternalType := t.resolveExternalType(schema.Extensions) 817 if isExternalType { 818 tpe, pkg, alias := t.knownDefGoType(t.ModelName, *schema, t.goTypeName) 819 debugLog("found type %s declared as external, imported from %s as %s. Has type hints? %t, rendered has embedded? %t", 820 t.ModelName, pkg, tpe, extType.Hints.Kind != "", extType.Embedded) 821 822 if extType.Hints.Kind != "" && !extType.Embedded { 823 // use hint to qualify type 824 debugLog("short circuits external type resolution with hint for %s", tpe) 825 result = t.shortCircuitResolveExternal(tpe, pkg, alias, extType, schema, isRequired) 826 result.IsExternal = isAnonymous // mark anonymous external types only, not definitions 827 return 828 } 829 830 // use spec to qualify type 831 debugLog("marking type %s as external embedded: %t", tpe, extType.Embedded) 832 defer func() { // enforce bubbling up decisions taken about being an external type 833 // mark this type as an embedded external definition if requested 834 result.IsEmbedded = extType.Embedded 835 result.IsExternal = isAnonymous // for non-embedded, mark anonymous external types only, not definitions 836 837 result.IsAnonymous = false 838 result.IsAliased = true 839 result.IsNullable = isRequired 840 if extType.Hints.Nullable != nil { 841 result.IsNullable = swag.BoolValue(extType.Hints.Nullable) 842 } 843 844 result.IsMap = false 845 result.AliasedType = result.GoType 846 result.IsInterface = false 847 848 if result.IsEmbedded { 849 result.ElemType = &resolvedType{ 850 IsExternal: isAnonymous, // mark anonymous external types only, not definitions 851 IsInterface: false, 852 Pkg: extType.Import.Package, 853 PkgAlias: extType.Import.Alias, 854 SkipExternalValidation: swag.BoolValue(extType.Hints.NoValidation), 855 } 856 if extType.Import.Alias != "" { 857 result.ElemType.GoType = extType.Import.Alias + "." + extType.Type 858 } else { 859 result.ElemType.GoType = extType.Type 860 } 861 result.ElemType.setKind(extType.Hints.Kind) 862 if result.IsInterface || result.IsStream { 863 result.ElemType.IsNullable = false 864 } 865 if extType.Hints.Nullable != nil { 866 result.ElemType.IsNullable = swag.BoolValue(extType.Hints.Nullable) 867 } 868 // embedded external: by default consider validation is skipped for the external type 869 // 870 // NOTE: at this moment the template generates a type assertion, so this setting does not really matter 871 // for embedded types. 872 if extType.Hints.NoValidation != nil { 873 result.ElemType.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 874 } else { 875 result.ElemType.SkipExternalValidation = true 876 } 877 } else { 878 // non-embedded external type: by default consider that validation is enabled (SkipExternalValidation: false) 879 result.SkipExternalValidation = swag.BoolValue(extType.Hints.NoValidation) 880 } 881 882 if nullable, ok := t.isNullableOverride(schema); ok { 883 result.IsNullable = nullable 884 } 885 }() 886 } 887 888 tpe := t.firstType(schema) 889 var returns bool 890 891 guardValidations(tpe, schema, schema.Type...) 892 893 returns, result, err = t.resolveSchemaRef(schema, isRequired) 894 895 if returns { 896 if !isAnonymous { 897 result.IsMap = false 898 result.IsComplexObject = true 899 } 900 901 return 902 } 903 904 defer func() { 905 result.setExtensions(schema, tpe) 906 }() 907 908 // special case of swagger type "file", rendered as io.ReadCloser interface 909 if t.firstType(schema) == file { 910 result.SwaggerType = file 911 result.IsPrimitive = true 912 result.IsNullable = false 913 result.GoType = formatMapping[str][binary] 914 result.IsStream = true 915 return 916 } 917 918 returns, result, err = t.resolveFormat(schema, isAnonymous, isRequired) 919 if returns { 920 return 921 } 922 923 result.IsNullable = t.isNullable(schema) || isRequired 924 925 switch tpe { 926 case array: 927 result, err = t.resolveArray(schema, isAnonymous, false) 928 929 case file, number, integer, boolean: 930 result.Extensions = schema.Extensions 931 result.GoType = typeMapping[tpe] 932 result.SwaggerType = tpe 933 t.inferAliasing(&result, schema, isAnonymous, isRequired) 934 935 switch tpe { 936 case boolean: 937 result.IsPrimitive = true 938 result.IsCustomFormatter = false 939 result.IsNullable = nullableBool(schema, isRequired) 940 case number, integer: 941 result.IsPrimitive = true 942 result.IsCustomFormatter = false 943 result.IsNullable = nullableNumber(schema, isRequired) 944 case file: 945 } 946 947 case str: 948 result.GoType = str 949 result.SwaggerType = str 950 t.inferAliasing(&result, schema, isAnonymous, isRequired) 951 952 result.IsPrimitive = true 953 result.IsNullable = nullableString(schema, isRequired) 954 result.Extensions = schema.Extensions 955 956 case object: 957 result, err = t.resolveObject(schema, isAnonymous) 958 if err != nil { 959 result = resolvedType{} 960 break 961 } 962 result.HasDiscriminator = schema.Discriminator != "" 963 964 case "null": 965 if schema.Validations().HasObjectValidations() { 966 // no explicit object type, but inferred from object validations: 967 // this makes the type a map[string]interface{} instead of interface{} 968 result, err = t.resolveObject(schema, isAnonymous) 969 if err != nil { 970 result = resolvedType{} 971 break 972 } 973 result.HasDiscriminator = schema.Discriminator != "" 974 break 975 } 976 977 result.GoType = iface 978 result.SwaggerType = object 979 result.IsNullable = false 980 result.IsInterface = true 981 982 default: 983 err = fmt.Errorf("unresolvable: %v (format %q)", schema.Type, schema.Format) 984 } 985 986 return 987 } 988 989 func warnSkipValidation(types interface{}) func(string, interface{}) { 990 return func(validation string, value interface{}) { 991 value = reflect.Indirect(reflect.ValueOf(value)).Interface() 992 log.Printf("warning: validation %s (value: %v) not compatible with type %v. Skipped", validation, value, types) 993 } 994 } 995 996 // guardValidations removes (with a warning) validations that don't fit with the schema type. 997 // 998 // Notice that the "enum" validation is allowed on any type but file. 999 func guardValidations(tpe string, schema interface { 1000 Validations() spec.SchemaValidations 1001 SetValidations(spec.SchemaValidations) 1002 }, types ...string) { 1003 1004 v := schema.Validations() 1005 if len(types) == 0 { 1006 types = []string{tpe} 1007 } 1008 defer func() { 1009 schema.SetValidations(v) 1010 }() 1011 1012 if tpe != array { 1013 v.ClearArrayValidations(warnSkipValidation(types)) 1014 } 1015 1016 if tpe != str && tpe != file { 1017 v.ClearStringValidations(warnSkipValidation(types)) 1018 } 1019 1020 if tpe != object { 1021 v.ClearObjectValidations(warnSkipValidation(types)) 1022 } 1023 1024 if tpe != number && tpe != integer { 1025 v.ClearNumberValidations(warnSkipValidation(types)) 1026 } 1027 1028 if tpe == file { 1029 // keep MinLength/MaxLength on file 1030 if v.Pattern != "" { 1031 warnSkipValidation(types)("pattern", v.Pattern) 1032 v.Pattern = "" 1033 } 1034 if v.HasEnum() { 1035 warnSkipValidation(types)("enum", v.Enum) 1036 v.Enum = nil 1037 } 1038 } 1039 1040 // other cases: mapped as interface{}: no validations allowed but Enum 1041 } 1042 1043 // guardFormatConflicts handles all conflicting properties 1044 // (for schema model or simple schema) when a format is set. 1045 // 1046 // At this moment, validation guards already handle all known conflicts, but for the 1047 // special case of binary (i.e. io.Reader). 1048 func guardFormatConflicts(format string, schema interface { 1049 Validations() spec.SchemaValidations 1050 SetValidations(spec.SchemaValidations) 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, tpe 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 }