github.com/kaisawind/go-swagger@v0.19.0/scan/schema.go (about)

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