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