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