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