github.com/Percona-Lab/go-swagger@v0.19.0/generator/types.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  	"fmt"
    19  	"log"
    20  	"path"
    21  	"path/filepath"
    22  	"strings"
    23  
    24  	"github.com/go-openapi/loads"
    25  	"github.com/go-openapi/spec"
    26  	"github.com/go-openapi/swag"
    27  	"github.com/kr/pretty"
    28  )
    29  
    30  const (
    31  	iface   = "interface{}"
    32  	array   = "array"
    33  	file    = "file"
    34  	number  = "number"
    35  	integer = "integer"
    36  	boolean = "boolean"
    37  	str     = "string"
    38  	object  = "object"
    39  	binary  = "binary"
    40  	sHTTP   = "http"
    41  	body    = "body"
    42  )
    43  
    44  // Extensions supported by go-swagger
    45  const (
    46  	xClass       = "x-class"         // class name used by discriminator
    47  	xGoCustomTag = "x-go-custom-tag" // additional tag for serializers on struct fields
    48  	xGoName      = "x-go-name"       // name of the generated go variable
    49  	xGoType      = "x-go-type"       // reuse existing type (do not generate)
    50  	xIsNullable  = "x-isnullable"
    51  	xNullable    = "x-nullable" // turns the schema into a pointer
    52  	xOmitEmpty   = "x-omitempty"
    53  	xSchemes     = "x-schemes" // additional schemes supported for operations (server generation)
    54  	xOrder       = "x-order"   // sort order for properties (or any schema)
    55  )
    56  
    57  // swaggerTypeMapping contains a mapping from go type to swagger type or format
    58  var swaggerTypeName map[string]string
    59  
    60  func init() {
    61  	swaggerTypeName = make(map[string]string)
    62  	for k, v := range typeMapping {
    63  		swaggerTypeName[v] = k
    64  	}
    65  }
    66  
    67  func simpleResolvedType(tn, fmt string, items *spec.Items) (result resolvedType) {
    68  	result.SwaggerType = tn
    69  	result.SwaggerFormat = fmt
    70  
    71  	if tn == file {
    72  		// special case of swagger type "file", rendered as io.ReadCloser interface
    73  		result.IsPrimitive = true
    74  		result.GoType = formatMapping[str][binary]
    75  		result.IsStream = true
    76  		return
    77  	}
    78  
    79  	if fmt != "" {
    80  		fmtn := strings.Replace(fmt, "-", "", -1)
    81  		if fmm, ok := formatMapping[tn]; ok {
    82  			if tpe, ok := fmm[fmtn]; ok {
    83  				result.GoType = tpe
    84  				result.IsPrimitive = true
    85  				_, result.IsCustomFormatter = customFormatters[tpe]
    86  				// special case of swagger format "binary", rendered as io.ReadCloser interface
    87  				// TODO(fredbi): should set IsCustomFormatter=false when binary
    88  				result.IsStream = fmt == binary
    89  				return
    90  			}
    91  		}
    92  	}
    93  
    94  	if tpe, ok := typeMapping[tn]; ok {
    95  		result.GoType = tpe
    96  		_, result.IsPrimitive = primitives[tpe]
    97  		result.IsPrimitive = ok
    98  		return
    99  	}
   100  
   101  	if tn == array {
   102  		result.IsArray = true
   103  		result.IsPrimitive = false
   104  		result.IsCustomFormatter = false
   105  		result.IsNullable = false
   106  		if items == nil {
   107  			result.GoType = "[]" + iface
   108  			return
   109  		}
   110  		res := simpleResolvedType(items.Type, items.Format, items.Items)
   111  		result.GoType = "[]" + res.GoType
   112  		return
   113  	}
   114  	result.GoType = tn
   115  	_, result.IsPrimitive = primitives[tn]
   116  	return
   117  }
   118  
   119  func typeForHeader(header spec.Header) resolvedType {
   120  	return simpleResolvedType(header.Type, header.Format, header.Items)
   121  }
   122  
   123  func newTypeResolver(pkg string, doc *loads.Document) *typeResolver {
   124  	resolver := typeResolver{ModelsPackage: pkg, Doc: doc}
   125  	resolver.KnownDefs = make(map[string]struct{}, len(doc.Spec().Definitions))
   126  	for k, sch := range doc.Spec().Definitions {
   127  		tpe, _, _ := knownDefGoType(k, sch, nil)
   128  		resolver.KnownDefs[tpe] = struct{}{}
   129  	}
   130  	return &resolver
   131  }
   132  
   133  // knownDefGoType returns go type, package and package alias for definition
   134  func knownDefGoType(def string, schema spec.Schema, clear func(string) string) (string, string, string) {
   135  	debugLog("known def type: %q", def)
   136  	ext := schema.Extensions
   137  	if nm, ok := ext.GetString(xGoName); ok {
   138  		if clear == nil {
   139  			debugLog("known def type %s no clear: %q", xGoName, nm)
   140  			return nm, "", ""
   141  		}
   142  		debugLog("known def type %s clear: %q -> %q", xGoName, nm, clear(nm))
   143  		return clear(nm), "", ""
   144  	}
   145  	v, ok := ext[xGoType]
   146  	if !ok {
   147  		if clear == nil {
   148  			debugLog("known def type no clear: %q", def)
   149  			return def, "", ""
   150  		}
   151  		debugLog("known def type clear: %q -> %q", def, clear(def))
   152  		return clear(def), "", ""
   153  	}
   154  	xt := v.(map[string]interface{})
   155  	t := xt["type"].(string)
   156  	imp := xt["import"].(map[string]interface{})
   157  	pkg := imp["package"].(string)
   158  	al, ok := imp["alias"]
   159  	var alias string
   160  	if ok {
   161  		alias = al.(string)
   162  	} else {
   163  		alias = path.Base(pkg)
   164  	}
   165  	debugLog("known def type %s no clear: %q: pkg=%s, alias=%s", xGoType, alias+"."+t, pkg, alias)
   166  	return alias + "." + t, pkg, alias
   167  }
   168  
   169  type typeResolver struct {
   170  	Doc           *loads.Document
   171  	ModelsPackage string
   172  	ModelName     string
   173  	KnownDefs     map[string]struct{}
   174  	// unexported fields
   175  	keepDefinitionsPkg string
   176  	knownDefsKept      map[string]struct{}
   177  }
   178  
   179  // NewWithModelName clones a type resolver and specifies a new model name
   180  func (t *typeResolver) NewWithModelName(name string) *typeResolver {
   181  	tt := newTypeResolver(t.ModelsPackage, t.Doc)
   182  	tt.ModelName = name
   183  
   184  	// propagates kept definitions
   185  	tt.keepDefinitionsPkg = t.keepDefinitionsPkg
   186  	tt.knownDefsKept = t.knownDefsKept
   187  	return tt
   188  }
   189  
   190  // withKeepDefinitionsPackage instructs the type resolver to keep previously resolved package name for
   191  // definitions known at the moment it is first called.
   192  func (t *typeResolver) withKeepDefinitionsPackage(definitionsPackage string) *typeResolver {
   193  	t.keepDefinitionsPkg = definitionsPackage
   194  	t.knownDefsKept = make(map[string]struct{}, len(t.KnownDefs))
   195  	for k := range t.KnownDefs {
   196  		t.knownDefsKept[k] = struct{}{}
   197  	}
   198  	return t
   199  }
   200  
   201  // IsNullable hints the generator as to render the type with a pointer or not.
   202  //
   203  // A schema is deemed nullable (i.e. rendered by a pointer) when:
   204  // - a custom extension says it has to be so
   205  // - it is an object with properties
   206  // - it is a composed object (allOf)
   207  //
   208  // The interpretation of Required as a mean to make a type nullable is carried on elsewhere.
   209  func (t *typeResolver) IsNullable(schema *spec.Schema) bool {
   210  	nullable := t.isNullable(schema)
   211  	return nullable || len(schema.AllOf) > 0
   212  }
   213  
   214  func (t *typeResolver) resolveSchemaRef(schema *spec.Schema, isRequired bool) (returns bool, result resolvedType, err error) {
   215  	if schema.Ref.String() != "" {
   216  		debugLog("resolving ref (anon: %t, req: %t) %s", false, isRequired, schema.Ref.String())
   217  		returns = true
   218  		var ref *spec.Schema
   219  		var er error
   220  
   221  		ref, er = spec.ResolveRef(t.Doc.Spec(), &schema.Ref)
   222  		if er != nil {
   223  			debugLog("error resolving ref %s: %v", schema.Ref.String(), er)
   224  			err = er
   225  			return
   226  		}
   227  		res, er := t.ResolveSchema(ref, false, isRequired)
   228  		if er != nil {
   229  			err = er
   230  			return
   231  		}
   232  		result = res
   233  
   234  		tn := filepath.Base(schema.Ref.GetURL().Fragment)
   235  		tpe, pkg, alias := knownDefGoType(tn, *ref, t.goTypeName)
   236  		debugLog("type name %s, package %s, alias %s", tpe, pkg, alias)
   237  		if tpe != "" {
   238  			result.GoType = tpe
   239  			result.Pkg = pkg
   240  			result.PkgAlias = alias
   241  		}
   242  		result.HasDiscriminator = res.HasDiscriminator
   243  		result.IsBaseType = result.HasDiscriminator
   244  		result.IsNullable = t.IsNullable(ref)
   245  		//result.IsAliased = true
   246  		return
   247  
   248  	}
   249  	return
   250  }
   251  
   252  func (t *typeResolver) inferAliasing(result *resolvedType, schema *spec.Schema, isAnonymous bool, isRequired bool) {
   253  	if !isAnonymous && t.ModelName != "" {
   254  		result.AliasedType = result.GoType
   255  		result.IsAliased = true
   256  		result.GoType = t.goTypeName(t.ModelName)
   257  	}
   258  }
   259  
   260  func (t *typeResolver) resolveFormat(schema *spec.Schema, isAnonymous bool, isRequired bool) (returns bool, result resolvedType, err error) {
   261  
   262  	if schema.Format != "" {
   263  		// defaults to string
   264  		result.SwaggerType = str
   265  		if len(schema.Type) > 0 {
   266  			result.SwaggerType = schema.Type[0]
   267  		}
   268  
   269  		debugLog("resolving format (anon: %t, req: %t)", isAnonymous, isRequired)
   270  		schFmt := strings.Replace(schema.Format, "-", "", -1)
   271  		if fmm, ok := formatMapping[result.SwaggerType]; ok {
   272  			if tpe, ok := fmm[schFmt]; ok {
   273  				returns = true
   274  				result.GoType = tpe
   275  				_, result.IsCustomFormatter = customFormatters[tpe]
   276  			}
   277  		}
   278  		if tpe, ok := typeMapping[schFmt]; !returns && ok {
   279  			returns = true
   280  			result.GoType = tpe
   281  			_, result.IsCustomFormatter = customFormatters[tpe]
   282  		}
   283  
   284  		result.SwaggerFormat = schema.Format
   285  		t.inferAliasing(&result, schema, isAnonymous, isRequired)
   286  		// special case of swagger format "binary", rendered as io.ReadCloser interface and is therefore not a primitive type
   287  		// TODO: should set IsCustomFormatter=false in this case.
   288  		result.IsPrimitive = schFmt != binary
   289  		result.IsStream = schFmt == binary
   290  		// propagate extensions in resolvedType
   291  		result.Extensions = schema.Extensions
   292  
   293  		switch result.SwaggerType {
   294  		case str:
   295  			result.IsNullable = nullableStrfmt(schema, isRequired)
   296  		case number, integer:
   297  			result.IsNullable = nullableNumber(schema, isRequired)
   298  		default:
   299  			result.IsNullable = t.IsNullable(schema)
   300  		}
   301  	}
   302  	return
   303  }
   304  
   305  func (t *typeResolver) isNullable(schema *spec.Schema) bool {
   306  	check := func(extension string) (bool, bool) {
   307  		v, found := schema.Extensions[extension]
   308  		nullable, cast := v.(bool)
   309  		return nullable, found && cast
   310  	}
   311  
   312  	if nullable, ok := check(xIsNullable); ok {
   313  		return nullable
   314  	}
   315  	if nullable, ok := check(xNullable); ok {
   316  		return nullable
   317  	}
   318  	return len(schema.Properties) > 0
   319  }
   320  
   321  func setIsEmptyOmitted(result *resolvedType, schema *spec.Schema, tpe string) {
   322  	defaultValue := true
   323  	if tpe == array {
   324  		defaultValue = false
   325  	}
   326  	v, found := schema.Extensions[xOmitEmpty]
   327  	if !found {
   328  		result.IsEmptyOmitted = defaultValue
   329  		return
   330  	}
   331  
   332  	omitted, cast := v.(bool)
   333  	result.IsEmptyOmitted = omitted && cast
   334  }
   335  
   336  func (t *typeResolver) firstType(schema *spec.Schema) string {
   337  	if len(schema.Type) == 0 || schema.Type[0] == "" {
   338  		return object
   339  	}
   340  	if len(schema.Type) > 1 {
   341  		// JSON-Schema multiple types, e.g. {"type": [ "object", "array" ]} are not supported.
   342  		// TODO: should keep the first _supported_ type, e.g. skip null
   343  		log.Printf("warning: JSON-Schema type definition as array with several types is not supported in %#v. Taking the first type: %s", schema.Type, schema.Type[0])
   344  	}
   345  	return schema.Type[0]
   346  }
   347  
   348  func (t *typeResolver) resolveArray(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) {
   349  	debugLog("resolving array (anon: %t, req: %t)", isAnonymous, isRequired)
   350  
   351  	result.IsArray = true
   352  	result.IsNullable = false
   353  
   354  	if schema.AdditionalItems != nil {
   355  		result.HasAdditionalItems = (schema.AdditionalItems.Allows || schema.AdditionalItems.Schema != nil)
   356  	}
   357  
   358  	if schema.Items == nil {
   359  		result.GoType = "[]" + iface
   360  		result.SwaggerType = array
   361  		result.SwaggerFormat = ""
   362  		t.inferAliasing(&result, schema, isAnonymous, isRequired)
   363  
   364  		return
   365  	}
   366  
   367  	if len(schema.Items.Schemas) > 0 {
   368  		result.IsArray = false
   369  		result.IsTuple = true
   370  		result.SwaggerType = array
   371  		result.SwaggerFormat = ""
   372  		t.inferAliasing(&result, schema, isAnonymous, isRequired)
   373  
   374  		return
   375  	}
   376  
   377  	rt, er := t.ResolveSchema(schema.Items.Schema, true, false)
   378  	if er != nil {
   379  		err = er
   380  		return
   381  	}
   382  	// override the general nullability rule from ResolveSchema():
   383  	// only complex items are nullable (when not discriminated, not forced by x-nullable)
   384  	rt.IsNullable = t.IsNullable(schema.Items.Schema) && !rt.HasDiscriminator
   385  	result.GoType = "[]" + rt.GoType
   386  	if rt.IsNullable && !strings.HasPrefix(rt.GoType, "*") {
   387  		result.GoType = "[]*" + rt.GoType
   388  	}
   389  
   390  	result.ElemType = &rt
   391  	result.SwaggerType = array
   392  	result.SwaggerFormat = ""
   393  	t.inferAliasing(&result, schema, isAnonymous, isRequired)
   394  	result.Extensions = schema.Extensions
   395  
   396  	return
   397  }
   398  
   399  func (t *typeResolver) goTypeName(nm string) string {
   400  	if len(t.knownDefsKept) > 0 {
   401  		// if a definitions package has been defined, already resolved definitions are
   402  		// always resolved against their original package (e.g. "models"), and not the
   403  		// current package.
   404  		// This allows complex anonymous extra schemas to reuse known definitions generated in another package.
   405  		if _, ok := t.knownDefsKept[nm]; ok {
   406  			return strings.Join([]string{t.keepDefinitionsPkg, swag.ToGoName(nm)}, ".")
   407  		}
   408  	}
   409  
   410  	if t.ModelsPackage == "" {
   411  		return swag.ToGoName(nm)
   412  	}
   413  	if _, ok := t.KnownDefs[nm]; ok {
   414  		return strings.Join([]string{t.ModelsPackage, swag.ToGoName(nm)}, ".")
   415  	}
   416  	return swag.ToGoName(nm)
   417  }
   418  
   419  func (t *typeResolver) resolveObject(schema *spec.Schema, isAnonymous bool) (result resolvedType, err error) {
   420  	debugLog("resolving object %s (anon: %t, req: %t)", t.ModelName, isAnonymous, false)
   421  
   422  	result.IsAnonymous = isAnonymous
   423  
   424  	result.IsBaseType = schema.Discriminator != ""
   425  	if !isAnonymous {
   426  		result.SwaggerType = object
   427  		tpe, pkg, alias := knownDefGoType(t.ModelName, *schema, t.goTypeName)
   428  		result.GoType = tpe
   429  		result.Pkg = pkg
   430  		result.PkgAlias = alias
   431  	}
   432  	if len(schema.AllOf) > 0 {
   433  		result.GoType = t.goTypeName(t.ModelName)
   434  		result.IsComplexObject = true
   435  		var isNullable bool
   436  		for _, p := range schema.AllOf {
   437  			if t.IsNullable(&p) {
   438  				isNullable = true
   439  			}
   440  		}
   441  		result.IsNullable = isNullable
   442  		result.SwaggerType = object
   443  		return
   444  	}
   445  
   446  	// if this schema has properties, build a map of property name to
   447  	// resolved type, this should also flag the object as anonymous,
   448  	// when a ref is found, the anonymous flag will be reset
   449  	if len(schema.Properties) > 0 {
   450  		result.IsNullable = t.IsNullable(schema)
   451  		result.IsComplexObject = true
   452  		// no return here, still need to check for additional properties
   453  	}
   454  
   455  	// account for additional properties
   456  	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
   457  		sch := schema.AdditionalProperties.Schema
   458  		et, er := t.ResolveSchema(sch, sch.Ref.String() == "", false)
   459  		if er != nil {
   460  			err = er
   461  			return
   462  		}
   463  
   464  		result.IsMap = !result.IsComplexObject
   465  
   466  		result.SwaggerType = object
   467  
   468  		// only complex map elements are nullable (when not forced by x-nullable)
   469  		// TODO: figure out if required to check when not discriminated like arrays?
   470  		et.IsNullable = t.isNullable(schema.AdditionalProperties.Schema)
   471  		if et.IsNullable {
   472  			result.GoType = "map[string]*" + et.GoType
   473  		} else {
   474  			result.GoType = "map[string]" + et.GoType
   475  		}
   476  
   477  		// Resolving nullability conflicts for:
   478  		// - map[][]...[]{items}
   479  		// - map[]{aliased type}
   480  		//
   481  		// when IsMap is true and the type is a distinct definition,
   482  		// aliased type or anonymous construct generated independently.
   483  		//
   484  		// IsMapNullOverride is to be handled by the generator for special cases
   485  		// where the map element is considered non nullable and the element itself is.
   486  		//
   487  		// This allows to appreciate nullability according to the context
   488  		needsOverride := result.IsMap && (et.IsArray || (sch.Ref.String() != "" || et.IsAliased || et.IsAnonymous))
   489  
   490  		if needsOverride {
   491  			var er error
   492  			if et.IsArray {
   493  				var it resolvedType
   494  				s := sch
   495  				// resolve the last items after nested arrays
   496  				for s.Items != nil && s.Items.Schema != nil {
   497  					it, er = t.ResolveSchema(s.Items.Schema, sch.Ref.String() == "", false)
   498  					if er != nil {
   499  						return
   500  					}
   501  					s = s.Items.Schema
   502  				}
   503  				// mark an override when nullable status conflicts, i.e. when the original type is not already nullable
   504  				if !it.IsAnonymous || it.IsAnonymous && it.IsNullable {
   505  					result.IsMapNullOverride = true
   506  				}
   507  			} else {
   508  				// this locks the generator on the local nullability status
   509  				result.IsMapNullOverride = true
   510  			}
   511  		}
   512  
   513  		t.inferAliasing(&result, schema, isAnonymous, false)
   514  		result.ElemType = &et
   515  		return
   516  	}
   517  
   518  	if len(schema.Properties) > 0 {
   519  		return
   520  	}
   521  
   522  	// an object without property and without AdditionalProperties schema is rendered as interface{}
   523  	result.GoType = iface
   524  	result.IsMap = true
   525  	result.SwaggerType = object
   526  	result.IsNullable = false
   527  	result.IsInterface = len(schema.Properties) == 0
   528  	return
   529  }
   530  
   531  // nullableBool makes a boolean a pointer when we want to distinguish the zero value from no value set.
   532  // This is the case when:
   533  // - a x-nullable extension says so in the spec
   534  // - it is **not** a read-only property
   535  // - it is a required property
   536  // - it has a default value
   537  func nullableBool(schema *spec.Schema, isRequired bool) bool {
   538  	if nullable := nullableExtension(schema.Extensions); nullable != nil {
   539  		return *nullable
   540  	}
   541  	required := isRequired && schema.Default == nil && !schema.ReadOnly
   542  	optional := !isRequired && (schema.Default != nil || schema.ReadOnly)
   543  
   544  	return required || optional
   545  }
   546  
   547  // nullableNumber makes a number a pointer when we want to distinguish the zero value from no value set.
   548  // This is the case when:
   549  // - a x-nullable extension says so in the spec
   550  // - it is **not** a read-only property
   551  // - it is a required property
   552  // - boundaries defines the zero value as a valid value:
   553  //   - there is a non-exclusive boundary set at the zero value of the type
   554  //   - the [min,max] range crosses the zero value of the type
   555  func nullableNumber(schema *spec.Schema, isRequired bool) bool {
   556  	if nullable := nullableExtension(schema.Extensions); nullable != nil {
   557  		return *nullable
   558  	}
   559  	hasDefault := schema.Default != nil && !swag.IsZero(schema.Default)
   560  
   561  	isMin := schema.Minimum != nil && (*schema.Minimum != 0 || schema.ExclusiveMinimum)
   562  	bcMin := schema.Minimum != nil && *schema.Minimum == 0 && !schema.ExclusiveMinimum
   563  	isMax := schema.Minimum == nil && (schema.Maximum != nil && (*schema.Maximum != 0 || schema.ExclusiveMaximum))
   564  	bcMax := schema.Maximum != nil && *schema.Maximum == 0 && !schema.ExclusiveMaximum
   565  	isMinMax := (schema.Minimum != nil && schema.Maximum != nil && *schema.Minimum < *schema.Maximum)
   566  	bcMinMax := (schema.Minimum != nil && schema.Maximum != nil && (*schema.Minimum < 0 && 0 < *schema.Maximum))
   567  
   568  	nullable := !schema.ReadOnly && (isRequired || (hasDefault && !(isMin || isMax || isMinMax)) || bcMin || bcMax || bcMinMax)
   569  	return nullable
   570  }
   571  
   572  // nullableString makes a string nullable when we want to distinguish the zero value from no value set.
   573  // This is the case when:
   574  // - a x-nullable extension says so in the spec
   575  // - it is **not** a read-only property
   576  // - it is a required property
   577  // - it has a MinLength property set to 0
   578  // - it has a default other than "" (the zero for strings) and no MinLength or zero MinLength
   579  func nullableString(schema *spec.Schema, isRequired bool) bool {
   580  	if nullable := nullableExtension(schema.Extensions); nullable != nil {
   581  		return *nullable
   582  	}
   583  	hasDefault := schema.Default != nil && !swag.IsZero(schema.Default)
   584  
   585  	isMin := schema.MinLength != nil && *schema.MinLength != 0
   586  	bcMin := schema.MinLength != nil && *schema.MinLength == 0
   587  
   588  	nullable := !schema.ReadOnly && (isRequired || (hasDefault && !isMin) || bcMin)
   589  	return nullable
   590  }
   591  
   592  func nullableStrfmt(schema *spec.Schema, isRequired bool) bool {
   593  	notBinary := schema.Format != binary
   594  	if nullable := nullableExtension(schema.Extensions); nullable != nil && notBinary {
   595  		return *nullable
   596  	}
   597  	hasDefault := schema.Default != nil && !swag.IsZero(schema.Default)
   598  
   599  	nullable := !schema.ReadOnly && (isRequired || hasDefault)
   600  	return notBinary && nullable
   601  }
   602  
   603  func nullableExtension(ext spec.Extensions) *bool {
   604  	if ext == nil {
   605  		return nil
   606  	}
   607  
   608  	if boolPtr := boolExtension(ext, xNullable); boolPtr != nil {
   609  		return boolPtr
   610  	}
   611  
   612  	return boolExtension(ext, xIsNullable)
   613  }
   614  
   615  func boolExtension(ext spec.Extensions, key string) *bool {
   616  	if v, ok := ext[key]; ok {
   617  		if bb, ok := v.(bool); ok {
   618  			return &bb
   619  		}
   620  	}
   621  	return nil
   622  }
   623  
   624  func (t *typeResolver) ResolveSchema(schema *spec.Schema, isAnonymous, isRequired bool) (result resolvedType, err error) {
   625  	debugLog("resolving schema (anon: %t, req: %t) %s", isAnonymous, isRequired, t.ModelName)
   626  	if schema == nil {
   627  		result.IsInterface = true
   628  		result.GoType = iface
   629  		return
   630  	}
   631  
   632  	tpe := t.firstType(schema)
   633  	defer setIsEmptyOmitted(&result, schema, tpe)
   634  
   635  	var returns bool
   636  	returns, result, err = t.resolveSchemaRef(schema, isRequired)
   637  	if returns {
   638  		if !isAnonymous {
   639  			result.IsMap = false
   640  			result.IsComplexObject = true
   641  			debugLog("not anonymous ref")
   642  		}
   643  		debugLog("returning after ref")
   644  		return
   645  	}
   646  
   647  	// special case of swagger type "file", rendered as io.ReadCloser interface
   648  	if t.firstType(schema) == file {
   649  		result.SwaggerType = file
   650  		result.IsPrimitive = true
   651  		result.IsNullable = false
   652  		result.GoType = formatMapping[str][binary]
   653  		result.IsStream = true
   654  		return
   655  	}
   656  
   657  	returns, result, err = t.resolveFormat(schema, isAnonymous, isRequired)
   658  	if returns {
   659  		debugLog("returning after resolve format: %s", pretty.Sprint(result))
   660  		return
   661  	}
   662  
   663  	result.IsNullable = t.isNullable(schema) || isRequired
   664  
   665  	switch tpe {
   666  	case array:
   667  		result, err = t.resolveArray(schema, isAnonymous, false)
   668  		return
   669  
   670  	case file, number, integer, boolean:
   671  		result.Extensions = schema.Extensions
   672  		result.GoType = typeMapping[tpe]
   673  		result.SwaggerType = tpe
   674  		t.inferAliasing(&result, schema, isAnonymous, isRequired)
   675  
   676  		switch tpe {
   677  		case boolean:
   678  			result.IsPrimitive = true
   679  			result.IsCustomFormatter = false
   680  			result.IsNullable = nullableBool(schema, isRequired)
   681  		case number, integer:
   682  			result.IsPrimitive = true
   683  			result.IsCustomFormatter = false
   684  			result.IsNullable = nullableNumber(schema, isRequired)
   685  		case file:
   686  		}
   687  		return
   688  
   689  	case str:
   690  		result.GoType = str
   691  		result.SwaggerType = str
   692  		t.inferAliasing(&result, schema, isAnonymous, isRequired)
   693  
   694  		result.IsPrimitive = true
   695  		result.IsNullable = nullableString(schema, isRequired)
   696  		result.Extensions = schema.Extensions
   697  
   698  	case object:
   699  		result, err = t.resolveObject(schema, isAnonymous)
   700  		if err != nil {
   701  			return resolvedType{}, err
   702  		}
   703  		result.HasDiscriminator = schema.Discriminator != ""
   704  		return
   705  
   706  	case "null":
   707  		result.GoType = iface
   708  		result.SwaggerType = object
   709  		result.IsNullable = false
   710  		result.IsInterface = true
   711  		return
   712  
   713  	default:
   714  		err = fmt.Errorf("unresolvable: %v (format %q)", schema.Type, schema.Format)
   715  		return
   716  	}
   717  	return result, err
   718  }
   719  
   720  // resolvedType is a swagger type that has been resolved and analyzed for usage
   721  // in a template
   722  type resolvedType struct {
   723  	IsAnonymous       bool
   724  	IsArray           bool
   725  	IsMap             bool
   726  	IsInterface       bool
   727  	IsPrimitive       bool
   728  	IsCustomFormatter bool
   729  	IsAliased         bool
   730  	IsNullable        bool
   731  	IsStream          bool
   732  	IsEmptyOmitted    bool
   733  
   734  	// A tuple gets rendered as an anonymous struct with P{index} as property name
   735  	IsTuple            bool
   736  	HasAdditionalItems bool
   737  
   738  	// A complex object gets rendered as a struct
   739  	IsComplexObject bool
   740  
   741  	// A polymorphic type
   742  	IsBaseType       bool
   743  	HasDiscriminator bool
   744  
   745  	GoType        string
   746  	Pkg           string
   747  	PkgAlias      string
   748  	AliasedType   string
   749  	SwaggerType   string
   750  	SwaggerFormat string
   751  	Extensions    spec.Extensions
   752  
   753  	// The type of the element in a slice or map
   754  	ElemType *resolvedType
   755  
   756  	// IsMapNullOverride indicates that a nullable object is used within an
   757  	// aliased map. In this case, the reference is not rendered with a pointer
   758  	IsMapNullOverride bool
   759  
   760  	// IsSuperAlias indicates that the aliased type is really the same type,
   761  	// e.g. in golang, this translates to: type A = B
   762  	IsSuperAlias bool
   763  }
   764  
   765  func (rt *resolvedType) Zero() string {
   766  	// if type is aliased, provide zero from the aliased type
   767  	if rt.IsAliased {
   768  		if zr, ok := zeroes[rt.AliasedType]; ok {
   769  			return rt.GoType + "(" + zr + ")"
   770  		}
   771  	}
   772  	// zero function provided as native or by strfmt function
   773  	if zr, ok := zeroes[rt.GoType]; ok {
   774  		return zr
   775  	}
   776  	// map and slice initializer
   777  	if rt.IsMap {
   778  		return "make(" + rt.GoType + ", 50)"
   779  	} else if rt.IsArray {
   780  		return "make(" + rt.GoType + ", 0, 50)"
   781  	}
   782  	// object initializer
   783  	if rt.IsTuple || rt.IsComplexObject {
   784  		if rt.IsNullable {
   785  			return "new(" + rt.GoType + ")"
   786  		}
   787  		return rt.GoType + "{}"
   788  	}
   789  	// interface initializer
   790  	if rt.IsInterface {
   791  		return "nil"
   792  	}
   793  
   794  	return ""
   795  }