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