github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/scan/schema.go (about)

     1  //go:build !go1.11
     2  // +build !go1.11
     3  
     4  // Copyright 2015 go-swagger maintainers
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //    http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package scan
    19  
    20  import (
    21  	"encoding/json"
    22  	"fmt"
    23  	"go/ast"
    24  	"log"
    25  	"os"
    26  	"path/filepath"
    27  	"reflect"
    28  	"runtime"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"golang.org/x/tools/go/loader"
    33  
    34  	"github.com/go-openapi/spec"
    35  )
    36  
    37  func addExtension(ve *spec.VendorExtensible, key string, value interface{}) {
    38  	if os.Getenv("SWAGGER_GENERATE_EXTENSION") == "false" {
    39  		return
    40  	}
    41  
    42  	ve.AddExtension(key, value)
    43  }
    44  
    45  type schemaTypable struct {
    46  	schema *spec.Schema
    47  	level  int
    48  }
    49  
    50  func (st schemaTypable) Typed(tpe, format string) {
    51  	st.schema.Typed(tpe, format)
    52  }
    53  
    54  func (st schemaTypable) SetRef(ref spec.Ref) {
    55  	st.schema.Ref = ref
    56  }
    57  
    58  func (st schemaTypable) Schema() *spec.Schema {
    59  	return st.schema
    60  }
    61  
    62  func (st schemaTypable) Items() swaggerTypable {
    63  	if st.schema.Items == nil {
    64  		st.schema.Items = new(spec.SchemaOrArray)
    65  	}
    66  	if st.schema.Items.Schema == nil {
    67  		st.schema.Items.Schema = new(spec.Schema)
    68  	}
    69  
    70  	st.schema.Typed("array", "")
    71  	return schemaTypable{st.schema.Items.Schema, st.level + 1}
    72  }
    73  
    74  func (st schemaTypable) AdditionalProperties() swaggerTypable {
    75  	if st.schema.AdditionalProperties == nil {
    76  		st.schema.AdditionalProperties = new(spec.SchemaOrBool)
    77  	}
    78  	if st.schema.AdditionalProperties.Schema == nil {
    79  		st.schema.AdditionalProperties.Schema = new(spec.Schema)
    80  	}
    81  
    82  	st.schema.Typed("object", "")
    83  	return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1}
    84  }
    85  
    86  func (st schemaTypable) Level() int { return st.level }
    87  
    88  func (st schemaTypable) WithEnum(values ...interface{}) {
    89  	st.schema.WithEnum(values...)
    90  }
    91  
    92  type schemaValidations struct {
    93  	current *spec.Schema
    94  }
    95  
    96  func (sv schemaValidations) SetMaximum(val float64, exclusive bool) {
    97  	sv.current.Maximum = &val
    98  	sv.current.ExclusiveMaximum = exclusive
    99  }
   100  func (sv schemaValidations) SetMinimum(val float64, exclusive bool) {
   101  	sv.current.Minimum = &val
   102  	sv.current.ExclusiveMinimum = exclusive
   103  }
   104  func (sv schemaValidations) SetMultipleOf(val float64)  { sv.current.MultipleOf = &val }
   105  func (sv schemaValidations) SetMinItems(val int64)      { sv.current.MinItems = &val }
   106  func (sv schemaValidations) SetMaxItems(val int64)      { sv.current.MaxItems = &val }
   107  func (sv schemaValidations) SetMinLength(val int64)     { sv.current.MinLength = &val }
   108  func (sv schemaValidations) SetMaxLength(val int64)     { sv.current.MaxLength = &val }
   109  func (sv schemaValidations) SetPattern(val string)      { sv.current.Pattern = val }
   110  func (sv schemaValidations) SetUnique(val bool)         { sv.current.UniqueItems = val }
   111  func (sv schemaValidations) SetDefault(val interface{}) { sv.current.Default = val }
   112  func (sv schemaValidations) SetExample(val interface{}) { sv.current.Example = val }
   113  func (sv schemaValidations) SetEnum(val string) {
   114  	sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Format: sv.current.Format, Type: sv.current.Type[0]})
   115  }
   116  
   117  type schemaDecl struct {
   118  	File      *ast.File
   119  	Decl      *ast.GenDecl
   120  	TypeSpec  *ast.TypeSpec
   121  	GoName    string
   122  	Name      string
   123  	annotated bool
   124  }
   125  
   126  func newSchemaDecl(file *ast.File, decl *ast.GenDecl, ts *ast.TypeSpec) *schemaDecl {
   127  	sd := &schemaDecl{
   128  		File:     file,
   129  		Decl:     decl,
   130  		TypeSpec: ts,
   131  	}
   132  	sd.inferNames()
   133  	return sd
   134  }
   135  
   136  func (sd *schemaDecl) hasAnnotation() bool {
   137  	sd.inferNames()
   138  	return sd.annotated
   139  }
   140  
   141  func (sd *schemaDecl) inferNames() (goName string, name string) {
   142  	if sd.GoName != "" {
   143  		goName, name = sd.GoName, sd.Name
   144  		return
   145  	}
   146  	goName = sd.TypeSpec.Name.Name
   147  	name = goName
   148  	if sd.Decl.Doc != nil {
   149  	DECLS:
   150  		for _, cmt := range sd.Decl.Doc.List {
   151  			for _, ln := range strings.Split(cmt.Text, "\n") {
   152  				matches := rxModelOverride.FindStringSubmatch(ln)
   153  				if len(matches) > 0 {
   154  					sd.annotated = true
   155  				}
   156  				if len(matches) > 1 && len(matches[1]) > 0 {
   157  					name = matches[1]
   158  					break DECLS
   159  				}
   160  			}
   161  		}
   162  	}
   163  	sd.GoName = goName
   164  	sd.Name = name
   165  	return
   166  }
   167  
   168  type schemaParser struct {
   169  	program    *loader.Program
   170  	postDecls  []schemaDecl
   171  	known      map[string]spec.Schema
   172  	discovered *schemaDecl
   173  }
   174  
   175  func newSchemaParser(prog *loader.Program) *schemaParser {
   176  	scp := new(schemaParser)
   177  	scp.program = prog
   178  	scp.known = make(map[string]spec.Schema)
   179  	return scp
   180  }
   181  
   182  func (scp *schemaParser) Parse(gofile *ast.File, target interface{}) error {
   183  	tgt := target.(map[string]spec.Schema)
   184  	for _, decl := range gofile.Decls {
   185  		gd, ok := decl.(*ast.GenDecl)
   186  		if !ok {
   187  			continue
   188  		}
   189  		for _, spc := range gd.Specs {
   190  			if ts, ok := spc.(*ast.TypeSpec); ok {
   191  				sd := newSchemaDecl(gofile, gd, ts)
   192  				if err := scp.parseDecl(tgt, sd); err != nil {
   193  					return err
   194  				}
   195  			}
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  func (scp *schemaParser) parseDecl(definitions map[string]spec.Schema, decl *schemaDecl) error {
   202  	// check if there is a swagger:model tag that is followed by a word,
   203  	// this word is the type name for swagger
   204  	// the package and type are recorded in the extensions
   205  	// once type name is found convert it to a schema, by looking up the schema in the
   206  	// definitions dictionary that got passed into this parse method
   207  
   208  	// if our schemaParser is parsing a discovered schemaDecl and it does not match
   209  	// the current schemaDecl we can skip parsing.
   210  	if scp.discovered != nil && scp.discovered.Name != decl.Name {
   211  		return nil
   212  	}
   213  
   214  	decl.inferNames()
   215  	schema := definitions[decl.Name]
   216  	schPtr := &schema
   217  
   218  	// analyze doc comment for the model
   219  	sp := new(sectionedParser)
   220  	sp.setTitle = func(lines []string) { schema.Title = joinDropLast(lines) }
   221  	sp.setDescription = func(lines []string) { schema.Description = joinDropLast(lines) }
   222  	if err := sp.Parse(decl.Decl.Doc); err != nil {
   223  		return err
   224  	}
   225  
   226  	// if the type is marked to ignore, just return
   227  	if sp.ignored {
   228  		return nil
   229  	}
   230  
   231  	// analyze struct body for fields etc
   232  	// each exported struct field:
   233  	// * gets a type mapped to a go primitive
   234  	// * perhaps gets a format
   235  	// * has to document the validations that apply for the type and the field
   236  	// * when the struct field points to a model it becomes a ref: #/definitions/ModelName
   237  	// * the first line of the comment is the title
   238  	// * the following lines are the description
   239  	switch tpe := decl.TypeSpec.Type.(type) {
   240  	case *ast.StructType:
   241  		if err := scp.parseStructType(decl.File, schPtr, tpe, make(map[string]string)); err != nil {
   242  			return err
   243  		}
   244  	case *ast.InterfaceType:
   245  		if err := scp.parseInterfaceType(decl.File, schPtr, tpe, make(map[string]string)); err != nil {
   246  			return err
   247  		}
   248  	case *ast.Ident:
   249  		prop := &schemaTypable{schPtr, 0}
   250  		if strfmtName, ok := strfmtName(decl.Decl.Doc); ok {
   251  			prop.Typed("string", strfmtName)
   252  		} else {
   253  			if err := scp.parseNamedType(decl.File, tpe, prop); err != nil {
   254  				return err
   255  			}
   256  		}
   257  		if enumName, ok := enumName(decl.Decl.Doc); ok {
   258  			var enumValues = getEnumValues(decl.File, enumName)
   259  			if len(enumValues) > 0 {
   260  				var typeName = reflect.TypeOf(enumValues[0]).String()
   261  				prop.WithEnum(enumValues...)
   262  
   263  				err := swaggerSchemaForType(typeName, prop)
   264  				if err != nil {
   265  					return fmt.Errorf("file %s, error is: %v", decl.File.Name, err)
   266  				}
   267  			}
   268  		}
   269  	case *ast.SelectorExpr:
   270  		prop := &schemaTypable{schPtr, 0}
   271  		if strfmtName, ok := strfmtName(decl.Decl.Doc); ok {
   272  			prop.Typed("string", strfmtName)
   273  		} else {
   274  			if err := scp.parseNamedType(decl.File, tpe, prop); err != nil {
   275  				return err
   276  			}
   277  		}
   278  
   279  	case *ast.ArrayType:
   280  		prop := &schemaTypable{schPtr, 0}
   281  		if strfmtName, ok := strfmtName(decl.Decl.Doc); ok {
   282  			prop.Items().Typed("string", strfmtName)
   283  		} else {
   284  			if err := scp.parseNamedType(decl.File, tpe, &schemaTypable{schPtr, 0}); err != nil {
   285  				return err
   286  			}
   287  		}
   288  
   289  	case *ast.MapType:
   290  		prop := &schemaTypable{schPtr, 0}
   291  		if strfmtName, ok := strfmtName(decl.Decl.Doc); ok {
   292  			prop.AdditionalProperties().Typed("string", strfmtName)
   293  		} else {
   294  			if err := scp.parseNamedType(decl.File, tpe, &schemaTypable{schPtr, 0}); err != nil {
   295  				return err
   296  			}
   297  		}
   298  	default:
   299  		log.Printf("WARNING: Missing parser for a %T, skipping model: %s\n", tpe, decl.Name)
   300  		return nil
   301  	}
   302  
   303  	if schPtr.Ref.String() == "" {
   304  		if decl.Name != decl.GoName {
   305  			addExtension(&schPtr.VendorExtensible, "x-go-name", decl.GoName)
   306  		}
   307  		for _, pkgInfo := range scp.program.AllPackages {
   308  			if pkgInfo.Importable {
   309  				for _, fil := range pkgInfo.Files {
   310  					if fil.Pos() == decl.File.Pos() {
   311  						addExtension(&schPtr.VendorExtensible, "x-go-package", pkgInfo.Pkg.Path())
   312  					}
   313  				}
   314  			}
   315  		}
   316  	}
   317  	definitions[decl.Name] = schema
   318  	return nil
   319  }
   320  
   321  func (scp *schemaParser) parseNamedType(gofile *ast.File, expr ast.Expr, prop swaggerTypable) error {
   322  	switch ftpe := expr.(type) {
   323  	case *ast.Ident: // simple value
   324  		pkg, err := scp.packageForFile(gofile, ftpe)
   325  		if err != nil {
   326  			return err
   327  		}
   328  		return scp.parseIdentProperty(pkg, ftpe, prop)
   329  
   330  	case *ast.StarExpr: // pointer to something, optional by default
   331  		if err := scp.parseNamedType(gofile, ftpe.X, prop); err != nil {
   332  			return err
   333  		}
   334  
   335  	case *ast.ArrayType: // slice type
   336  		if err := scp.parseNamedType(gofile, ftpe.Elt, prop.Items()); err != nil {
   337  			return err
   338  		}
   339  
   340  	case *ast.StructType:
   341  		schema := prop.Schema()
   342  		if schema == nil {
   343  			return fmt.Errorf("items doesn't support embedded structs")
   344  		}
   345  		return scp.parseStructType(gofile, prop.Schema(), ftpe, make(map[string]string))
   346  
   347  	case *ast.SelectorExpr:
   348  		err := scp.typeForSelector(gofile, ftpe, prop)
   349  		return err
   350  
   351  	case *ast.MapType:
   352  		// check if key is a string type, if not print a message
   353  		// and skip the map property. Only maps with string keys can go into additional properties
   354  		sch := prop.Schema()
   355  		if sch == nil {
   356  			return fmt.Errorf("items doesn't support maps")
   357  		}
   358  		if keyIdent, ok := ftpe.Key.(*ast.Ident); sch != nil && ok {
   359  			if keyIdent.Name == "string" {
   360  				if sch.AdditionalProperties == nil {
   361  					sch.AdditionalProperties = new(spec.SchemaOrBool)
   362  				}
   363  				sch.AdditionalProperties.Allows = false
   364  				if sch.AdditionalProperties.Schema == nil {
   365  					sch.AdditionalProperties.Schema = new(spec.Schema)
   366  				}
   367  				if err := scp.parseNamedType(gofile, ftpe.Value, schemaTypable{sch.AdditionalProperties.Schema, 0}); err != nil {
   368  					return err
   369  				}
   370  				sch.Typed("object", "")
   371  			}
   372  		}
   373  
   374  	case *ast.InterfaceType:
   375  		prop.Schema().Typed("object", "")
   376  	default:
   377  		pos := "unknown file:unknown position"
   378  		if scp != nil {
   379  			if scp.program != nil {
   380  				if scp.program.Fset != nil {
   381  					pos = scp.program.Fset.Position(expr.Pos()).String()
   382  				}
   383  			}
   384  		}
   385  		return fmt.Errorf("expr (%s) is unsupported for a schema", pos)
   386  	}
   387  	return nil
   388  }
   389  
   390  func (scp *schemaParser) parseEmbeddedType(gofile *ast.File, schema *spec.Schema, expr ast.Expr, seenPreviously map[string]string) error {
   391  	switch tpe := expr.(type) {
   392  	case *ast.Ident:
   393  		// do lookup of type
   394  		// take primitives into account, they should result in an error for swagger
   395  		pkg, err := scp.packageForFile(gofile, tpe)
   396  		if err != nil {
   397  			return err
   398  		}
   399  		file, _, ts, err := findSourceFile(pkg, tpe.Name)
   400  		if err != nil {
   401  			return err
   402  		}
   403  
   404  		switch st := ts.Type.(type) {
   405  		case *ast.StructType:
   406  			return scp.parseStructType(file, schema, st, seenPreviously)
   407  		case *ast.InterfaceType:
   408  			return scp.parseInterfaceType(file, schema, st, seenPreviously)
   409  		default:
   410  			prop := &schemaTypable{schema, 0}
   411  			return scp.parseNamedType(gofile, st, prop)
   412  		}
   413  
   414  	case *ast.SelectorExpr:
   415  		// look up package, file and then type
   416  		pkg, err := scp.packageForSelector(gofile, tpe.X)
   417  		if err != nil {
   418  			return fmt.Errorf("embedded struct: %v", err)
   419  		}
   420  		file, _, ts, err := findSourceFile(pkg, tpe.Sel.Name)
   421  		if err != nil {
   422  			return fmt.Errorf("embedded struct: %v", err)
   423  		}
   424  		if st, ok := ts.Type.(*ast.StructType); ok {
   425  			return scp.parseStructType(file, schema, st, seenPreviously)
   426  		}
   427  		if st, ok := ts.Type.(*ast.InterfaceType); ok {
   428  			return scp.parseInterfaceType(file, schema, st, seenPreviously)
   429  		}
   430  	case *ast.StarExpr:
   431  		return scp.parseEmbeddedType(gofile, schema, tpe.X, seenPreviously)
   432  	default:
   433  		return fmt.Errorf(
   434  			"parseEmbeddedType: unsupported type %v at position %#v",
   435  			expr,
   436  			scp.program.Fset.Position(tpe.Pos()),
   437  		)
   438  	}
   439  	return fmt.Errorf("unable to resolve embedded struct for: %v", expr)
   440  }
   441  
   442  func (scp *schemaParser) parseAllOfMember(gofile *ast.File, schema *spec.Schema, expr ast.Expr, seenPreviously map[string]string) error {
   443  	// TODO: check if struct is annotated with swagger:model or known in the definitions otherwise
   444  	var pkg *loader.PackageInfo
   445  	var file *ast.File
   446  	var gd *ast.GenDecl
   447  	var ts *ast.TypeSpec
   448  	var err error
   449  
   450  	switch tpe := expr.(type) {
   451  	case *ast.Ident:
   452  		// do lookup of type
   453  		// take primitives into account, they should result in an error for swagger
   454  		pkg, err = scp.packageForFile(gofile, tpe)
   455  		if err != nil {
   456  			return err
   457  		}
   458  		file, gd, ts, err = findSourceFile(pkg, tpe.Name)
   459  		if err != nil {
   460  			return err
   461  		}
   462  
   463  	case *ast.SelectorExpr:
   464  		// look up package, file and then type
   465  		pkg, err = scp.packageForSelector(gofile, tpe.X)
   466  		if err != nil {
   467  			return fmt.Errorf("embedded struct: %v", err)
   468  		}
   469  		file, gd, ts, err = findSourceFile(pkg, tpe.Sel.Name)
   470  		if err != nil {
   471  			return fmt.Errorf("embedded struct: %v", err)
   472  		}
   473  	default:
   474  		return fmt.Errorf("unable to resolve allOf member for: %v", expr)
   475  	}
   476  
   477  	sd := newSchemaDecl(file, gd, ts)
   478  	if sd.hasAnnotation() && pkg.String() != "time" && ts.Name.Name != "Time" {
   479  		ref, err := spec.NewRef("#/definitions/" + sd.Name)
   480  		if err != nil {
   481  			return err
   482  		}
   483  		schema.Ref = ref
   484  		scp.postDecls = append(scp.postDecls, *sd)
   485  	} else {
   486  		switch st := ts.Type.(type) {
   487  		case *ast.StructType:
   488  			return scp.parseStructType(file, schema, st, seenPreviously)
   489  		case *ast.InterfaceType:
   490  			return scp.parseInterfaceType(file, schema, st, seenPreviously)
   491  		}
   492  	}
   493  
   494  	return nil
   495  }
   496  func (scp *schemaParser) parseInterfaceType(gofile *ast.File, bschema *spec.Schema, tpe *ast.InterfaceType, seenPreviously map[string]string) error {
   497  	if tpe.Methods == nil {
   498  		return nil
   499  	}
   500  
   501  	// first check if this has embedded interfaces, if so make sure to refer to those by ref
   502  	// when they are decorated with an allOf annotation
   503  	// go over the method list again and this time collect the nullary methods and parse the comments
   504  	// as if they are properties on a struct
   505  	var schema *spec.Schema
   506  	seenProperties := seenPreviously
   507  	hasAllOf := false
   508  
   509  	for _, fld := range tpe.Methods.List {
   510  		if len(fld.Names) == 0 {
   511  			// if this created an allOf property then we have to rejig the schema var
   512  			// because all the fields collected that aren't from embedded structs should go in
   513  			// their own proper schema
   514  			// first process embedded structs in order of embedding
   515  			if allOfMember(fld.Doc) {
   516  				hasAllOf = true
   517  				if schema == nil {
   518  					schema = new(spec.Schema)
   519  				}
   520  				var newSch spec.Schema
   521  				// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   522  				// otherwise the fields will just be included as normal properties
   523  				if err := scp.parseAllOfMember(gofile, &newSch, fld.Type, seenProperties); err != nil {
   524  					return err
   525  				}
   526  
   527  				if fld.Doc != nil {
   528  					for _, cmt := range fld.Doc.List {
   529  						for _, ln := range strings.Split(cmt.Text, "\n") {
   530  							matches := rxAllOf.FindStringSubmatch(ln)
   531  							ml := len(matches)
   532  							if ml > 1 {
   533  								mv := matches[ml-1]
   534  								if mv != "" {
   535  									addExtension(&bschema.VendorExtensible, "x-class", mv)
   536  								}
   537  							}
   538  						}
   539  					}
   540  				}
   541  
   542  				bschema.AllOf = append(bschema.AllOf, newSch)
   543  				continue
   544  			}
   545  
   546  			var newSch spec.Schema
   547  			// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   548  			// otherwise the fields will just be included as normal properties
   549  			if err := scp.parseEmbeddedType(gofile, &newSch, fld.Type, seenProperties); err != nil {
   550  				return err
   551  			}
   552  			bschema.AllOf = append(bschema.AllOf, newSch)
   553  			hasAllOf = true
   554  		}
   555  	}
   556  
   557  	if schema == nil {
   558  		schema = bschema
   559  	}
   560  	// then add and possibly override values
   561  	if schema.Properties == nil {
   562  		schema.Properties = make(map[string]spec.Schema)
   563  	}
   564  	schema.Typed("object", "")
   565  	for _, fld := range tpe.Methods.List {
   566  		if mtpe, ok := fld.Type.(*ast.FuncType); ok && mtpe.Params.NumFields() == 0 && mtpe.Results.NumFields() == 1 {
   567  			gnm := fld.Names[0].Name
   568  			nm := gnm
   569  			if fld.Doc != nil {
   570  				for _, cmt := range fld.Doc.List {
   571  					for _, ln := range strings.Split(cmt.Text, "\n") {
   572  						matches := rxName.FindStringSubmatch(ln)
   573  						ml := len(matches)
   574  						if ml > 1 {
   575  							nm = matches[ml-1]
   576  						}
   577  					}
   578  				}
   579  			}
   580  
   581  			ps := schema.Properties[nm]
   582  			if err := parseProperty(scp, gofile, mtpe.Results.List[0].Type, schemaTypable{&ps, 0}); err != nil {
   583  				return err
   584  			}
   585  
   586  			if err := scp.createParser(nm, schema, &ps, fld).Parse(fld.Doc); err != nil {
   587  				return err
   588  			}
   589  
   590  			if ps.Ref.String() == "" && nm != gnm {
   591  				addExtension(&ps.VendorExtensible, "x-go-name", gnm)
   592  			}
   593  			seenProperties[nm] = gnm
   594  			schema.Properties[nm] = ps
   595  		}
   596  
   597  	}
   598  	if schema != nil && hasAllOf && len(schema.Properties) > 0 {
   599  		bschema.AllOf = append(bschema.AllOf, *schema)
   600  	}
   601  	for k := range schema.Properties {
   602  		if _, ok := seenProperties[k]; !ok {
   603  			delete(schema.Properties, k)
   604  		}
   605  	}
   606  	return nil
   607  }
   608  
   609  func (scp *schemaParser) parseStructType(gofile *ast.File, bschema *spec.Schema, tpe *ast.StructType, seenPreviously map[string]string) error {
   610  	if tpe.Fields == nil {
   611  		return nil
   612  	}
   613  	var schema *spec.Schema
   614  	seenProperties := seenPreviously
   615  	hasAllOf := false
   616  
   617  	for _, fld := range tpe.Fields.List {
   618  		if len(fld.Names) == 0 {
   619  			// if the field is annotated with swagger:ignore, ignore it
   620  			if ignored(fld.Doc) {
   621  				continue
   622  			}
   623  
   624  			_, ignore, _, err := parseJSONTag(fld)
   625  			if err != nil {
   626  				return err
   627  			}
   628  			if ignore {
   629  				continue
   630  			}
   631  
   632  			// if this created an allOf property then we have to rejig the schema var
   633  			// because all the fields collected that aren't from embedded structs should go in
   634  			// their own proper schema
   635  			// first process embedded structs in order of embedding
   636  			if allOfMember(fld.Doc) {
   637  				hasAllOf = true
   638  				if schema == nil {
   639  					schema = new(spec.Schema)
   640  				}
   641  				var newSch spec.Schema
   642  				// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   643  				// otherwise the fields will just be included as normal properties
   644  				if err := scp.parseAllOfMember(gofile, &newSch, fld.Type, seenProperties); err != nil {
   645  					return err
   646  				}
   647  
   648  				if fld.Doc != nil {
   649  					for _, cmt := range fld.Doc.List {
   650  						for _, ln := range strings.Split(cmt.Text, "\n") {
   651  							matches := rxAllOf.FindStringSubmatch(ln)
   652  							ml := len(matches)
   653  							if ml > 1 {
   654  								mv := matches[ml-1]
   655  								if mv != "" {
   656  									addExtension(&bschema.VendorExtensible, "x-class", mv)
   657  								}
   658  							}
   659  						}
   660  					}
   661  				}
   662  
   663  				bschema.AllOf = append(bschema.AllOf, newSch)
   664  				continue
   665  			}
   666  			if schema == nil {
   667  				schema = bschema
   668  			}
   669  
   670  			// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   671  			// otherwise the fields will just be included as normal properties
   672  			if err := scp.parseEmbeddedType(gofile, schema, fld.Type, seenProperties); err != nil {
   673  				return err
   674  			}
   675  		}
   676  	}
   677  	if schema == nil {
   678  		schema = bschema
   679  	}
   680  
   681  	// then add and possibly override values
   682  	if schema.Properties == nil {
   683  		schema.Properties = make(map[string]spec.Schema)
   684  	}
   685  	schema.Typed("object", "")
   686  	for _, fld := range tpe.Fields.List {
   687  		if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() {
   688  			// if the field is annotated with swagger:ignore, ignore it
   689  			if ignored(fld.Doc) {
   690  				continue
   691  			}
   692  
   693  			gnm := fld.Names[0].Name
   694  			nm, ignore, isString, err := parseJSONTag(fld)
   695  			if err != nil {
   696  				return err
   697  			}
   698  			if ignore {
   699  				for seenTagName, seenFieldName := range seenPreviously {
   700  					if seenFieldName == gnm {
   701  						delete(schema.Properties, seenTagName)
   702  						break
   703  					}
   704  				}
   705  				continue
   706  			}
   707  
   708  			ps := schema.Properties[nm]
   709  			if err := parseProperty(scp, gofile, fld.Type, schemaTypable{&ps, 0}); err != nil {
   710  				return err
   711  			}
   712  			if isString {
   713  				ps.Typed("string", ps.Format)
   714  				ps.Ref = spec.Ref{}
   715  			}
   716  			if strfmtName, ok := strfmtName(fld.Doc); ok {
   717  				ps.Typed("string", strfmtName)
   718  				ps.Ref = spec.Ref{}
   719  			}
   720  
   721  			if err := scp.createParser(nm, schema, &ps, fld).Parse(fld.Doc); err != nil {
   722  				return err
   723  			}
   724  
   725  			if ps.Ref.String() == "" && nm != gnm {
   726  				addExtension(&ps.VendorExtensible, "x-go-name", gnm)
   727  			}
   728  			// we have 2 cases:
   729  			// 1. field with different name override tag
   730  			// 2. field with different name removes tag
   731  			// so we need to save both tag&name
   732  			seenProperties[nm] = gnm
   733  			schema.Properties[nm] = ps
   734  		}
   735  	}
   736  	if schema != nil && hasAllOf && len(schema.Properties) > 0 {
   737  		bschema.AllOf = append(bschema.AllOf, *schema)
   738  	}
   739  	for k := range schema.Properties {
   740  		if _, ok := seenProperties[k]; !ok {
   741  			delete(schema.Properties, k)
   742  		}
   743  	}
   744  	return nil
   745  }
   746  
   747  func schemaVendorExtensibleSetter(meta *spec.Schema) func(json.RawMessage) error {
   748  	return func(jsonValue json.RawMessage) error {
   749  		var jsonData spec.Extensions
   750  		err := json.Unmarshal(jsonValue, &jsonData)
   751  		if err != nil {
   752  			return err
   753  		}
   754  		for k := range jsonData {
   755  			if !rxAllowedExtensions.MatchString(k) {
   756  				return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k)
   757  			}
   758  		}
   759  		meta.Extensions = jsonData
   760  		return nil
   761  	}
   762  }
   763  
   764  func (scp *schemaParser) createParser(nm string, schema, ps *spec.Schema, fld *ast.Field) *sectionedParser {
   765  	sp := new(sectionedParser)
   766  
   767  	schemeType, err := ps.Type.MarshalJSON()
   768  	if err != nil {
   769  		return nil
   770  	}
   771  
   772  	if ps.Ref.String() == "" {
   773  		sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) }
   774  		sp.taggers = []tagParser{
   775  			newSingleLineTagParser("maximum", &setMaximum{schemaValidations{ps}, rxf(rxMaximumFmt, "")}),
   776  			newSingleLineTagParser("minimum", &setMinimum{schemaValidations{ps}, rxf(rxMinimumFmt, "")}),
   777  			newSingleLineTagParser("multipleOf", &setMultipleOf{schemaValidations{ps}, rxf(rxMultipleOfFmt, "")}),
   778  			newSingleLineTagParser("minLength", &setMinLength{schemaValidations{ps}, rxf(rxMinLengthFmt, "")}),
   779  			newSingleLineTagParser("maxLength", &setMaxLength{schemaValidations{ps}, rxf(rxMaxLengthFmt, "")}),
   780  			newSingleLineTagParser("pattern", &setPattern{schemaValidations{ps}, rxf(rxPatternFmt, "")}),
   781  			newSingleLineTagParser("minItems", &setMinItems{schemaValidations{ps}, rxf(rxMinItemsFmt, "")}),
   782  			newSingleLineTagParser("maxItems", &setMaxItems{schemaValidations{ps}, rxf(rxMaxItemsFmt, "")}),
   783  			newSingleLineTagParser("unique", &setUnique{schemaValidations{ps}, rxf(rxUniqueFmt, "")}),
   784  			newSingleLineTagParser("enum", &setEnum{schemaValidations{ps}, rxf(rxEnumFmt, "")}),
   785  			newSingleLineTagParser("default", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}),
   786  			newSingleLineTagParser("type", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}),
   787  			newSingleLineTagParser("example", &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxExampleFmt, "")}),
   788  			newSingleLineTagParser("required", &setRequiredSchema{schema, nm}),
   789  			newSingleLineTagParser("readOnly", &setReadOnlySchema{ps}),
   790  			newSingleLineTagParser("discriminator", &setDiscriminator{schema, nm}),
   791  			newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, schemaVendorExtensibleSetter(ps)), true),
   792  		}
   793  
   794  		itemsTaggers := func(items *spec.Schema, level int) []tagParser {
   795  			schemeType, err := items.Type.MarshalJSON()
   796  			if err != nil {
   797  				return nil
   798  			}
   799  			// the expression is 1-index based not 0-index
   800  			itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1)
   801  			return []tagParser{
   802  				newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{schemaValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}),
   803  				newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{schemaValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}),
   804  				newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{schemaValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}),
   805  				newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{schemaValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}),
   806  				newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{schemaValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}),
   807  				newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{schemaValidations{items}, rxf(rxPatternFmt, itemsPrefix)}),
   808  				newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{schemaValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}),
   809  				newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{schemaValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}),
   810  				newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{schemaValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}),
   811  				newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{schemaValidations{items}, rxf(rxEnumFmt, itemsPrefix)}),
   812  				newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}),
   813  				newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxExampleFmt, itemsPrefix)}),
   814  			}
   815  		}
   816  
   817  		var parseArrayTypes func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error)
   818  		parseArrayTypes = func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) {
   819  			if items == nil || items.Schema == nil {
   820  				return []tagParser{}, nil
   821  			}
   822  			switch iftpe := expr.(type) {
   823  			case *ast.ArrayType:
   824  				eleTaggers := itemsTaggers(items.Schema, level)
   825  				sp.taggers = append(eleTaggers, sp.taggers...)
   826  				otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Schema.Items, level+1)
   827  				if err != nil {
   828  					return nil, err
   829  				}
   830  				return otherTaggers, nil
   831  			case *ast.Ident:
   832  				taggers := []tagParser{}
   833  				if iftpe.Obj == nil {
   834  					taggers = itemsTaggers(items.Schema, level)
   835  				}
   836  				otherTaggers, err := parseArrayTypes(expr, items.Schema.Items, level+1)
   837  				if err != nil {
   838  					return nil, err
   839  				}
   840  				return append(taggers, otherTaggers...), nil
   841  			case *ast.StarExpr:
   842  				otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
   843  				if err != nil {
   844  					return nil, err
   845  				}
   846  				return otherTaggers, nil
   847  			default:
   848  				return nil, fmt.Errorf("unknown field type ele for %q", nm)
   849  			}
   850  		}
   851  		// check if this is a primitive, if so parse the validations from the
   852  		// doc comments of the slice declaration.
   853  		if ftped, ok := fld.Type.(*ast.ArrayType); ok {
   854  			taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0)
   855  			if err != nil {
   856  				return sp
   857  			}
   858  			sp.taggers = append(taggers, sp.taggers...)
   859  		}
   860  
   861  	} else {
   862  		sp.taggers = []tagParser{
   863  			newSingleLineTagParser("required", &setRequiredSchema{schema, nm}),
   864  		}
   865  	}
   866  	return sp
   867  }
   868  
   869  // hasFilePathPrefix reports whether the filesystem path s begins with the
   870  // elements in prefix.
   871  //
   872  // taken from: https://github.com/golang/go/blob/c87520c5981ecdeaa99e7ba636a6088f900c0c75/src/cmd/go/internal/load/path.go#L60-L80
   873  func hasFilePathPrefix(s, prefix string) bool {
   874  	sv := strings.ToUpper(filepath.VolumeName(s))
   875  	pv := strings.ToUpper(filepath.VolumeName(prefix))
   876  	s = s[len(sv):]
   877  	prefix = prefix[len(pv):]
   878  	switch {
   879  	default:
   880  		return false
   881  	case sv != pv:
   882  		return false
   883  	case len(s) == len(prefix):
   884  		return s == prefix
   885  	case len(s) > len(prefix):
   886  		if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
   887  			return strings.HasPrefix(s, prefix)
   888  		}
   889  		return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
   890  	}
   891  }
   892  
   893  func (scp *schemaParser) packageForFile(gofile *ast.File, tpe *ast.Ident) (*loader.PackageInfo, error) {
   894  	fn := scp.program.Fset.File(gofile.Pos()).Name()
   895  	if Debug {
   896  		log.Println("trying for", fn, tpe.Name, tpe.String())
   897  	}
   898  	fa, err := filepath.Abs(fn)
   899  	if err != nil {
   900  		return nil, err
   901  	}
   902  	if Debug {
   903  		log.Println("absolute path", fa)
   904  	}
   905  	var fgp string
   906  	gopath := os.Getenv("GOPATH")
   907  	if gopath == "" {
   908  		gopath = filepath.Join(os.Getenv("HOME"), "go")
   909  	}
   910  	for _, p := range append(filepath.SplitList(gopath), runtime.GOROOT()) {
   911  		pref := filepath.Join(p, "src")
   912  		if hasFilePathPrefix(fa, pref) {
   913  			fgp = filepath.Dir(strings.TrimPrefix(fa, pref))[1:]
   914  			break
   915  		}
   916  	}
   917  	if Debug {
   918  		log.Println("package in gopath", fgp)
   919  	}
   920  	for pkg, pkgInfo := range scp.program.AllPackages {
   921  		if Debug {
   922  			log.Println("inferring for", tpe.Name, "with", gofile.Name.Name, "at", pkg.Path(), "against", filepath.ToSlash(fgp))
   923  		}
   924  		if pkg.Name() == gofile.Name.Name && filepath.ToSlash(fgp) == pkg.Path() {
   925  			return pkgInfo, nil
   926  		}
   927  	}
   928  
   929  	return nil, fmt.Errorf("unable to determine package for %s", fn)
   930  }
   931  
   932  func (scp *schemaParser) packageForSelector(gofile *ast.File, expr ast.Expr) (*loader.PackageInfo, error) {
   933  
   934  	if pth, ok := expr.(*ast.Ident); ok {
   935  		// lookup import
   936  		var selPath string
   937  		for _, imp := range gofile.Imports {
   938  			pv, err := strconv.Unquote(imp.Path.Value)
   939  			if err != nil {
   940  				pv = imp.Path.Value
   941  			}
   942  			if imp.Name != nil {
   943  				if imp.Name.Name == pth.Name {
   944  					selPath = pv
   945  					break
   946  				}
   947  			} else {
   948  				pkg := scp.program.Package(pv)
   949  				if pkg != nil && pth.Name == pkg.Pkg.Name() {
   950  					selPath = pv
   951  					break
   952  				} else {
   953  					parts := strings.Split(pv, "/")
   954  					if len(parts) > 0 && parts[len(parts)-1] == pth.Name {
   955  						selPath = pv
   956  						break
   957  					}
   958  				}
   959  			}
   960  		}
   961  		// find actual struct
   962  		if selPath == "" {
   963  			return nil, fmt.Errorf("no import found for %s", pth.Name)
   964  		}
   965  
   966  		pkg := scp.program.Package(selPath)
   967  		if pkg != nil {
   968  			return pkg, nil
   969  		}
   970  		// TODO: I must admit this made me cry, it's not even a great solution.
   971  		pkg = scp.program.Package("github.com/go-swagger/go-swagger/vendor/" + selPath)
   972  		if pkg != nil {
   973  			return pkg, nil
   974  		}
   975  		for _, info := range scp.program.AllPackages {
   976  			n := info.String()
   977  			path := "/vendor/" + selPath
   978  			if strings.HasSuffix(n, path) {
   979  				pkg = scp.program.Package(n)
   980  				return pkg, nil
   981  			}
   982  		}
   983  	}
   984  	return nil, fmt.Errorf("can't determine selector path from %v", expr)
   985  }
   986  
   987  func (scp *schemaParser) makeRef(file *ast.File, pkg *loader.PackageInfo, gd *ast.GenDecl, ts *ast.TypeSpec, prop swaggerTypable) error {
   988  	sd := newSchemaDecl(file, gd, ts)
   989  	sd.inferNames()
   990  	// make an exception for time.Time because this is a well-known string format
   991  	if sd.Name == "Time" && pkg.String() == "time" {
   992  		return nil
   993  	}
   994  	ref, err := spec.NewRef("#/definitions/" + sd.Name)
   995  	if err != nil {
   996  		return err
   997  	}
   998  	prop.SetRef(ref)
   999  	scp.postDecls = append(scp.postDecls, *sd)
  1000  	return nil
  1001  }
  1002  
  1003  func (scp *schemaParser) parseIdentProperty(pkg *loader.PackageInfo, expr *ast.Ident, prop swaggerTypable) error {
  1004  	// before proceeding make an exception to time.Time because it is a well known string format
  1005  	if pkg.String() == "time" && expr.String() == "Time" {
  1006  		prop.Typed("string", "date-time")
  1007  		return nil
  1008  	}
  1009  
  1010  	// find the file this selector points to
  1011  	file, gd, ts, err := findSourceFile(pkg, expr.Name)
  1012  
  1013  	if err != nil {
  1014  		err := swaggerSchemaForType(expr.Name, prop)
  1015  		if err != nil {
  1016  			return fmt.Errorf("package %s, error is: %v", pkg.String(), err)
  1017  		}
  1018  		return nil
  1019  	}
  1020  
  1021  	if at, ok := ts.Type.(*ast.ArrayType); ok {
  1022  		// the swagger spec defines strfmt base64 as []byte.
  1023  		// in that case we don't actually want to turn it into an array
  1024  		// but we want to turn it into a string
  1025  		if _, ok := at.Elt.(*ast.Ident); ok {
  1026  			if strfmtName, ok := strfmtName(gd.Doc); ok {
  1027  				prop.Typed("string", strfmtName)
  1028  				return nil
  1029  			}
  1030  		}
  1031  		// this is a selector, so most likely not base64
  1032  		if strfmtName, ok := strfmtName(gd.Doc); ok {
  1033  			prop.Items().Typed("string", strfmtName)
  1034  			return nil
  1035  		}
  1036  	}
  1037  
  1038  	// look at doc comments for swagger:strfmt [name]
  1039  	// when found this is the format name, create a schema with that name
  1040  	if strfmtName, ok := strfmtName(gd.Doc); ok {
  1041  		prop.Typed("string", strfmtName)
  1042  		return nil
  1043  	}
  1044  
  1045  	if enumName, ok := enumName(gd.Doc); ok {
  1046  		var enumValues = getEnumValues(file, enumName)
  1047  		if len(enumValues) > 0 {
  1048  			prop.WithEnum(enumValues...)
  1049  			var typeName = reflect.TypeOf(enumValues[0]).String()
  1050  			err := swaggerSchemaForType(typeName, prop)
  1051  			if err != nil {
  1052  				return fmt.Errorf("file %s, error is: %v", file.Name, err)
  1053  			}
  1054  		}
  1055  	}
  1056  
  1057  	if defaultName, ok := defaultName(gd.Doc); ok {
  1058  		log.Println(defaultName)
  1059  		return nil
  1060  	}
  1061  
  1062  	if typeName, ok := typeName(gd.Doc); ok {
  1063  		_ = swaggerSchemaForType(typeName, prop)
  1064  		return nil
  1065  	}
  1066  
  1067  	if isAliasParam(prop) || aliasParam(gd.Doc) {
  1068  		itype, ok := ts.Type.(*ast.Ident)
  1069  		if ok {
  1070  			err := swaggerSchemaForType(itype.Name, prop)
  1071  			if err == nil {
  1072  				return nil
  1073  			}
  1074  		}
  1075  	}
  1076  	switch tpe := ts.Type.(type) {
  1077  	case *ast.ArrayType:
  1078  		return scp.makeRef(file, pkg, gd, ts, prop)
  1079  	case *ast.StructType:
  1080  		return scp.makeRef(file, pkg, gd, ts, prop)
  1081  
  1082  	case *ast.Ident:
  1083  		return scp.makeRef(file, pkg, gd, ts, prop)
  1084  
  1085  	case *ast.StarExpr:
  1086  		return parseProperty(scp, file, tpe.X, prop)
  1087  
  1088  	case *ast.SelectorExpr:
  1089  		// return scp.refForSelector(file, gd, tpe, ts, prop)
  1090  		return scp.makeRef(file, pkg, gd, ts, prop)
  1091  
  1092  	case *ast.InterfaceType:
  1093  		return scp.makeRef(file, pkg, gd, ts, prop)
  1094  
  1095  	case *ast.MapType:
  1096  		return scp.makeRef(file, pkg, gd, ts, prop)
  1097  
  1098  	default:
  1099  		err := swaggerSchemaForType(expr.Name, prop)
  1100  		if err != nil {
  1101  			return fmt.Errorf("package %s, error is: %v", pkg.String(), err)
  1102  		}
  1103  		return nil
  1104  	}
  1105  
  1106  }
  1107  
  1108  func (scp *schemaParser) typeForSelector(gofile *ast.File, expr *ast.SelectorExpr, prop swaggerTypable) error {
  1109  	pkg, err := scp.packageForSelector(gofile, expr.X)
  1110  	if err != nil {
  1111  		return err
  1112  	}
  1113  
  1114  	return scp.parseIdentProperty(pkg, expr.Sel, prop)
  1115  }
  1116  
  1117  func findSourceFile(pkg *loader.PackageInfo, typeName string) (*ast.File, *ast.GenDecl, *ast.TypeSpec, error) {
  1118  	for _, file := range pkg.Files {
  1119  		for _, decl := range file.Decls {
  1120  			if gd, ok := decl.(*ast.GenDecl); ok {
  1121  				for _, gs := range gd.Specs {
  1122  					if ts, ok := gs.(*ast.TypeSpec); ok {
  1123  						strfmtNme, isStrfmt := strfmtName(gd.Doc)
  1124  						if (isStrfmt && strfmtNme == typeName) || ts.Name != nil && ts.Name.Name == typeName {
  1125  							return file, gd, ts, nil
  1126  						}
  1127  					}
  1128  				}
  1129  			}
  1130  		}
  1131  	}
  1132  	return nil, nil, nil, fmt.Errorf("unable to find %s in %s", typeName, pkg.String())
  1133  }
  1134  
  1135  func allOfMember(comments *ast.CommentGroup) bool {
  1136  	if comments != nil {
  1137  		for _, cmt := range comments.List {
  1138  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1139  				if rxAllOf.MatchString(ln) {
  1140  					return true
  1141  				}
  1142  			}
  1143  		}
  1144  	}
  1145  	return false
  1146  }
  1147  
  1148  func fileParam(comments *ast.CommentGroup) bool {
  1149  	if comments != nil {
  1150  		for _, cmt := range comments.List {
  1151  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1152  				if rxFileUpload.MatchString(ln) {
  1153  					return true
  1154  				}
  1155  			}
  1156  		}
  1157  	}
  1158  	return false
  1159  }
  1160  
  1161  func strfmtName(comments *ast.CommentGroup) (string, bool) {
  1162  	if comments != nil {
  1163  		for _, cmt := range comments.List {
  1164  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1165  				matches := rxStrFmt.FindStringSubmatch(ln)
  1166  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
  1167  					return strings.TrimSpace(matches[1]), true
  1168  				}
  1169  			}
  1170  		}
  1171  	}
  1172  	return "", false
  1173  }
  1174  
  1175  func ignored(comments *ast.CommentGroup) bool {
  1176  	if comments != nil {
  1177  		for _, cmt := range comments.List {
  1178  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1179  				if rxIgnoreOverride.MatchString(ln) {
  1180  					return true
  1181  				}
  1182  			}
  1183  		}
  1184  	}
  1185  	return false
  1186  }
  1187  
  1188  func enumName(comments *ast.CommentGroup) (string, bool) {
  1189  	if comments != nil {
  1190  		for _, cmt := range comments.List {
  1191  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1192  				matches := rxEnum.FindStringSubmatch(ln)
  1193  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
  1194  					return strings.TrimSpace(matches[1]), true
  1195  				}
  1196  			}
  1197  		}
  1198  	}
  1199  	return "", false
  1200  }
  1201  
  1202  func aliasParam(comments *ast.CommentGroup) bool {
  1203  	if comments != nil {
  1204  		for _, cmt := range comments.List {
  1205  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1206  				if rxAlias.MatchString(ln) {
  1207  					return true
  1208  				}
  1209  			}
  1210  		}
  1211  	}
  1212  	return false
  1213  }
  1214  
  1215  func defaultName(comments *ast.CommentGroup) (string, bool) {
  1216  	if comments != nil {
  1217  		for _, cmt := range comments.List {
  1218  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1219  				matches := rxDefault.FindStringSubmatch(ln)
  1220  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
  1221  					return strings.TrimSpace(matches[1]), true
  1222  				}
  1223  			}
  1224  		}
  1225  	}
  1226  	return "", false
  1227  }
  1228  
  1229  func typeName(comments *ast.CommentGroup) (string, bool) {
  1230  
  1231  	var typ string
  1232  	if comments != nil {
  1233  		for _, cmt := range comments.List {
  1234  			for _, ln := range strings.Split(cmt.Text, "\n") {
  1235  				matches := rxType.FindStringSubmatch(ln)
  1236  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
  1237  					typ = strings.TrimSpace(matches[1])
  1238  					return typ, true
  1239  				}
  1240  			}
  1241  		}
  1242  	}
  1243  	return "", false
  1244  }
  1245  
  1246  func parseProperty(scp *schemaParser, gofile *ast.File, fld ast.Expr, prop swaggerTypable) error {
  1247  	switch ftpe := fld.(type) {
  1248  	case *ast.Ident: // simple value
  1249  		pkg, err := scp.packageForFile(gofile, ftpe)
  1250  		if err != nil {
  1251  			return err
  1252  		}
  1253  		return scp.parseIdentProperty(pkg, ftpe, prop)
  1254  
  1255  	case *ast.StarExpr: // pointer to something, optional by default
  1256  		if err := parseProperty(scp, gofile, ftpe.X, prop); err != nil {
  1257  			return err
  1258  		}
  1259  
  1260  	case *ast.ArrayType: // slice type
  1261  		if err := parseProperty(scp, gofile, ftpe.Elt, prop.Items()); err != nil {
  1262  			return err
  1263  		}
  1264  
  1265  	case *ast.StructType:
  1266  		schema := prop.Schema()
  1267  		if schema == nil {
  1268  			return fmt.Errorf("items doesn't support embedded structs")
  1269  		}
  1270  		return scp.parseStructType(gofile, prop.Schema(), ftpe, make(map[string]string))
  1271  
  1272  	case *ast.SelectorExpr:
  1273  		err := scp.typeForSelector(gofile, ftpe, prop)
  1274  		return err
  1275  
  1276  	case *ast.MapType:
  1277  		// check if key is a string type, if not print a message
  1278  		// and skip the map property. Only maps with string keys can go into additional properties
  1279  		sch := prop.Schema()
  1280  		if sch == nil {
  1281  			return fmt.Errorf("items doesn't support maps")
  1282  		}
  1283  		if keyIdent, ok := ftpe.Key.(*ast.Ident); sch != nil && ok {
  1284  			if keyIdent.Name == "string" {
  1285  				if sch.AdditionalProperties == nil {
  1286  					sch.AdditionalProperties = new(spec.SchemaOrBool)
  1287  				}
  1288  				sch.AdditionalProperties.Allows = false
  1289  				if sch.AdditionalProperties.Schema == nil {
  1290  					sch.AdditionalProperties.Schema = new(spec.Schema)
  1291  				}
  1292  				if err := parseProperty(scp, gofile, ftpe.Value, schemaTypable{sch.AdditionalProperties.Schema, 0}); err != nil {
  1293  					return err
  1294  				}
  1295  				sch.Typed("object", "")
  1296  			}
  1297  		}
  1298  
  1299  	case *ast.InterfaceType:
  1300  		prop.Schema().Typed("object", "")
  1301  	default:
  1302  		pos := "unknown file:unknown position"
  1303  		if scp != nil {
  1304  			if scp.program != nil {
  1305  				if scp.program.Fset != nil {
  1306  					pos = scp.program.Fset.Position(fld.Pos()).String()
  1307  				}
  1308  			}
  1309  		}
  1310  		return fmt.Errorf("Expr (%s) is unsupported for a schema", pos)
  1311  	}
  1312  	return nil
  1313  }
  1314  
  1315  func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, err error) {
  1316  	if len(field.Names) > 0 {
  1317  		name = field.Names[0].Name
  1318  	}
  1319  	if field.Tag != nil && len(strings.TrimSpace(field.Tag.Value)) > 0 {
  1320  		tv, err := strconv.Unquote(field.Tag.Value)
  1321  		if err != nil {
  1322  			return name, false, false, err
  1323  		}
  1324  
  1325  		if strings.TrimSpace(tv) != "" {
  1326  			st := reflect.StructTag(tv)
  1327  			jsonParts := strings.Split(st.Get("json"), ",")
  1328  			jsonName := jsonParts[0]
  1329  
  1330  			if len(jsonParts) > 1 && jsonParts[1] == "string" {
  1331  				isString = isFieldStringable(field.Type)
  1332  			}
  1333  
  1334  			if jsonName == "-" {
  1335  				return name, true, isString, nil
  1336  			} else if jsonName != "" {
  1337  				return jsonName, false, isString, nil
  1338  			}
  1339  		}
  1340  	}
  1341  	return name, false, false, nil
  1342  }
  1343  
  1344  // isFieldStringable check if the field type is a scalar. If the field type is
  1345  // *ast.StarExpr and is pointer type, check if it refers to a scalar.
  1346  // Otherwise, the ",string" directive doesn't apply.
  1347  func isFieldStringable(tpe ast.Expr) bool {
  1348  	if ident, ok := tpe.(*ast.Ident); ok {
  1349  		switch ident.Name {
  1350  		case "int", "int8", "int16", "int32", "int64",
  1351  			"uint", "uint8", "uint16", "uint32", "uint64",
  1352  			"float64", "string", "bool":
  1353  			return true
  1354  		}
  1355  	} else if starExpr, ok := tpe.(*ast.StarExpr); ok {
  1356  		return isFieldStringable(starExpr.X)
  1357  	} else {
  1358  		return false
  1359  	}
  1360  	return false
  1361  }