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