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