github.com/go-swagger/go-swagger@v0.31.0/generator/model.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 "errors" 19 "fmt" 20 "log" 21 "path" 22 "path/filepath" 23 "sort" 24 "strconv" 25 "strings" 26 27 "github.com/go-openapi/analysis" 28 "github.com/go-openapi/loads" 29 "github.com/go-openapi/spec" 30 "github.com/go-openapi/swag" 31 ) 32 33 const asMethod = "()" 34 35 /* 36 Rewrite specification document first: 37 38 * anonymous objects 39 * tuples 40 * extensible objects (properties + additionalProperties) 41 * AllOfs when they match the rewrite criteria (not a nullable allOf) 42 43 Find string enums and generate specialized idiomatic enum with them 44 45 Every action that happens tracks the path which is a linked list of refs 46 47 48 */ 49 50 // GenerateModels generates all model files for some schema definitions 51 func GenerateModels(modelNames []string, opts *GenOpts) error { 52 // overide any default or incompatible options setting 53 opts.IncludeModel = true 54 opts.IgnoreOperations = true 55 opts.ExistingModels = "" 56 opts.IncludeHandler = false 57 opts.IncludeMain = false 58 opts.IncludeSupport = false 59 generator, err := newAppGenerator("", modelNames, nil, opts) 60 if err != nil { 61 return err 62 } 63 return generator.Generate() 64 } 65 66 // GenerateDefinition generates a single model file for some schema definitions 67 func GenerateDefinition(modelNames []string, opts *GenOpts) error { 68 if err := opts.CheckOpts(); err != nil { 69 return err 70 } 71 72 if err := opts.setTemplates(); err != nil { 73 return err 74 } 75 76 specDoc, _, err := opts.analyzeSpec() 77 if err != nil { 78 return err 79 } 80 81 modelNames = pruneEmpty(modelNames) 82 if len(modelNames) == 0 { 83 for k := range specDoc.Spec().Definitions { 84 modelNames = append(modelNames, k) 85 } 86 } 87 88 for _, modelName := range modelNames { 89 // lookup schema 90 model, ok := specDoc.Spec().Definitions[modelName] 91 if !ok { 92 return fmt.Errorf("model %q not found in definitions given by %q", modelName, opts.Spec) 93 } 94 95 // generate files 96 generator := definitionGenerator{ 97 Name: modelName, 98 Model: model, 99 SpecDoc: specDoc, 100 Target: filepath.Join( 101 opts.Target, 102 filepath.FromSlash(opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, ""))), 103 opts: opts, 104 } 105 106 if err := generator.Generate(); err != nil { 107 return err 108 } 109 } 110 111 return nil 112 } 113 114 type definitionGenerator struct { 115 Name string 116 Model spec.Schema 117 SpecDoc *loads.Document 118 Target string 119 opts *GenOpts 120 } 121 122 func (m *definitionGenerator) Generate() error { 123 mod, err := makeGenDefinition(m.Name, m.Target, m.Model, m.SpecDoc, m.opts) 124 if err != nil { 125 return fmt.Errorf("could not generate definitions for model %s on target %s: %w", m.Name, m.Target, err) 126 } 127 128 if m.opts.DumpData { 129 return dumpData(swag.ToDynamicJSON(mod)) 130 } 131 132 if m.opts.IncludeModel { 133 log.Println("including additional model") 134 if err := m.generateModel(mod); err != nil { 135 return fmt.Errorf("could not generate model: %w", err) 136 } 137 } 138 log.Println("generated model", m.Name) 139 140 return nil 141 } 142 143 func (m *definitionGenerator) generateModel(g *GenDefinition) error { 144 debugLog("rendering definitions for %+v", *g) 145 return m.opts.renderDefinition(g) 146 } 147 148 func makeGenDefinition(name, pkg string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) { 149 gd, err := makeGenDefinitionHierarchy(name, pkg, "", schema, specDoc, opts) 150 151 if err == nil && gd != nil { 152 // before yielding the schema to the renderer, we check if the top-level Validate method gets some content 153 // this means that the immediate content of the top level definitions has at least one validation. 154 // 155 // If none is found at this level and that no special case where no Validate() method is exposed at all 156 // (e.g. io.ReadCloser and interface{} types and their aliases), then there is an empty Validate() method which 157 // just return nil (the object abides by the runtime.Validatable interface, but knows it has nothing to validate). 158 // 159 // We do this at the top level because of the possibility of aliased types which always bubble up validation to types which 160 // are referring to them. This results in correct but inelegant code with empty validations. 161 gd.GenSchema.HasValidations = shallowValidationLookup(gd.GenSchema) 162 } 163 return gd, err 164 } 165 166 func shallowValidationLookup(sch GenSchema) bool { 167 // scan top level need for validations 168 // 169 // NOTE: this supersedes the previous NeedsValidation flag 170 // With the introduction of this shallow lookup, it is no more necessary 171 // to establish a distinction between HasValidations (e.g. carries on validations) 172 // and NeedsValidation (e.g. should have a Validate method with something in it). 173 // The latter was almost not used anyhow. 174 175 if sch.HasAdditionalProperties && sch.AdditionalProperties == nil { 176 log.Printf("warning: schema for additional properties in schema %q is empty. skipped", sch.Name) 177 } 178 179 if sch.IsArray && sch.HasValidations { 180 return true 181 } 182 if sch.IsStream || sch.IsInterface { // these types have no validation - aliased types on those do not implement the Validatable interface 183 return false 184 } 185 if sch.Required || hasFormatValidation(sch.resolvedType) { 186 return true 187 } 188 if sch.HasStringValidations() || sch.HasNumberValidations() || sch.HasEnum() || len(sch.ItemsEnum) > 0 || sch.HasObjectValidations() { 189 return true 190 } 191 for _, a := range sch.AllOf { 192 if a.HasValidations { 193 return true 194 } 195 } 196 for _, p := range sch.Properties { 197 // Using a base type within another structure triggers validation of the base type. 198 // The discriminator property in the base type definition itself does not. 199 if (p.HasValidations || p.Required) && !(sch.IsBaseType && p.Name == sch.DiscriminatorField) || (p.IsAliased || p.IsComplexObject) && !(p.IsInterface || p.IsStream) { 200 return true 201 } 202 } 203 if sch.IsTuple && (sch.AdditionalItems != nil && (sch.AdditionalItems.HasValidations || sch.AdditionalItems.Required)) { 204 return true 205 } 206 if sch.HasAdditionalProperties && sch.AdditionalProperties != nil && (sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream) { 207 return false 208 } 209 210 if sch.HasAdditionalProperties && sch.AdditionalProperties != nil && (sch.AdditionalProperties.HasValidations || sch.AdditionalProperties.Required || sch.AdditionalProperties.IsAliased && !(sch.AdditionalProperties.IsInterface || sch.AdditionalProperties.IsStream)) { 211 return true 212 } 213 214 if sch.IsAliased && (sch.IsPrimitive && sch.HasValidations) { // non primitive aliased have either other attributes with validation (above) or shall not validate 215 return true 216 } 217 if sch.HasBaseType || sch.IsSubType { 218 return true 219 } 220 return false 221 } 222 223 func isExternal(schema spec.Schema) bool { 224 extType, ok := hasExternalType(schema.Extensions) 225 return ok && !extType.Embedded 226 } 227 228 func makeGenDefinitionHierarchy(name, pkg, container string, schema spec.Schema, specDoc *loads.Document, opts *GenOpts) (*GenDefinition, error) { 229 // Check if model is imported from external package using x-go-type 230 receiver := "m" 231 // models are resolved in the current package 232 modelPkg := opts.LanguageOpts.ManglePackageName(path.Base(filepath.ToSlash(pkg)), "definitions") 233 resolver := newTypeResolver("", "", specDoc).withDefinitionPackage(modelPkg) 234 resolver.ModelName = name 235 analyzed := analysis.New(specDoc.Spec()) 236 237 di := discriminatorInfo(analyzed) 238 239 pg := schemaGenContext{ 240 Path: "", 241 Name: name, 242 Receiver: receiver, 243 IndexVar: "i", 244 ValueExpr: receiver, 245 Schema: schema, 246 Required: false, 247 TypeResolver: resolver, 248 Named: true, 249 ExtraSchemas: make(map[string]GenSchema), 250 Discrimination: di, 251 Container: container, 252 IncludeValidator: opts.IncludeValidator, 253 IncludeModel: opts.IncludeModel, 254 StrictAdditionalProperties: opts.StrictAdditionalProperties, 255 WithXML: opts.WithXML, 256 StructTags: opts.StructTags, 257 WantsRootedErrorPath: opts.WantsRootedErrorPath, 258 } 259 if err := pg.makeGenSchema(); err != nil { 260 return nil, fmt.Errorf("could not generate schema for %s: %w", name, err) 261 } 262 dsi, ok := di.Discriminators["#/definitions/"+name] 263 if ok { 264 // when these 2 are true then the schema will render as an interface 265 pg.GenSchema.IsBaseType = true 266 pg.GenSchema.IsExported = true 267 pg.GenSchema.DiscriminatorField = dsi.FieldName 268 269 if pg.GenSchema.Discriminates == nil { 270 pg.GenSchema.Discriminates = make(map[string]string) 271 } 272 pg.GenSchema.Discriminates[name] = dsi.GoType 273 pg.GenSchema.DiscriminatorValue = name 274 275 for _, v := range dsi.Children { 276 pg.GenSchema.Discriminates[v.FieldValue] = v.GoType 277 } 278 279 for j := range pg.GenSchema.Properties { 280 if !strings.HasSuffix(pg.GenSchema.Properties[j].ValueExpression, asMethod) { 281 pg.GenSchema.Properties[j].ValueExpression += asMethod 282 } 283 } 284 } 285 286 dse, ok := di.Discriminated["#/definitions/"+name] 287 if ok { 288 pg.GenSchema.DiscriminatorField = dse.FieldName 289 pg.GenSchema.DiscriminatorValue = dse.FieldValue 290 pg.GenSchema.IsSubType = true 291 knownProperties := make(map[string]struct{}) 292 293 // find the referenced definitions 294 // check if it has a discriminator defined 295 // when it has a discriminator get the schema and run makeGenSchema for it. 296 // replace the ref with this new genschema 297 swsp := specDoc.Spec() 298 for i, ss := range schema.AllOf { 299 if pg.GenSchema.AllOf == nil { 300 log.Printf("warning: resolved schema for subtype %q.AllOf[%d] is empty. skipped", name, i) 301 continue 302 } 303 ref := ss.Ref 304 for ref.String() != "" { 305 var rsch *spec.Schema 306 var err error 307 rsch, err = spec.ResolveRef(swsp, &ref) 308 if err != nil { 309 return nil, err 310 } 311 if rsch != nil && rsch.Ref.String() != "" { 312 ref = rsch.Ref 313 continue 314 } 315 ref = spec.Ref{} 316 if rsch != nil && rsch.Discriminator != "" { 317 gs, err := makeGenDefinitionHierarchy(strings.TrimPrefix(ss.Ref.String(), "#/definitions/"), pkg, pg.GenSchema.Name, *rsch, specDoc, opts) 318 if err != nil { 319 return nil, err 320 } 321 gs.GenSchema.IsBaseType = true 322 gs.GenSchema.IsExported = true 323 pg.GenSchema.AllOf[i] = gs.GenSchema 324 schPtr := &(pg.GenSchema.AllOf[i]) 325 if schPtr.AdditionalItems != nil { 326 schPtr.AdditionalItems.IsBaseType = true 327 } 328 if schPtr.AdditionalProperties != nil { 329 schPtr.AdditionalProperties.IsBaseType = true 330 } 331 for j := range schPtr.Properties { 332 schPtr.Properties[j].IsBaseType = true 333 knownProperties[schPtr.Properties[j].Name] = struct{}{} 334 } 335 } 336 } 337 } 338 339 // dedupe the fields 340 alreadySeen := make(map[string]struct{}) 341 for i, ss := range pg.GenSchema.AllOf { 342 var remainingProperties GenSchemaList 343 for _, p := range ss.Properties { 344 if _, ok := knownProperties[p.Name]; !ok || ss.IsBaseType { 345 if _, seen := alreadySeen[p.Name]; !seen { 346 remainingProperties = append(remainingProperties, p) 347 alreadySeen[p.Name] = struct{}{} 348 } 349 } 350 } 351 pg.GenSchema.AllOf[i].Properties = remainingProperties 352 } 353 354 } 355 356 defaultImports := map[string]string{ 357 "errors": "github.com/go-openapi/errors", 358 "runtime": "github.com/go-openapi/runtime", 359 "swag": "github.com/go-openapi/swag", 360 "validate": "github.com/go-openapi/validate", 361 "strfmt": "github.com/go-openapi/strfmt", 362 } 363 364 return &GenDefinition{ 365 GenCommon: GenCommon{ 366 Copyright: opts.Copyright, 367 TargetImportPath: opts.LanguageOpts.baseImport(opts.Target), 368 }, 369 Package: modelPkg, 370 GenSchema: pg.GenSchema, 371 DependsOn: pg.Dependencies, 372 DefaultImports: defaultImports, 373 ExtraSchemas: gatherExtraSchemas(pg.ExtraSchemas), 374 Imports: findImports(&pg.GenSchema), 375 External: isExternal(schema), 376 }, nil 377 } 378 379 func findImports(sch *GenSchema) map[string]string { 380 imp := make(map[string]string, 20) 381 t := sch.resolvedType 382 if t.Pkg != "" && t.PkgAlias != "" { 383 imp[t.PkgAlias] = t.Pkg 384 } 385 if t.IsEmbedded && t.ElemType != nil { 386 if t.ElemType.Pkg != "" && t.ElemType.PkgAlias != "" { 387 imp[t.ElemType.PkgAlias] = t.ElemType.Pkg 388 } 389 } 390 if sch.Items != nil { 391 sub := findImports(sch.Items) 392 for k, v := range sub { 393 imp[k] = v 394 } 395 } 396 if sch.AdditionalItems != nil { 397 sub := findImports(sch.AdditionalItems) 398 for k, v := range sub { 399 imp[k] = v 400 } 401 } 402 if sch.Object != nil { 403 sub := findImports(sch.Object) 404 for k, v := range sub { 405 imp[k] = v 406 } 407 } 408 if sch.Properties != nil { 409 for _, props := range sch.Properties { 410 p := props 411 sub := findImports(&p) 412 for k, v := range sub { 413 imp[k] = v 414 } 415 } 416 } 417 if sch.AdditionalProperties != nil { 418 sub := findImports(sch.AdditionalProperties) 419 for k, v := range sub { 420 imp[k] = v 421 } 422 } 423 if sch.AllOf != nil { 424 for _, props := range sch.AllOf { 425 p := props 426 sub := findImports(&p) 427 for k, v := range sub { 428 imp[k] = v 429 } 430 } 431 } 432 for k, v := range sch.ExtraImports { 433 if k != "" && v != "" { 434 imp[k] = v 435 } 436 } 437 438 return imp 439 } 440 441 type schemaGenContext struct { 442 Required bool 443 AdditionalProperty bool 444 Untyped bool 445 Named bool 446 IsVirtual bool 447 IsTuple bool 448 IncludeValidator bool 449 IncludeModel bool 450 StrictAdditionalProperties bool 451 WantsRootedErrorPath bool 452 WithXML bool 453 Index int 454 455 Path string 456 Name string 457 ParamName string 458 Accessor string 459 Receiver string 460 IndexVar string 461 KeyVar string 462 ValueExpr string 463 Container string 464 Schema spec.Schema 465 TypeResolver *typeResolver 466 StructTags []string 467 468 GenSchema GenSchema 469 Dependencies []string // NOTE: Dependencies is actually set nowhere 470 ExtraSchemas map[string]GenSchema 471 Discriminator *discor 472 Discriminated *discee 473 Discrimination *discInfo 474 475 // force to use container in inlined definitions (for deconflicting) 476 UseContainerInName bool 477 // indicates is the schema is part of a slice or a map 478 IsElem bool 479 // indicates is the schema is part of a struct 480 IsProperty bool 481 } 482 483 func (sg *schemaGenContext) NewSliceBranch(schema *spec.Schema) *schemaGenContext { 484 debugLog("new slice branch %s (model: %s)", sg.Name, sg.TypeResolver.ModelName) 485 pg := sg.shallowClone() 486 indexVar := pg.IndexVar 487 if pg.Path == "" { 488 pg.Path = "strconv.Itoa(" + indexVar + ")" 489 } else { 490 pg.Path = pg.Path + "+ \".\" + strconv.Itoa(" + indexVar + ")" 491 } 492 // check who is parent, if it's a base type then rewrite the value expression 493 if sg.Discrimination != nil && sg.Discrimination.Discriminators != nil { 494 _, rewriteValueExpr := sg.Discrimination.Discriminators["#/definitions/"+sg.TypeResolver.ModelName] 495 if (pg.IndexVar == "i" && rewriteValueExpr) || sg.GenSchema.ElemType.IsBaseType { 496 if !sg.GenSchema.IsAliased { 497 pg.ValueExpr = sg.Receiver + "." + swag.ToJSONName(sg.GenSchema.Name) + "Field" 498 } else { 499 pg.ValueExpr = sg.Receiver 500 } 501 } 502 } 503 sg.GenSchema.IsBaseType = sg.GenSchema.ElemType.HasDiscriminator 504 pg.IndexVar = indexVar + "i" 505 pg.ValueExpr = pg.ValueExpr + "[" + indexVar + "]" 506 pg.Schema = *schema 507 pg.Required = false 508 pg.IsElem = true 509 if sg.IsVirtual { 510 pg.TypeResolver = sg.TypeResolver.NewWithModelName(sg.TypeResolver.ModelName) 511 } 512 513 // when this is an anonymous complex object, this needs to become a ref 514 return pg 515 } 516 517 func (sg *schemaGenContext) NewAdditionalItems(schema *spec.Schema) *schemaGenContext { 518 debugLog("new additional items\n") 519 520 pg := sg.shallowClone() 521 indexVar := pg.IndexVar 522 pg.Name = sg.Name + " items" 523 itemsLen := 0 524 if sg.Schema.Items != nil { 525 itemsLen = sg.Schema.Items.Len() 526 } 527 var mod string 528 if itemsLen > 0 { 529 mod = "+" + strconv.Itoa(itemsLen) 530 } 531 if pg.Path == "" { 532 pg.Path = "strconv.Itoa(" + indexVar + mod + ")" 533 } else { 534 pg.Path = pg.Path + "+ \".\" + strconv.Itoa(" + indexVar + mod + ")" 535 } 536 pg.IndexVar = indexVar 537 pg.ValueExpr = sg.ValueExpr + "." + pascalize(sg.GoName()) + "Items[" + indexVar + "]" 538 pg.Schema = spec.Schema{} 539 if schema != nil { 540 pg.Schema = *schema 541 } 542 pg.Required = false 543 return pg 544 } 545 546 func (sg *schemaGenContext) NewTupleElement(schema *spec.Schema, index int) *schemaGenContext { 547 debugLog("New tuple element\n") 548 549 pg := sg.shallowClone() 550 if pg.Path == "" { 551 pg.Path = "\"" + strconv.Itoa(index) + "\"" 552 } else { 553 pg.Path = pg.Path + "+ \".\"+\"" + strconv.Itoa(index) + "\"" 554 } 555 pg.ValueExpr = pg.ValueExpr + ".P" + strconv.Itoa(index) 556 557 pg.Required = true 558 pg.IsTuple = true 559 pg.Schema = *schema 560 561 return pg 562 } 563 564 func (sg *schemaGenContext) NewStructBranch(name string, schema spec.Schema) *schemaGenContext { 565 debugLog("new struct branch %s (parent %s)", sg.Name, sg.Container) 566 pg := sg.shallowClone() 567 if sg.Path == "" { 568 pg.Path = fmt.Sprintf("%q", name) 569 } else { 570 pg.Path = pg.Path + "+\".\"+" + fmt.Sprintf("%q", name) 571 } 572 pg.Name = name 573 pg.ValueExpr = pg.ValueExpr + "." + pascalize(goName(&schema, name)) 574 pg.Schema = schema 575 pg.IsProperty = true 576 for _, fn := range sg.Schema.Required { 577 if name == fn { 578 pg.Required = true 579 break 580 } 581 } 582 debugLog("made new struct branch %s (parent %s)", pg.Name, pg.Container) 583 return pg 584 } 585 586 func (sg *schemaGenContext) shallowClone() *schemaGenContext { 587 debugLog("cloning context %s\n", sg.Name) 588 pg := new(schemaGenContext) 589 *pg = *sg 590 if pg.Container == "" { 591 pg.Container = sg.Name 592 } 593 pg.GenSchema = GenSchema{StructTags: sg.StructTags} 594 pg.Dependencies = nil 595 pg.Named = false 596 pg.Index = 0 597 pg.IsTuple = false 598 pg.IncludeValidator = sg.IncludeValidator 599 pg.IncludeModel = sg.IncludeModel 600 pg.StrictAdditionalProperties = sg.StrictAdditionalProperties 601 return pg 602 } 603 604 func (sg *schemaGenContext) NewCompositionBranch(schema spec.Schema, index int) *schemaGenContext { 605 debugLog("new composition branch %s (parent: %s, index: %d)", sg.Name, sg.Container, index) 606 pg := sg.shallowClone() 607 pg.Schema = schema 608 pg.Name = "AO" + strconv.Itoa(index) 609 if sg.Name != sg.TypeResolver.ModelName { 610 pg.Name = sg.Name + pg.Name 611 } 612 pg.Index = index 613 debugLog("made new composition branch %s (parent: %s)", pg.Name, pg.Container) 614 return pg 615 } 616 617 func (sg *schemaGenContext) NewAdditionalProperty(schema spec.Schema) *schemaGenContext { 618 debugLog("new additional property %s (expr: %s)", sg.Name, sg.ValueExpr) 619 pg := sg.shallowClone() 620 pg.Schema = schema 621 if pg.KeyVar == "" { 622 pg.ValueExpr = sg.ValueExpr 623 } 624 pg.KeyVar += "k" 625 pg.ValueExpr += "[" + pg.KeyVar + "]" 626 pg.Path = pg.KeyVar 627 pg.GenSchema.Suffix = "Value" 628 if sg.Path != "" { 629 pg.Path = sg.Path + "+\".\"+" + pg.KeyVar 630 } 631 pg.IsElem = true 632 // propagates the special IsNullable override for maps of slices and 633 // maps of aliased types. 634 pg.GenSchema.IsMapNullOverride = sg.GenSchema.IsMapNullOverride 635 return pg 636 } 637 638 func hasContextValidations(model *spec.Schema) bool { 639 // always assume ref needs context validate 640 // TODO: find away to determine ref needs context validate or not 641 if model.ReadOnly || model.Ref.String() != "" { 642 return true 643 } 644 return false 645 } 646 647 func hasValidations(model *spec.Schema, isRequired bool) bool { 648 if isRequired { 649 return true 650 } 651 652 v := model.Validations() 653 if v.HasNumberValidations() || v.HasStringValidations() || v.HasArrayValidations() || v.HasEnum() || v.HasObjectValidations() { 654 return true 655 } 656 657 // since this was added to deal with discriminator, we'll fix this when testing discriminated types 658 if len(model.Properties) > 0 && model.Discriminator == "" { 659 return true 660 } 661 662 // lift validations from allOf branches 663 for _, s := range model.AllOf { 664 schema := s 665 if s.Ref.String() != "" || hasValidations(&schema, false) { 666 return true 667 } 668 } 669 670 return false 671 } 672 673 func hasFormatValidation(tpe resolvedType) bool { 674 if tpe.IsCustomFormatter && !tpe.IsStream && !tpe.IsBase64 { 675 return true 676 } 677 if tpe.IsArray && tpe.ElemType != nil { 678 return hasFormatValidation(*tpe.ElemType) 679 } 680 return false 681 } 682 683 func (sg *schemaGenContext) schemaValidations() sharedValidations { 684 model := sg.Schema 685 686 isRequired := sg.Required 687 if model.Default != nil || model.ReadOnly { 688 // when readOnly or default is specified, this disables Required validation (Swagger-specific) 689 isRequired = false 690 if sg.Required { 691 log.Printf("warning: properties with a default value or readOnly should not be required [%s]", sg.Name) 692 } 693 } 694 695 v := model.Validations() 696 return sharedValidations{ 697 Required: sg.Required, /* TODO(fred): guard for cases with discriminator field, default and readOnly*/ 698 SchemaValidations: v, 699 HasSliceValidations: v.HasArrayValidations() || v.HasEnum(), 700 HasValidations: hasValidations(&model, isRequired), 701 } 702 } 703 704 func mergeValidation(other *schemaGenContext) bool { 705 // NOTE: NeesRequired and NeedsValidation are deprecated 706 if other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.HasValidations { 707 return true 708 } 709 if other.GenSchema.AdditionalItems != nil && other.GenSchema.AdditionalItems.HasValidations { 710 return true 711 } 712 for _, sch := range other.GenSchema.AllOf { 713 if sch.HasValidations { 714 return true 715 } 716 } 717 return other.GenSchema.HasValidations 718 } 719 720 func (sg *schemaGenContext) MergeResult(other *schemaGenContext, liftsRequired bool) { 721 sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || mergeValidation(other) 722 sg.GenSchema.HasContextValidations = sg.GenSchema.HasContextValidations || other.GenSchema.HasContextValidations 723 724 if liftsRequired && other.GenSchema.AdditionalProperties != nil && other.GenSchema.AdditionalProperties.Required { 725 sg.GenSchema.Required = true 726 } 727 if liftsRequired && other.GenSchema.Required { 728 sg.GenSchema.Required = other.GenSchema.Required 729 } 730 731 if other.GenSchema.HasBaseType { 732 sg.GenSchema.HasBaseType = other.GenSchema.HasBaseType 733 } 734 735 sg.Dependencies = append(sg.Dependencies, other.Dependencies...) 736 737 // lift extra schemas 738 for k, v := range other.ExtraSchemas { 739 sg.ExtraSchemas[k] = v 740 } 741 if other.GenSchema.IsMapNullOverride { 742 sg.GenSchema.IsMapNullOverride = true 743 } 744 745 // lift extra imports 746 if other.GenSchema.Pkg != "" && other.GenSchema.PkgAlias != "" { 747 sg.GenSchema.ExtraImports[other.GenSchema.PkgAlias] = other.GenSchema.Pkg 748 } 749 for k, v := range other.GenSchema.ExtraImports { 750 sg.GenSchema.ExtraImports[k] = v 751 } 752 } 753 754 func (sg *schemaGenContext) buildProperties() error { 755 debugLog("building properties %s (parent: %s)", sg.Name, sg.Container) 756 757 for k, v := range sg.Schema.Properties { 758 debugLogAsJSON("building property %s[%q] (IsTuple: %t) (IsBaseType: %t) (HasValidations: %t)", 759 sg.Name, k, sg.IsTuple, sg.GenSchema.IsBaseType, sg.GenSchema.HasValidations, v) 760 761 vv := v 762 763 // check if this requires de-anonymizing, if so lift this as a new struct and extra schema 764 tpe, err := sg.TypeResolver.ResolveSchema(&vv, true, sg.IsTuple || swag.ContainsStrings(sg.Schema.Required, k)) 765 if err != nil { 766 return err 767 } 768 if sg.Schema.Discriminator == k { 769 tpe.IsNullable = false 770 } 771 772 var hasValidation bool 773 if tpe.IsComplexObject && tpe.IsAnonymous && len(v.Properties) > 0 { 774 // this is an anonymous complex construct: build a new new type for it 775 pg := sg.makeNewStruct(sg.makeRefName()+swag.ToGoName(k), v) 776 pg.IsTuple = sg.IsTuple 777 if sg.Path != "" { 778 pg.Path = sg.Path + "+ \".\"+" + fmt.Sprintf("%q", k) 779 } else { 780 pg.Path = fmt.Sprintf("%q", k) 781 } 782 if err := pg.makeGenSchema(); err != nil { 783 return err 784 } 785 if v.Discriminator != "" { 786 pg.GenSchema.IsBaseType = true 787 pg.GenSchema.IsExported = true 788 pg.GenSchema.HasBaseType = true 789 } 790 791 vv = *spec.RefProperty("#/definitions/" + pg.Name) 792 hasValidation = pg.GenSchema.HasValidations 793 sg.ExtraSchemas[pg.Name] = pg.GenSchema 794 // NOTE: MergeResult lifts validation status and extra schemas 795 sg.MergeResult(pg, false) 796 } 797 798 emprop := sg.NewStructBranch(k, vv) 799 emprop.IsTuple = sg.IsTuple 800 801 if err := emprop.makeGenSchema(); err != nil { 802 return err 803 } 804 805 // whatever the validations says, if we have an interface{}, do not validate 806 // NOTE: this may be the case when the type is left empty and we get a Enum validation. 807 if emprop.GenSchema.IsInterface || emprop.GenSchema.IsStream { 808 emprop.GenSchema.HasValidations = false 809 } else if hasValidation || emprop.GenSchema.HasValidations || emprop.GenSchema.Required || emprop.GenSchema.IsAliased || len(emprop.GenSchema.AllOf) > 0 { 810 emprop.GenSchema.HasValidations = true 811 sg.GenSchema.HasValidations = true 812 } 813 814 // generates format validation on property 815 emprop.GenSchema.HasValidations = emprop.GenSchema.HasValidations || hasFormatValidation(tpe) 816 817 if emprop.Schema.Ref.String() != "" { 818 // expand the schema of this property, so we take informed decisions about its type 819 ref := emprop.Schema.Ref 820 var sch *spec.Schema 821 for ref.String() != "" { 822 var rsch *spec.Schema 823 var err error 824 specDoc := sg.TypeResolver.Doc 825 rsch, err = spec.ResolveRef(specDoc.Spec(), &ref) 826 if err != nil { 827 return err 828 } 829 if rsch == nil { 830 return errors.New("spec.ResolveRef returned nil schema") 831 } 832 if rsch != nil && rsch.Ref.String() != "" { 833 ref = rsch.Ref 834 continue 835 } 836 ref = spec.Ref{} 837 sch = rsch 838 } 839 840 if emprop.Discrimination != nil { 841 if _, ok := emprop.Discrimination.Discriminators[emprop.Schema.Ref.String()]; ok { 842 emprop.GenSchema.IsBaseType = true 843 emprop.GenSchema.IsNullable = false 844 emprop.GenSchema.HasBaseType = true 845 } 846 if _, ok := emprop.Discrimination.Discriminated[emprop.Schema.Ref.String()]; ok { 847 emprop.GenSchema.IsSubType = true 848 } 849 } 850 851 // set property name 852 nm := filepath.Base(emprop.Schema.Ref.GetURL().Fragment) 853 854 tr := sg.TypeResolver.NewWithModelName(goName(&emprop.Schema, swag.ToGoName(nm))) 855 ttpe, err := tr.ResolveSchema(sch, false, true) 856 if err != nil { 857 return err 858 } 859 if ttpe.IsAliased { 860 emprop.GenSchema.IsAliased = true 861 } 862 863 // lift validations 864 hv := hasValidations(sch, false) 865 866 // include format validation, excluding binary 867 hv = hv || hasFormatValidation(ttpe) 868 869 // a base type property is always validated against the base type 870 // exception: for the base type definition itself (see shallowValidationLookup()) 871 if (hv || emprop.GenSchema.IsBaseType) && !(emprop.GenSchema.IsInterface || emprop.GenSchema.IsStream) { 872 emprop.GenSchema.HasValidations = true 873 } 874 if ttpe.HasAdditionalItems && sch.AdditionalItems.Schema != nil { 875 // when AdditionalItems specifies a Schema, there is a validation 876 // check if we stepped upon an exception 877 child, err := tr.ResolveSchema(sch.AdditionalItems.Schema, false, true) 878 if err != nil { 879 return err 880 } 881 if !child.IsInterface && !child.IsStream { 882 emprop.GenSchema.HasValidations = true 883 } 884 } 885 if ttpe.IsMap && sch.AdditionalProperties != nil && sch.AdditionalProperties.Schema != nil { 886 // when AdditionalProperties specifies a Schema, there is a validation 887 // check if we stepped upon an exception 888 child, err := tr.ResolveSchema(sch.AdditionalProperties.Schema, false, true) 889 if err != nil { 890 return err 891 } 892 if !child.IsInterface && !child.IsStream { 893 emprop.GenSchema.HasValidations = true 894 } 895 } 896 } 897 898 if sg.Schema.Discriminator == k { 899 // this is the discriminator property: 900 // it is required, but forced as non-nullable, 901 // since we never fill it with a zero-value 902 // TODO: when no other property than discriminator, there is no validation 903 emprop.GenSchema.IsNullable = false 904 } 905 if emprop.GenSchema.IsBaseType { 906 sg.GenSchema.HasBaseType = true 907 } 908 sg.MergeResult(emprop, false) 909 910 // when discriminated, data is accessed via a getter func 911 if emprop.GenSchema.HasDiscriminator { 912 emprop.GenSchema.ValueExpression += asMethod 913 } 914 915 emprop.GenSchema.Extensions = emprop.Schema.Extensions 916 917 // set custom serializer tag 918 if customTag, found := tpe.Extensions[xGoCustomTag]; found { 919 tagAsStr, ok := customTag.(string) 920 if ok { 921 emprop.GenSchema.CustomTag = tagAsStr 922 } else { 923 log.Printf("warning: expect %s extension to be a string, got: %v. Skipped", xGoCustomTag, customTag) 924 } 925 } 926 sg.GenSchema.Properties = append(sg.GenSchema.Properties, emprop.GenSchema) 927 } 928 sort.Sort(sg.GenSchema.Properties) 929 930 return nil 931 } 932 933 func (sg *schemaGenContext) buildAllOf() error { 934 if len(sg.Schema.AllOf) == 0 { 935 return nil 936 } 937 938 var hasArray, hasNonArray int 939 940 sort.Sort(sg.GenSchema.AllOf) 941 if sg.Container == "" { 942 sg.Container = sg.Name 943 } 944 debugLogAsJSON("building all of for %d entries", len(sg.Schema.AllOf), sg.Schema) 945 for i, schema := range sg.Schema.AllOf { 946 sch := schema 947 tpe, ert := sg.TypeResolver.ResolveSchema(&sch, sch.Ref.String() == "", false) 948 if ert != nil { 949 return ert 950 } 951 952 // check for multiple arrays in allOf branches. 953 // Although a valid JSON-Schema construct, it is not suited for serialization. 954 // This is the same if we attempt to serialize an array with another object. 955 // We issue a generation warning on this. 956 if tpe.IsArray { 957 hasArray++ 958 } else { 959 hasNonArray++ 960 } 961 debugLogAsJSON("trying", sch) 962 if (tpe.IsAnonymous && len(sch.AllOf) > 0) || (sch.Ref.String() == "" && !tpe.IsComplexObject && (tpe.IsArray || tpe.IsInterface || tpe.IsPrimitive)) { 963 // cases where anonymous structures cause the creation of a new type: 964 // - nested allOf: this one is itself a AllOf: build a new type for it 965 // - anonymous simple types for edge cases: array, primitive, interface{} 966 // NOTE: when branches are aliased or anonymous, the nullable property in the branch type is lost. 967 name := swag.ToVarName(goName(&sch, sg.makeRefName()+"AllOf"+strconv.Itoa(i))) 968 debugLog("building anonymous nested allOf in %s: %s", sg.Name, name) 969 ng := sg.makeNewStruct(name, sch) 970 if err := ng.makeGenSchema(); err != nil { 971 return err 972 } 973 974 newsch := spec.RefProperty("#/definitions/" + ng.Name) 975 sg.Schema.AllOf[i] = *newsch 976 977 pg := sg.NewCompositionBranch(*newsch, i) 978 if err := pg.makeGenSchema(); err != nil { 979 return err 980 } 981 982 // lift extra schemas & validations from new type 983 pg.MergeResult(ng, true) 984 985 // lift validations when complex or ref'ed: 986 // - parent always calls its Validatable child 987 // - child may or may not have validations 988 // 989 // Exception: child is not Validatable when interface or stream 990 if !pg.GenSchema.IsInterface && !pg.GenSchema.IsStream { 991 sg.GenSchema.HasValidations = true 992 } 993 994 // add the newly created type to the list of schemas to be rendered inline 995 pg.ExtraSchemas[ng.Name] = ng.GenSchema 996 997 sg.MergeResult(pg, true) 998 999 sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, pg.GenSchema) 1000 1001 continue 1002 } 1003 1004 comprop := sg.NewCompositionBranch(sch, i) 1005 if err := comprop.makeGenSchema(); err != nil { 1006 return err 1007 } 1008 if comprop.GenSchema.IsMap && comprop.GenSchema.HasAdditionalProperties && comprop.GenSchema.AdditionalProperties != nil && !comprop.GenSchema.IsInterface { 1009 // the anonymous branch is a map for AdditionalProperties: rewrite value expression 1010 comprop.GenSchema.ValueExpression = comprop.GenSchema.ValueExpression + "." + comprop.Name 1011 comprop.GenSchema.AdditionalProperties.ValueExpression = comprop.GenSchema.ValueExpression + "[" + comprop.GenSchema.AdditionalProperties.KeyVar + "]" 1012 } 1013 1014 // lift validations when complex or ref'ed 1015 if (comprop.GenSchema.IsComplexObject || comprop.Schema.Ref.String() != "") && !(comprop.GenSchema.IsInterface || comprop.GenSchema.IsStream) { 1016 comprop.GenSchema.HasValidations = true 1017 } 1018 sg.MergeResult(comprop, true) 1019 sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, comprop.GenSchema) 1020 } 1021 1022 if hasArray > 1 || (hasArray > 0 && hasNonArray > 0) { 1023 log.Printf("warning: cannot generate serializable allOf with conflicting array definitions in %s", sg.Container) 1024 } 1025 1026 // AllOf types are always considered nullable, except when an extension says otherwise 1027 if override, ok := sg.TypeResolver.isNullableOverride(&sg.Schema); ok { 1028 sg.GenSchema.IsNullable = override 1029 } else { 1030 sg.GenSchema.IsNullable = true 1031 } 1032 1033 // prevent IsAliased to bubble up (e.g. when a single branch is itself aliased) 1034 sg.GenSchema.IsAliased = sg.GenSchema.IsAliased && len(sg.GenSchema.AllOf) < 2 1035 1036 return nil 1037 } 1038 1039 type mapStack struct { 1040 Type *spec.Schema 1041 Next *mapStack 1042 Previous *mapStack 1043 ValueRef *schemaGenContext 1044 Context *schemaGenContext 1045 NewObj *schemaGenContext 1046 } 1047 1048 func newMapStack(context *schemaGenContext) (first, last *mapStack, err error) { 1049 ms := &mapStack{ 1050 Type: &context.Schema, 1051 Context: context, 1052 } 1053 1054 l := ms 1055 for l.HasMore() { 1056 tpe, err := l.Context.TypeResolver.ResolveSchema(l.Type.AdditionalProperties.Schema, true, true) 1057 if err != nil { 1058 return nil, nil, err 1059 } 1060 1061 if !tpe.IsMap { 1062 // reached the end of the rabbit hole 1063 if tpe.IsComplexObject && tpe.IsAnonymous { 1064 // found an anonymous object: create the struct from a newly created definition 1065 nw := l.Context.makeNewStruct(l.Context.makeRefName()+" Anon", *l.Type.AdditionalProperties.Schema) 1066 sch := spec.RefProperty("#/definitions/" + nw.Name) 1067 l.NewObj = nw 1068 1069 l.Type.AdditionalProperties.Schema = sch 1070 l.ValueRef = l.Context.NewAdditionalProperty(*sch) 1071 } 1072 1073 // other cases where to stop are: a $ref or a simple object 1074 break 1075 } 1076 1077 // continue digging for maps 1078 l.Next = &mapStack{ 1079 Previous: l, 1080 Type: l.Type.AdditionalProperties.Schema, 1081 Context: l.Context.NewAdditionalProperty(*l.Type.AdditionalProperties.Schema), 1082 } 1083 l = l.Next 1084 } 1085 1086 // return top and bottom entries of this stack of AdditionalProperties 1087 return ms, l, nil 1088 } 1089 1090 // Build rewinds the stack of additional properties, building schemas from bottom to top 1091 func (mt *mapStack) Build() error { 1092 if mt.NewObj == nil && mt.ValueRef == nil && mt.Next == nil && mt.Previous == nil { 1093 csch := mt.Type.AdditionalProperties.Schema 1094 cp := mt.Context.NewAdditionalProperty(*csch) 1095 d := mt.Context.TypeResolver.Doc 1096 1097 asch, err := analysis.Schema(analysis.SchemaOpts{ 1098 Root: d.Spec(), 1099 BasePath: d.SpecFilePath(), 1100 Schema: csch, 1101 }) 1102 if err != nil { 1103 return err 1104 } 1105 cp.Required = !asch.IsSimpleSchema && !asch.IsMap 1106 1107 // when the schema is an array or an alias, this may result in inconsistent 1108 // nullable status between the map element and the array element (resp. the aliased type). 1109 // 1110 // Example: when an object has no property and only additionalProperties, 1111 // which turn out to be arrays of some other object. 1112 1113 // save the initial override 1114 hadOverride := cp.GenSchema.IsMapNullOverride 1115 if err := cp.makeGenSchema(); err != nil { 1116 return err 1117 } 1118 1119 // if we have an override at the top of stack, propagates it down nested arrays 1120 if hadOverride && cp.GenSchema.IsArray { 1121 // do it for nested arrays: override is also about map[string][][]... constructs 1122 it := &cp.GenSchema 1123 for it.Items != nil && it.IsArray { 1124 it.Items.IsMapNullOverride = hadOverride 1125 it = it.Items 1126 } 1127 } 1128 // cover other cases than arrays (aliased types) 1129 cp.GenSchema.IsMapNullOverride = hadOverride 1130 1131 mt.Context.MergeResult(cp, false) 1132 mt.Context.GenSchema.AdditionalProperties = &cp.GenSchema 1133 1134 // lift validations 1135 if (csch.Ref.String() != "" || cp.GenSchema.IsAliased) && !(cp.GenSchema.IsInterface || cp.GenSchema.IsStream) { 1136 // - we stopped on a ref, or anything else that require we call its Validate() method 1137 // - if the alias / ref is on an interface (or stream) type: no validation 1138 mt.Context.GenSchema.HasValidations = true 1139 mt.Context.GenSchema.AdditionalProperties.HasValidations = true 1140 } 1141 1142 debugLog("early mapstack exit, nullable: %t for %s", cp.GenSchema.IsNullable, cp.GenSchema.Name) 1143 return nil 1144 } 1145 cur := mt 1146 for cur != nil { 1147 if cur.NewObj != nil { 1148 // a new model has been created during the stack construction (new ref on anonymous object) 1149 if err := cur.NewObj.makeGenSchema(); err != nil { 1150 return err 1151 } 1152 } 1153 1154 if cur.ValueRef != nil { 1155 if err := cur.ValueRef.makeGenSchema(); err != nil { 1156 return nil 1157 } 1158 } 1159 1160 if cur.NewObj != nil { 1161 // newly created model from anonymous object is declared as extra schema 1162 cur.Context.MergeResult(cur.NewObj, false) 1163 1164 // propagates extra schemas 1165 cur.Context.ExtraSchemas[cur.NewObj.Name] = cur.NewObj.GenSchema 1166 } 1167 1168 if cur.ValueRef != nil { 1169 // this is the genSchema for this new anonymous AdditionalProperty 1170 if err := cur.Context.makeGenSchema(); err != nil { 1171 return err 1172 } 1173 1174 // if there is a ValueRef, we must have a NewObj (from newMapStack() construction) 1175 cur.ValueRef.GenSchema.HasValidations = cur.NewObj.GenSchema.HasValidations 1176 cur.Context.MergeResult(cur.ValueRef, false) 1177 cur.Context.GenSchema.AdditionalProperties = &cur.ValueRef.GenSchema 1178 } 1179 1180 if cur.Previous != nil { 1181 // we have a parent schema: build a schema for current AdditionalProperties 1182 if err := cur.Context.makeGenSchema(); err != nil { 1183 return err 1184 } 1185 } 1186 if cur.Next != nil { 1187 // we previously made a child schema: lifts things from that one 1188 // - Required is not lifted (in a cascade of maps, only the last element is actually checked for Required) 1189 cur.Context.MergeResult(cur.Next.Context, false) 1190 cur.Context.GenSchema.AdditionalProperties = &cur.Next.Context.GenSchema 1191 1192 // lift validations 1193 c := &cur.Next.Context.GenSchema 1194 if (cur.Next.Context.Schema.Ref.String() != "" || c.IsAliased) && !(c.IsInterface || c.IsStream) { 1195 // - we stopped on a ref, or anything else that require we call its Validate() 1196 // - if the alias / ref is on an interface (or stream) type: no validation 1197 cur.Context.GenSchema.HasValidations = true 1198 cur.Context.GenSchema.AdditionalProperties.HasValidations = true 1199 } 1200 } 1201 if cur.ValueRef != nil { 1202 cur.Context.MergeResult(cur.ValueRef, false) 1203 cur.Context.GenSchema.AdditionalProperties = &cur.ValueRef.GenSchema 1204 } 1205 1206 if cur.Context.GenSchema.AdditionalProperties != nil { 1207 // propagate overrides up the resolved schemas, but leaves any ExtraSchema untouched 1208 cur.Context.GenSchema.AdditionalProperties.IsMapNullOverride = cur.Context.GenSchema.IsMapNullOverride 1209 } 1210 cur = cur.Previous 1211 } 1212 1213 return nil 1214 } 1215 1216 func (mt *mapStack) HasMore() bool { 1217 return mt.Type.AdditionalProperties != nil && (mt.Type.AdditionalProperties.Schema != nil || mt.Type.AdditionalProperties.Allows) 1218 } 1219 1220 /* currently unused: 1221 func (mt *mapStack) Dict() map[string]interface{} { 1222 res := make(map[string]interface{}) 1223 res["context"] = mt.Context.Schema 1224 if mt.Next != nil { 1225 res["next"] = mt.Next.Dict() 1226 } 1227 if mt.NewObj != nil { 1228 res["obj"] = mt.NewObj.Schema 1229 } 1230 if mt.ValueRef != nil { 1231 res["value"] = mt.ValueRef.Schema 1232 } 1233 return res 1234 } 1235 */ 1236 1237 func (sg *schemaGenContext) buildAdditionalProperties() error { 1238 if sg.Schema.AdditionalProperties == nil { 1239 if sg.Schema.MinProperties == nil && sg.Schema.MaxProperties == nil { 1240 return nil 1241 } 1242 1243 // whenever there is a validation on min/max properties and no additionalProperties is defined, 1244 // we imply additionalProperties: true (corresponds to jsonschema defaults). 1245 sg.Schema.AdditionalProperties = &spec.SchemaOrBool{Allows: true} 1246 } 1247 addp := *sg.Schema.AdditionalProperties 1248 1249 wantsAdditional := addp.Schema != nil || addp.Allows 1250 sg.GenSchema.HasAdditionalProperties = wantsAdditional 1251 if !wantsAdditional { 1252 return nil 1253 } 1254 1255 // flag swap 1256 if sg.GenSchema.IsComplexObject { 1257 sg.GenSchema.IsAdditionalProperties = true 1258 sg.GenSchema.IsComplexObject = false 1259 sg.GenSchema.IsMap = false 1260 } 1261 1262 if addp.Schema == nil { 1263 // this is for AdditionalProperties:true|false 1264 if addp.Allows { 1265 // additionalProperties: true is rendered as: map[string]interface{} 1266 addp.Schema = &spec.Schema{} 1267 1268 addp.Schema.Typed("object", "") 1269 sg.GenSchema.HasAdditionalProperties = true 1270 sg.GenSchema.IsComplexObject = false 1271 sg.GenSchema.IsMap = true 1272 1273 if !sg.IsElem && !sg.IsProperty { 1274 sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.Name+" additionalProperties") 1275 } 1276 cp := sg.NewAdditionalProperty(*addp.Schema) 1277 cp.Name += "AdditionalProperties" 1278 cp.Required = false 1279 if err := cp.makeGenSchema(); err != nil { 1280 return err 1281 } 1282 sg.MergeResult(cp, false) 1283 sg.GenSchema.AdditionalProperties = &cp.GenSchema 1284 debugLog("added interface{} schema for additionalProperties[allows == true], IsInterface=%t", cp.GenSchema.IsInterface) 1285 } 1286 return nil 1287 } 1288 1289 if !sg.GenSchema.IsMap && (sg.GenSchema.IsAdditionalProperties && sg.Named) { 1290 // we have a complex object with an AdditionalProperties schema 1291 1292 tpe, ert := sg.TypeResolver.ResolveSchema(addp.Schema, addp.Schema.Ref.String() == "", false) 1293 if ert != nil { 1294 return ert 1295 } 1296 1297 if tpe.IsComplexObject && tpe.IsAnonymous { 1298 // if the AdditionalProperties is an anonymous complex object, generate a new type for it 1299 pg := sg.makeNewStruct(sg.makeRefName()+" Anon", *addp.Schema) 1300 if err := pg.makeGenSchema(); err != nil { 1301 return err 1302 } 1303 sg.MergeResult(pg, false) 1304 sg.ExtraSchemas[pg.Name] = pg.GenSchema 1305 1306 sg.Schema.AdditionalProperties.Schema = spec.RefProperty("#/definitions/" + pg.Name) 1307 sg.IsVirtual = true 1308 1309 comprop := sg.NewAdditionalProperty(*sg.Schema.AdditionalProperties.Schema) 1310 if err := comprop.makeGenSchema(); err != nil { 1311 return err 1312 } 1313 1314 comprop.GenSchema.Required = true 1315 comprop.GenSchema.HasValidations = true 1316 1317 comprop.GenSchema.ValueExpression = sg.GenSchema.ValueExpression + "." + swag.ToGoName(sg.GenSchema.Name) + "[" + comprop.KeyVar + "]" 1318 1319 sg.GenSchema.AdditionalProperties = &comprop.GenSchema 1320 sg.GenSchema.HasAdditionalProperties = true 1321 sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.GenSchema.Name) 1322 1323 sg.MergeResult(comprop, false) 1324 1325 return nil 1326 } 1327 1328 // this is a regular named schema for AdditionalProperties 1329 sg.GenSchema.ValueExpression += "." + swag.ToGoName(sg.GenSchema.Name) 1330 comprop := sg.NewAdditionalProperty(*addp.Schema) 1331 d := sg.TypeResolver.Doc 1332 asch, err := analysis.Schema(analysis.SchemaOpts{ 1333 Root: d.Spec(), 1334 BasePath: d.SpecFilePath(), 1335 Schema: addp.Schema, 1336 }) 1337 if err != nil { 1338 return err 1339 } 1340 comprop.Required = !asch.IsSimpleSchema && !asch.IsMap 1341 if err := comprop.makeGenSchema(); err != nil { 1342 return err 1343 } 1344 sg.MergeResult(comprop, false) 1345 sg.GenSchema.AdditionalProperties = &comprop.GenSchema 1346 sg.GenSchema.AdditionalProperties.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]" 1347 1348 // rewrite value expression for arrays and arrays of arrays in maps (rendered as map[string][][]...) 1349 if sg.GenSchema.AdditionalProperties.IsArray { 1350 // maps of slices are where an override may take effect 1351 sg.GenSchema.AdditionalProperties.Items.IsMapNullOverride = sg.GenSchema.AdditionalProperties.IsMapNullOverride 1352 sg.GenSchema.AdditionalProperties.Items.ValueExpression = sg.GenSchema.ValueExpression + "[" + comprop.KeyVar + "]" + "[" + sg.GenSchema.AdditionalProperties.IndexVar + "]" 1353 ap := sg.GenSchema.AdditionalProperties.Items 1354 for ap != nil && ap.IsArray { 1355 ap.Items.IsMapNullOverride = ap.IsMapNullOverride 1356 ap.Items.ValueExpression = ap.ValueExpression + "[" + ap.IndexVar + "]" 1357 ap = ap.Items 1358 } 1359 } 1360 1361 // lift validation 1362 if (sg.GenSchema.AdditionalProperties.IsComplexObject || sg.GenSchema.AdditionalProperties.IsAliased || sg.GenSchema.AdditionalProperties.Required) && !(sg.GenSchema.AdditionalProperties.IsInterface || sg.GenSchema.IsStream) { 1363 sg.GenSchema.HasValidations = true 1364 } 1365 return nil 1366 } 1367 1368 if sg.GenSchema.IsMap && wantsAdditional { 1369 // this is itself an AdditionalProperties schema with some AdditionalProperties. 1370 // this also runs for aliased map types (with zero properties save additionalProperties) 1371 // 1372 // find out how deep this rabbit hole goes 1373 // descend, unwind and rewrite 1374 // This needs to be depth first, so it first goes as deep as it can and then 1375 // builds the result in reverse order. 1376 _, ls, err := newMapStack(sg) 1377 if err != nil { 1378 return err 1379 } 1380 return ls.Build() 1381 } 1382 1383 if sg.GenSchema.IsAdditionalProperties && !sg.Named { 1384 // for an anonymous object, first build the new object 1385 // and then replace the current one with a $ref to the 1386 // new object 1387 newObj := sg.makeNewStruct(sg.GenSchema.Name+" P"+strconv.Itoa(sg.Index), sg.Schema) 1388 if err := newObj.makeGenSchema(); err != nil { 1389 return err 1390 } 1391 1392 hasMapNullOverride := sg.GenSchema.IsMapNullOverride 1393 sg.GenSchema = GenSchema{StructTags: sg.StructTags} 1394 sg.Schema = *spec.RefProperty("#/definitions/" + newObj.Name) 1395 if err := sg.makeGenSchema(); err != nil { 1396 return err 1397 } 1398 sg.MergeResult(newObj, false) 1399 1400 sg.GenSchema.IsMapNullOverride = hasMapNullOverride 1401 if sg.GenSchema.IsArray { 1402 sg.GenSchema.Items.IsMapNullOverride = hasMapNullOverride 1403 } 1404 1405 sg.GenSchema.HasValidations = newObj.GenSchema.HasValidations 1406 sg.ExtraSchemas[newObj.Name] = newObj.GenSchema 1407 return nil 1408 } 1409 return nil 1410 } 1411 1412 func (sg *schemaGenContext) makeNewStruct(name string, schema spec.Schema) *schemaGenContext { 1413 debugLog("making new struct: name: %s, container: %s", name, sg.Container) 1414 sp := sg.TypeResolver.Doc.Spec() 1415 name = swag.ToGoName(name) 1416 if sg.TypeResolver.ModelName != sg.Name { 1417 name = swag.ToGoName(sg.TypeResolver.ModelName + " " + name) 1418 } 1419 if sp.Definitions == nil { 1420 sp.Definitions = make(spec.Definitions) 1421 } 1422 sp.Definitions[name] = schema 1423 pg := schemaGenContext{ 1424 Path: "", 1425 Name: name, 1426 Receiver: sg.Receiver, 1427 IndexVar: "i", 1428 ValueExpr: sg.Receiver, 1429 Schema: schema, 1430 Required: false, 1431 Named: true, 1432 ExtraSchemas: make(map[string]GenSchema), 1433 Discrimination: sg.Discrimination, 1434 Container: sg.Container, 1435 IncludeValidator: sg.IncludeValidator, 1436 IncludeModel: sg.IncludeModel, 1437 StrictAdditionalProperties: sg.StrictAdditionalProperties, 1438 StructTags: sg.StructTags, 1439 } 1440 if schema.Ref.String() == "" { 1441 pg.TypeResolver = sg.TypeResolver.NewWithModelName(name) 1442 } 1443 pg.GenSchema.IsVirtual = true 1444 1445 sg.ExtraSchemas[name] = pg.GenSchema 1446 return &pg 1447 } 1448 1449 func (sg *schemaGenContext) buildArray() error { 1450 tpe, err := sg.TypeResolver.ResolveSchema(sg.Schema.Items.Schema, true, false) 1451 if err != nil { 1452 return err 1453 } 1454 1455 // check if the element is a complex object, if so generate a new type for it 1456 if tpe.IsComplexObject && tpe.IsAnonymous { 1457 pg := sg.makeNewStruct(sg.makeRefName()+" items"+strconv.Itoa(sg.Index), *sg.Schema.Items.Schema) 1458 if err := pg.makeGenSchema(); err != nil { 1459 return err 1460 } 1461 sg.MergeResult(pg, false) 1462 sg.ExtraSchemas[pg.Name] = pg.GenSchema 1463 sg.Schema.Items.Schema = spec.RefProperty("#/definitions/" + pg.Name) 1464 sg.IsVirtual = true 1465 return sg.makeGenSchema() 1466 } 1467 1468 // create the generation schema for items 1469 elProp := sg.NewSliceBranch(sg.Schema.Items.Schema) 1470 1471 // when building a slice of maps, the map item is not required 1472 // items from maps of aliased or nullable type remain required 1473 1474 // NOTE(fredbi): since this is reset below, this Required = true serves the obscure purpose 1475 // of indirectly lifting validations from the slice. This is carried out differently now. 1476 // elProp.Required = true 1477 1478 if err := elProp.makeGenSchema(); err != nil { 1479 return err 1480 } 1481 1482 sg.MergeResult(elProp, false) 1483 1484 sg.GenSchema.IsBaseType = elProp.GenSchema.IsBaseType 1485 sg.GenSchema.ItemsEnum = elProp.GenSchema.Enum 1486 elProp.GenSchema.Suffix = "Items" 1487 1488 elProp.GenSchema.IsNullable = tpe.IsNullable && !tpe.HasDiscriminator 1489 if elProp.GenSchema.IsNullable { 1490 sg.GenSchema.GoType = "[]*" + elProp.GenSchema.GoType 1491 } else { 1492 sg.GenSchema.GoType = "[]" + elProp.GenSchema.GoType 1493 } 1494 1495 sg.GenSchema.IsArray = true 1496 1497 schemaCopy := elProp.GenSchema 1498 1499 schemaCopy.Required = false 1500 1501 // validations of items 1502 // include format validation, excluding binary and base64 format validation 1503 hv := hasValidations(sg.Schema.Items.Schema, false) || hasFormatValidation(schemaCopy.resolvedType) 1504 1505 // base types of polymorphic types must be validated 1506 // NOTE: IsNullable is not useful to figure out a validation: we use Refed and IsAliased below instead 1507 if hv || elProp.GenSchema.IsBaseType { 1508 schemaCopy.HasValidations = true 1509 } 1510 1511 if (elProp.Schema.Ref.String() != "" || elProp.GenSchema.IsAliased) && !(elProp.GenSchema.IsInterface || elProp.GenSchema.IsStream) { 1512 schemaCopy.HasValidations = true 1513 } 1514 1515 // lift validations 1516 sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || schemaCopy.HasValidations 1517 sg.GenSchema.HasSliceValidations = sg.Schema.Validations().HasArrayValidations() || sg.Schema.Validations().HasEnum() 1518 1519 // prevents bubbling custom formatter flag 1520 sg.GenSchema.IsCustomFormatter = false 1521 1522 sg.GenSchema.Items = &schemaCopy 1523 if sg.Named { 1524 sg.GenSchema.AliasedType = sg.GenSchema.GoType 1525 } 1526 1527 return nil 1528 } 1529 1530 func (sg *schemaGenContext) buildItems() error { 1531 if sg.Schema.Items == nil { 1532 // in swagger, arrays MUST have an items schema 1533 return nil 1534 } 1535 1536 // in Items spec, we have either Schema (array) or Schemas (tuple) 1537 presentsAsSingle := sg.Schema.Items.Schema != nil 1538 if presentsAsSingle && sg.Schema.AdditionalItems != nil { // unsure if this a valid of invalid schema 1539 return fmt.Errorf("single schema (%s) can't have additional items", sg.Name) 1540 } 1541 if presentsAsSingle { 1542 return sg.buildArray() 1543 } 1544 1545 // This is a tuple, build a new model that represents this 1546 if sg.Named { 1547 sg.GenSchema.Name = sg.Name 1548 sg.GenSchema.GoType = sg.TypeResolver.goTypeName(sg.Name) 1549 for i, sch := range sg.Schema.Items.Schemas { 1550 s := sch 1551 elProp := sg.NewTupleElement(&s, i) 1552 1553 if s.Ref.String() == "" { 1554 tpe, err := sg.TypeResolver.ResolveSchema(&s, s.Ref.String() == "", true) 1555 if err != nil { 1556 return err 1557 } 1558 if tpe.IsComplexObject && tpe.IsAnonymous { 1559 // if the tuple element is an anonymous complex object, build a new type for it 1560 pg := sg.makeNewStruct(sg.makeRefName()+" Items"+strconv.Itoa(i), s) 1561 if err := pg.makeGenSchema(); err != nil { 1562 return err 1563 } 1564 elProp.Schema = *spec.RefProperty("#/definitions/" + pg.Name) 1565 elProp.MergeResult(pg, false) 1566 elProp.ExtraSchemas[pg.Name] = pg.GenSchema 1567 } 1568 } 1569 1570 if err := elProp.makeGenSchema(); err != nil { 1571 return err 1572 } 1573 if elProp.GenSchema.IsInterface || elProp.GenSchema.IsStream { 1574 elProp.GenSchema.HasValidations = false 1575 } 1576 sg.MergeResult(elProp, false) 1577 1578 elProp.GenSchema.Name = "p" + strconv.Itoa(i) 1579 sg.GenSchema.Properties = append(sg.GenSchema.Properties, elProp.GenSchema) 1580 sg.GenSchema.IsTuple = true 1581 } 1582 return nil 1583 } 1584 1585 // for an anonymous object, first build the new object 1586 // and then replace the current one with a $ref to the 1587 // new tuple object 1588 var sch spec.Schema 1589 sch.Typed("object", "") 1590 sch.Properties = make(map[string]spec.Schema, len(sg.Schema.Items.Schemas)) 1591 for i, v := range sg.Schema.Items.Schemas { 1592 sch.Required = append(sch.Required, "P"+strconv.Itoa(i)) 1593 sch.Properties["P"+strconv.Itoa(i)] = v 1594 } 1595 sch.AdditionalItems = sg.Schema.AdditionalItems 1596 tup := sg.makeNewStruct(sg.GenSchema.Name+"Tuple"+strconv.Itoa(sg.Index), sch) 1597 tup.IsTuple = true 1598 if err := tup.makeGenSchema(); err != nil { 1599 return err 1600 } 1601 tup.GenSchema.IsTuple = true 1602 tup.GenSchema.IsComplexObject = false 1603 tup.GenSchema.Title = tup.GenSchema.Name + " a representation of an anonymous Tuple type" 1604 tup.GenSchema.Description = "" 1605 sg.ExtraSchemas[tup.Name] = tup.GenSchema 1606 1607 sg.Schema = *spec.RefProperty("#/definitions/" + tup.Name) 1608 if err := sg.makeGenSchema(); err != nil { 1609 return err 1610 } 1611 sg.MergeResult(tup, false) 1612 return nil 1613 } 1614 1615 func (sg *schemaGenContext) buildAdditionalItems() error { 1616 wantsAdditionalItems := sg.Schema.AdditionalItems != nil && 1617 (sg.Schema.AdditionalItems.Allows || sg.Schema.AdditionalItems.Schema != nil) 1618 1619 sg.GenSchema.HasAdditionalItems = wantsAdditionalItems 1620 if wantsAdditionalItems { 1621 // check if the element is a complex object, if so generate a new type for it 1622 tpe, err := sg.TypeResolver.ResolveSchema(sg.Schema.AdditionalItems.Schema, true, true) 1623 if err != nil { 1624 return err 1625 } 1626 if tpe.IsComplexObject && tpe.IsAnonymous { 1627 pg := sg.makeNewStruct(sg.makeRefName()+" Items", *sg.Schema.AdditionalItems.Schema) 1628 if err := pg.makeGenSchema(); err != nil { 1629 return err 1630 } 1631 sg.Schema.AdditionalItems.Schema = spec.RefProperty("#/definitions/" + pg.Name) 1632 pg.GenSchema.HasValidations = true 1633 sg.MergeResult(pg, false) 1634 sg.ExtraSchemas[pg.Name] = pg.GenSchema 1635 } 1636 1637 it := sg.NewAdditionalItems(sg.Schema.AdditionalItems.Schema) 1638 // if AdditionalItems are themselves arrays, bump the index var 1639 if tpe.IsArray { 1640 it.IndexVar += "i" 1641 } 1642 1643 if tpe.IsInterface { 1644 it.Untyped = true 1645 } 1646 1647 if err := it.makeGenSchema(); err != nil { 1648 return err 1649 } 1650 1651 // lift validations when complex is not anonymous or ref'ed 1652 if (tpe.IsComplexObject || it.Schema.Ref.String() != "") && !(tpe.IsInterface || tpe.IsStream) { 1653 it.GenSchema.HasValidations = true 1654 } 1655 1656 sg.MergeResult(it, true) 1657 sg.GenSchema.AdditionalItems = &it.GenSchema 1658 } 1659 return nil 1660 } 1661 1662 func (sg *schemaGenContext) buildXMLNameWithTags() error { 1663 // render some "xml" struct tag under one the following conditions: 1664 // - consumes/produces in spec contains xml 1665 // - struct tags CLI option contains xml 1666 // - XML object present in spec for this schema 1667 if sg.WithXML || swag.ContainsStrings(sg.StructTags, "xml") || sg.Schema.XML != nil { 1668 sg.GenSchema.XMLName = sg.Name 1669 1670 if sg.Schema.XML != nil { 1671 if sg.Schema.XML.Name != "" { 1672 sg.GenSchema.XMLName = sg.Schema.XML.Name 1673 } 1674 if sg.Schema.XML.Attribute { 1675 sg.GenSchema.XMLName += ",attr" 1676 } 1677 } 1678 } 1679 return nil 1680 } 1681 1682 func (sg *schemaGenContext) shortCircuitNamedRef() (bool, error) { 1683 // This if block ensures that a struct gets 1684 // rendered with the ref as embedded ref. 1685 // 1686 // NOTE: this assumes that all $ref point to a definition, 1687 // i.e. the spec is canonical, as guaranteed by minimal flattening. 1688 // 1689 if !sg.Named || sg.Schema.Ref.String() == "" { 1690 return false, nil 1691 } 1692 debugLogAsJSON("short circuit named ref: %q", sg.Schema.Ref.String(), sg.Schema) 1693 1694 // Simple aliased types (arrays, maps and primitives) 1695 // 1696 // Before deciding to make a struct with a composition branch (below), 1697 // check if the $ref points to a simple type or polymorphic (base) type. 1698 // 1699 // If this is the case, just realias this simple type, without creating a struct. 1700 // 1701 // In templates this case is identified by .IsSuperAlias = true 1702 asch, era := analysis.Schema(analysis.SchemaOpts{ 1703 Root: sg.TypeResolver.Doc.Spec(), 1704 BasePath: sg.TypeResolver.Doc.SpecFilePath(), 1705 Schema: &sg.Schema, 1706 }) 1707 if era != nil { 1708 return false, era 1709 } 1710 1711 if asch.IsArray || asch.IsMap || asch.IsKnownType || asch.IsBaseType { 1712 tpx, ers := sg.TypeResolver.ResolveSchema(&sg.Schema, false, true) 1713 if ers != nil { 1714 return false, ers 1715 } 1716 tpe := resolvedType{} 1717 tpe.IsMap = asch.IsMap 1718 tpe.IsArray = asch.IsArray 1719 tpe.IsPrimitive = asch.IsKnownType 1720 1721 tpe.IsAliased = true 1722 tpe.AliasedType = "" 1723 tpe.IsComplexObject = false 1724 tpe.IsAnonymous = false 1725 tpe.IsCustomFormatter = false 1726 tpe.IsBaseType = tpx.IsBaseType 1727 1728 tpe.GoType = sg.TypeResolver.goTypeName(path.Base(sg.Schema.Ref.String())) 1729 tpe.Pkg = sg.TypeResolver.definitionPkg 1730 1731 tpe.IsNullable = tpx.IsNullable // TODO 1732 tpe.IsInterface = tpx.IsInterface 1733 tpe.IsStream = tpx.IsStream 1734 tpe.IsEmbedded = tpx.IsEmbedded 1735 1736 tpe.SwaggerType = tpx.SwaggerType 1737 sch := spec.Schema{} 1738 pg := sg.makeNewStruct(sg.Name, sch) 1739 if err := pg.makeGenSchema(); err != nil { 1740 return true, err 1741 } 1742 sg.MergeResult(pg, true) 1743 sg.GenSchema = pg.GenSchema 1744 sg.GenSchema.resolvedType = tpe 1745 sg.GenSchema.resolvedType.IsSuperAlias = true 1746 sg.GenSchema.IsBaseType = tpe.IsBaseType 1747 1748 return true, nil 1749 } 1750 1751 // Aliased object: use golang struct composition. 1752 // Covers case of a type redefinition like: 1753 // thistype: 1754 // $ref: #/definitions/othertype 1755 // 1756 // This is rendered as a struct with type field, i.e. : 1757 // Alias struct { 1758 // AliasedType 1759 // } 1760 // 1761 // In templates, the schema is composed like AllOf. 1762 nullableOverride := sg.GenSchema.IsNullable 1763 1764 tpe := resolvedType{} 1765 tpe.GoType = sg.TypeResolver.goTypeName(sg.Name) 1766 tpe.Pkg = sg.TypeResolver.definitionPkg 1767 tpe.SwaggerType = "object" 1768 tpe.IsComplexObject = true 1769 tpe.IsMap = false 1770 tpe.IsArray = false 1771 tpe.IsAnonymous = false 1772 tpe.IsNullable = sg.TypeResolver.isNullable(&sg.Schema) 1773 1774 branch := sg.NewCompositionBranch(sg.Schema, 0) 1775 if err := branch.makeGenSchema(); err != nil { 1776 return true, err 1777 } 1778 sg.GenSchema.resolvedType = tpe 1779 sg.GenSchema.IsNullable = sg.GenSchema.IsNullable || nullableOverride 1780 // prevent format from bubbling up in composed type 1781 branch.GenSchema.IsCustomFormatter = false 1782 1783 sg.MergeResult(branch, true) 1784 1785 tpx, ers := sg.TypeResolver.ResolveSchema(&sg.Schema, false, true) 1786 if ers != nil { 1787 return false, ers 1788 } 1789 // we don't know the actual validation status yet. So assume true, 1790 // unless we can infer that no Validate() method will be present 1791 branch.GenSchema.HasValidations = !tpx.IsInterface && !tpx.IsStream 1792 sg.GenSchema.AllOf = append(sg.GenSchema.AllOf, branch.GenSchema) 1793 1794 return true, nil 1795 } 1796 1797 // liftSpecialAllOf attempts to simplify the rendering of allOf constructs by lifting simple things into the current schema. 1798 func (sg *schemaGenContext) liftSpecialAllOf() error { 1799 // if there is only a $ref or a primitive and an x-isnullable schema then this is a nullable pointer 1800 // so this should not compose several objects, just 1 1801 // if there is a ref with a discriminator then we look for x-class on the current definition to know 1802 // the value of the discriminator to instantiate the class 1803 if len(sg.Schema.AllOf) < 2 { 1804 return nil 1805 } 1806 var seenSchema int 1807 var seenNullable bool 1808 var schemaToLift spec.Schema 1809 1810 for _, schema := range sg.Schema.AllOf { 1811 sch := schema 1812 tpe, err := sg.TypeResolver.ResolveSchema(&sch, true, true) 1813 if err != nil { 1814 return err 1815 } 1816 if sg.TypeResolver.isNullable(&sch) { 1817 seenNullable = true 1818 } 1819 if len(sch.Type) > 0 || len(sch.Properties) > 0 || sch.Ref.GetURL() != nil || len(sch.AllOf) > 0 { 1820 seenSchema++ 1821 if seenSchema > 1 { 1822 // won't do anything if several candidates for a lift 1823 break 1824 } 1825 if (!tpe.IsAnonymous && tpe.IsComplexObject) || tpe.IsPrimitive { 1826 // lifting complex objects here results in inlined structs in the model 1827 schemaToLift = sch 1828 } 1829 } 1830 } 1831 1832 if seenSchema == 1 { 1833 // when there only a single schema to lift in allOf, replace the schema by its allOf definition 1834 debugLog("lifted schema in allOf for %s", sg.Name) 1835 sg.Schema = schemaToLift 1836 sg.GenSchema.IsNullable = seenNullable 1837 } 1838 return nil 1839 } 1840 1841 func (sg *schemaGenContext) buildAliased() error { 1842 if !sg.GenSchema.IsPrimitive && !sg.GenSchema.IsMap && !sg.GenSchema.IsArray && !sg.GenSchema.IsInterface { 1843 return nil 1844 } 1845 1846 if sg.GenSchema.IsPrimitive { 1847 if sg.GenSchema.SwaggerType == "string" && sg.GenSchema.SwaggerFormat == "" { 1848 sg.GenSchema.IsAliased = sg.GenSchema.GoType != sg.GenSchema.SwaggerType 1849 } 1850 if sg.GenSchema.IsNullable && sg.Named { 1851 sg.GenSchema.IsNullable = false 1852 } 1853 } 1854 1855 if sg.GenSchema.IsInterface { 1856 sg.GenSchema.IsAliased = sg.GenSchema.GoType != iface 1857 } 1858 1859 if sg.GenSchema.IsMap { 1860 sg.GenSchema.IsAliased = !strings.HasPrefix(sg.GenSchema.GoType, "map[") 1861 } 1862 1863 if sg.GenSchema.IsArray { 1864 sg.GenSchema.IsAliased = !strings.HasPrefix(sg.GenSchema.GoType, "[]") 1865 } 1866 return nil 1867 } 1868 1869 func (sg schemaGenContext) makeRefName() string { 1870 // figure out a longer name for deconflicting anonymous models. 1871 // This is used when makeNewStruct() is followed by the creation of a new ref to definitions 1872 if sg.UseContainerInName && sg.Container != sg.Name { 1873 return sg.Container + swag.ToGoName(sg.Name) 1874 } 1875 return sg.Name 1876 } 1877 1878 func (sg *schemaGenContext) GoName() string { 1879 return goName(&sg.Schema, sg.Name) 1880 } 1881 1882 func goName(sch *spec.Schema, orig string) string { 1883 name, _ := sch.Extensions.GetString(xGoName) 1884 if name != "" { 1885 return name 1886 } 1887 return orig 1888 } 1889 1890 func (sg *schemaGenContext) derefMapElement(outer *GenSchema, _ *GenSchema, elem *GenSchema) { 1891 derefType := strings.TrimPrefix(elem.GoType, "*") 1892 1893 if outer.IsAliased { 1894 nesting := strings.TrimSuffix(strings.TrimSuffix(outer.AliasedType, elem.GoType), "*") 1895 outer.AliasedType = nesting + derefType 1896 outer.GoType = derefType 1897 } else { 1898 nesting := strings.TrimSuffix(strings.TrimSuffix(outer.GoType, elem.GoType), "*") 1899 outer.GoType = nesting + derefType 1900 } 1901 1902 elem.GoType = derefType 1903 } 1904 1905 func (sg *schemaGenContext) checkNeedsPointer(outer *GenSchema, sch *GenSchema, elem *GenSchema) { 1906 derefType := strings.TrimPrefix(elem.GoType, "*") 1907 switch { 1908 case outer.IsAliased && !strings.HasSuffix(outer.AliasedType, "*"+derefType): 1909 // override nullability of map of primitive elements: render element of aliased or anonymous map as a pointer 1910 outer.AliasedType = strings.TrimSuffix(outer.AliasedType, derefType) + "*" + derefType 1911 case sch != nil: 1912 // nullable primitive 1913 if sch.IsAnonymous && !strings.HasSuffix(outer.GoType, "*"+derefType) { 1914 sch.GoType = strings.TrimSuffix(sch.GoType, derefType) + "*" + derefType 1915 } 1916 case outer.IsAnonymous && !strings.HasSuffix(outer.GoType, "*"+derefType): 1917 outer.GoType = strings.TrimSuffix(outer.GoType, derefType) + "*" + derefType 1918 } 1919 } 1920 1921 // buildMapOfNullable equalizes the nullablity status for aliased and anonymous maps of simple things, 1922 // with the nullability of its innermost element. 1923 // 1924 // NOTE: at the moment, we decide to align the type of the outer element (map) to the type of the inner element 1925 // The opposite could be done and result in non nullable primitive elements. If we do so, the validation 1926 // code needs to be adapted by removing IsZero() and Required() calls in codegen. 1927 func (sg *schemaGenContext) buildMapOfNullable(sch *GenSchema) { 1928 outer := &sg.GenSchema 1929 if sch == nil { 1930 sch = outer 1931 } 1932 if sch.IsMap && (outer.IsAliased || outer.IsAnonymous) { 1933 elem := sch.AdditionalProperties 1934 for elem != nil { 1935 if elem.IsPrimitive && elem.IsNullable { 1936 sg.checkNeedsPointer(outer, nil, elem) 1937 } else if elem.IsArray { 1938 // override nullability of array of primitive elements: 1939 // render element of aliased or anonyous map as a pointer 1940 it := elem.Items 1941 for it != nil { 1942 switch { 1943 case it.IsPrimitive && it.IsNullable: 1944 sg.checkNeedsPointer(outer, sch, it) 1945 case it.IsMap: 1946 sg.buildMapOfNullable(it) 1947 case !it.IsPrimitive && !it.IsArray && it.IsComplexObject && it.IsNullable: 1948 // structs in map are not rendered as pointer by default 1949 // unless some x-nullable overrides says so 1950 _, forced := it.Extensions[xNullable] 1951 if !forced { 1952 _, forced = it.Extensions[xIsNullable] 1953 } 1954 if !forced { 1955 sg.derefMapElement(outer, sch, it) 1956 } 1957 } 1958 it = it.Items 1959 } 1960 } 1961 elem = elem.AdditionalProperties 1962 } 1963 } 1964 } 1965 1966 func (sg *schemaGenContext) makeGenSchema() error { 1967 debugLogAsJSON("making gen schema (anon: %t, req: %t, tuple: %t) %s\n", 1968 !sg.Named, sg.Required, sg.IsTuple, sg.Name, sg.Schema) 1969 1970 sg.GenSchema.Example = "" 1971 if sg.Schema.Example != nil { 1972 data, err := asJSON(sg.Schema.Example) 1973 if err != nil { 1974 return err 1975 } 1976 // Deleting the unnecessary double quotes for string types 1977 // otherwise the generate spec will generate as "\"foo\"" 1978 sg.GenSchema.Example = strings.Trim(data, "\"") 1979 } 1980 sg.GenSchema.ExternalDocs = trimExternalDoc(sg.Schema.ExternalDocs) 1981 sg.GenSchema.IsExported = true 1982 sg.GenSchema.Path = sg.Path 1983 sg.GenSchema.IndexVar = sg.IndexVar 1984 sg.GenSchema.Location = body 1985 sg.GenSchema.ValueExpression = sg.ValueExpr 1986 sg.GenSchema.KeyVar = sg.KeyVar 1987 sg.GenSchema.OriginalName = sg.Name 1988 sg.GenSchema.Name = sg.GoName() 1989 sg.GenSchema.Title = sg.Schema.Title 1990 sg.GenSchema.Description = trimBOM(sg.Schema.Description) 1991 sg.GenSchema.ReceiverName = sg.Receiver 1992 sg.GenSchema.sharedValidations = sg.schemaValidations() 1993 sg.GenSchema.ReadOnly = sg.Schema.ReadOnly 1994 sg.GenSchema.IncludeValidator = sg.IncludeValidator 1995 sg.GenSchema.IncludeModel = sg.IncludeModel 1996 sg.GenSchema.StrictAdditionalProperties = sg.StrictAdditionalProperties 1997 sg.GenSchema.Default = sg.Schema.Default 1998 sg.GenSchema.StructTags = sg.StructTags 1999 sg.GenSchema.ExtraImports = make(map[string]string) 2000 sg.GenSchema.WantsRootedErrorPath = sg.WantsRootedErrorPath 2001 sg.GenSchema.IsElem = sg.IsElem 2002 sg.GenSchema.IsProperty = sg.IsProperty 2003 2004 var err error 2005 returns, err := sg.shortCircuitNamedRef() 2006 if err != nil { 2007 return err 2008 } 2009 if returns { 2010 // short circuited on a resolved $ref 2011 return nil 2012 } 2013 debugLogAsJSON("after short circuit named ref", sg.Schema) 2014 2015 if e := sg.liftSpecialAllOf(); e != nil { 2016 return e 2017 } 2018 nullableOverride := sg.GenSchema.IsNullable 2019 debugLogAsJSON("after lifting special all of", sg.Schema) 2020 2021 if sg.Container == "" { 2022 sg.Container = sg.GenSchema.Name 2023 } 2024 if e := sg.buildAllOf(); e != nil { 2025 return e 2026 } 2027 2028 var tpe resolvedType 2029 if sg.Untyped { 2030 tpe, err = sg.TypeResolver.ResolveSchema(nil, !sg.Named, sg.IsTuple || sg.Required || sg.GenSchema.Required) 2031 } else { 2032 tpe, err = sg.TypeResolver.ResolveSchema(&sg.Schema, !sg.Named, sg.IsTuple || sg.Required || sg.GenSchema.Required) 2033 } 2034 if err != nil { 2035 return err 2036 } 2037 2038 debugLog("gschema rrequired: %t, nullable: %t", sg.GenSchema.Required, sg.GenSchema.IsNullable) 2039 tpe.IsNullable = tpe.IsNullable || nullableOverride 2040 sg.GenSchema.resolvedType = tpe 2041 sg.GenSchema.IsBaseType = tpe.IsBaseType 2042 sg.GenSchema.HasDiscriminator = tpe.HasDiscriminator 2043 2044 // include format validations, excluding binary 2045 sg.GenSchema.HasValidations = sg.GenSchema.HasValidations || hasFormatValidation(tpe) 2046 2047 // include context validations 2048 sg.GenSchema.HasContextValidations = sg.GenSchema.HasContextValidations || hasContextValidations(&sg.Schema) && !tpe.IsInterface && !tpe.IsStream && !tpe.SkipExternalValidation 2049 2050 // usage of a polymorphic base type is rendered with getter funcs on private properties. 2051 // In the case of aliased types, the value expression remains unchanged to the receiver. 2052 if tpe.IsArray && tpe.ElemType != nil && tpe.ElemType.IsBaseType && sg.GenSchema.ValueExpression != sg.GenSchema.ReceiverName { 2053 sg.GenSchema.ValueExpression += asMethod 2054 } 2055 2056 if tpe.IsExternal { // anonymous external types 2057 extType, pkg, alias := sg.TypeResolver.knownDefGoType(sg.GenSchema.Name, sg.Schema, sg.TypeResolver.goTypeName) 2058 if pkg != "" && alias != "" { 2059 sg.GenSchema.ExtraImports[alias] = pkg 2060 } 2061 2062 if !tpe.IsEmbedded { 2063 sg.GenSchema.resolvedType = tpe 2064 sg.GenSchema.Required = sg.Required 2065 // assume we validate everything but interface and io.Reader - validation may be disabled by using the noValidation hint 2066 sg.GenSchema.HasValidations = !(tpe.IsInterface || tpe.IsStream || tpe.SkipExternalValidation) 2067 sg.GenSchema.IsAliased = sg.GenSchema.HasValidations 2068 2069 log.Printf("INFO: type %s is external, with inferred spec type %s, referred to as %s", sg.GenSchema.Name, sg.GenSchema.GoType, extType) 2070 sg.GenSchema.GoType = extType 2071 sg.GenSchema.AliasedType = extType 2072 2073 // short circuit schema building for external types 2074 return nil 2075 } 2076 // TODO: case for embedded types as anonymous definitions 2077 return fmt.Errorf("ERROR: inline definitions embedded types are not supported") 2078 } 2079 2080 debugLog("gschema nullable: %t", sg.GenSchema.IsNullable) 2081 if e := sg.buildAdditionalProperties(); e != nil { 2082 return e 2083 } 2084 2085 // rewrite value expression from top-down 2086 cur := &sg.GenSchema 2087 for cur.AdditionalProperties != nil { 2088 cur.AdditionalProperties.ValueExpression = cur.ValueExpression + "[" + cur.AdditionalProperties.KeyVar + "]" 2089 cur = cur.AdditionalProperties 2090 } 2091 2092 prev := sg.GenSchema 2093 if sg.Untyped { 2094 debugLogAsJSON("untyped resolve:%t", sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required, sg.Schema) 2095 tpe, err = sg.TypeResolver.ResolveSchema(nil, !sg.Named, sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required) 2096 } else { 2097 debugLogAsJSON("typed resolve, isAnonymous(%t), n: %t, t: %t, sgr: %t, sr: %t, isRequired(%t), BaseType(%t)", 2098 !sg.Named, sg.Named, sg.IsTuple, sg.Required, sg.GenSchema.Required, 2099 sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required, sg.GenSchema.IsBaseType, sg.Schema) 2100 tpe, err = sg.TypeResolver.ResolveSchema(&sg.Schema, !sg.Named, sg.Named || sg.IsTuple || sg.Required || sg.GenSchema.Required) 2101 } 2102 if err != nil { 2103 return err 2104 } 2105 otn := tpe.IsNullable // for debug only 2106 tpe.IsNullable = tpe.IsNullable || nullableOverride 2107 sg.GenSchema.resolvedType = tpe 2108 sg.GenSchema.IsComplexObject = prev.IsComplexObject 2109 sg.GenSchema.IsMap = prev.IsMap 2110 sg.GenSchema.IsAdditionalProperties = prev.IsAdditionalProperties 2111 sg.GenSchema.IsBaseType = sg.GenSchema.HasDiscriminator 2112 sg.GenSchema.IsElem = prev.IsElem 2113 sg.GenSchema.IsProperty = prev.IsProperty 2114 2115 debugLogAsJSON("gschema nnullable:IsNullable:%t,resolver.IsNullable:%t,nullableOverride:%t", 2116 sg.GenSchema.IsNullable, otn, nullableOverride, sg.Schema) 2117 if err := sg.buildProperties(); err != nil { 2118 return err 2119 } 2120 2121 if err := sg.buildXMLNameWithTags(); err != nil { 2122 return err 2123 } 2124 2125 if err := sg.buildAdditionalItems(); err != nil { 2126 return err 2127 } 2128 2129 if err := sg.buildItems(); err != nil { 2130 return err 2131 } 2132 2133 if err := sg.buildAliased(); err != nil { 2134 return err 2135 } 2136 2137 sg.buildMapOfNullable(nil) 2138 2139 // extra serializers & interfaces 2140 2141 // generate MarshalBinary for: 2142 // - tuple 2143 // - struct 2144 // - map 2145 // - aliased primitive of a formatter type which is not a stringer 2146 // 2147 // but not for: 2148 // - interface{} 2149 // - io.Reader 2150 gs := sg.GenSchema 2151 sg.GenSchema.WantsMarshalBinary = !(gs.IsInterface || gs.IsStream || gs.IsBaseType) && 2152 (gs.IsTuple || gs.IsComplexObject || gs.IsAdditionalProperties || (gs.IsPrimitive && gs.IsAliased && gs.IsCustomFormatter && !strings.Contains(gs.Zero(), `("`))) 2153 2154 debugLog("finished gen schema for %q", sg.Name) 2155 2156 return nil 2157 }