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