github.com/AngusLu/go-swagger@v0.28.0/codescan/schema.go (about)

     1  package codescan
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/types"
     8  	"log"
     9  	"os"
    10  	"reflect"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"golang.org/x/tools/go/ast/astutil"
    15  
    16  	"github.com/go-openapi/spec"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  func addExtension(ve *spec.VendorExtensible, key string, value interface{}) {
    21  	if os.Getenv("SWAGGER_GENERATE_EXTENSION") == "false" {
    22  		return
    23  	}
    24  
    25  	ve.AddExtension(key, value)
    26  }
    27  
    28  type schemaTypable struct {
    29  	schema *spec.Schema
    30  	level  int
    31  }
    32  
    33  func (st schemaTypable) Typed(tpe, format string) {
    34  	st.schema.Typed(tpe, format)
    35  }
    36  
    37  func (st schemaTypable) SetRef(ref spec.Ref) {
    38  	st.schema.Ref = ref
    39  }
    40  
    41  func (st schemaTypable) Schema() *spec.Schema {
    42  	return st.schema
    43  }
    44  
    45  func (st schemaTypable) Items() swaggerTypable {
    46  	if st.schema.Items == nil {
    47  		st.schema.Items = new(spec.SchemaOrArray)
    48  	}
    49  	if st.schema.Items.Schema == nil {
    50  		st.schema.Items.Schema = new(spec.Schema)
    51  	}
    52  
    53  	st.schema.Typed("array", "")
    54  	return schemaTypable{st.schema.Items.Schema, st.level + 1}
    55  }
    56  
    57  func (st schemaTypable) AdditionalProperties() swaggerTypable {
    58  	if st.schema.AdditionalProperties == nil {
    59  		st.schema.AdditionalProperties = new(spec.SchemaOrBool)
    60  	}
    61  	if st.schema.AdditionalProperties.Schema == nil {
    62  		st.schema.AdditionalProperties.Schema = new(spec.Schema)
    63  	}
    64  
    65  	st.schema.Typed("object", "")
    66  	return schemaTypable{st.schema.AdditionalProperties.Schema, st.level + 1}
    67  }
    68  
    69  func (st schemaTypable) Level() int { return st.level }
    70  
    71  func (st schemaTypable) AddExtension(key string, value interface{}) {
    72  	addExtension(&st.schema.VendorExtensible, key, value)
    73  }
    74  
    75  func (st schemaTypable) WithEnum(values ...interface{}) {
    76  	st.schema.WithEnum(values...)
    77  }
    78  
    79  func (st schemaTypable) WithEnumDescription(desc string) {
    80  	if desc == "" {
    81  		return
    82  	}
    83  	st.AddExtension(extEnumDesc, desc)
    84  }
    85  
    86  type schemaValidations struct {
    87  	current *spec.Schema
    88  }
    89  
    90  func (sv schemaValidations) SetMaximum(val float64, exclusive bool) {
    91  	sv.current.Maximum = &val
    92  	sv.current.ExclusiveMaximum = exclusive
    93  }
    94  func (sv schemaValidations) SetMinimum(val float64, exclusive bool) {
    95  	sv.current.Minimum = &val
    96  	sv.current.ExclusiveMinimum = exclusive
    97  }
    98  func (sv schemaValidations) SetMultipleOf(val float64)  { sv.current.MultipleOf = &val }
    99  func (sv schemaValidations) SetMinItems(val int64)      { sv.current.MinItems = &val }
   100  func (sv schemaValidations) SetMaxItems(val int64)      { sv.current.MaxItems = &val }
   101  func (sv schemaValidations) SetMinLength(val int64)     { sv.current.MinLength = &val }
   102  func (sv schemaValidations) SetMaxLength(val int64)     { sv.current.MaxLength = &val }
   103  func (sv schemaValidations) SetPattern(val string)      { sv.current.Pattern = val }
   104  func (sv schemaValidations) SetUnique(val bool)         { sv.current.UniqueItems = val }
   105  func (sv schemaValidations) SetDefault(val interface{}) { sv.current.Default = val }
   106  func (sv schemaValidations) SetExample(val interface{}) { sv.current.Example = val }
   107  func (sv schemaValidations) SetEnum(val string) {
   108  	sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Format: sv.current.Format, Type: sv.current.Type[0]})
   109  }
   110  
   111  type schemaBuilder struct {
   112  	ctx        *scanCtx
   113  	decl       *entityDecl
   114  	GoName     string
   115  	Name       string
   116  	annotated  bool
   117  	discovered []*entityDecl
   118  	postDecls  []*entityDecl
   119  }
   120  
   121  func (s *schemaBuilder) inferNames() (goName string, name string) {
   122  	if s.GoName != "" {
   123  		goName, name = s.GoName, s.Name
   124  		return
   125  	}
   126  
   127  	goName = s.decl.Ident.Name
   128  	name = goName
   129  	defer func() {
   130  		s.GoName = goName
   131  		s.Name = name
   132  	}()
   133  	if s.decl.Comments == nil {
   134  		return
   135  	}
   136  
   137  DECLS:
   138  	for _, cmt := range s.decl.Comments.List {
   139  		for _, ln := range strings.Split(cmt.Text, "\n") {
   140  			matches := rxModelOverride.FindStringSubmatch(ln)
   141  			if len(matches) > 0 {
   142  				s.annotated = true
   143  			}
   144  			if len(matches) > 1 && len(matches[1]) > 0 {
   145  				name = matches[1]
   146  				break DECLS
   147  			}
   148  		}
   149  	}
   150  	return
   151  }
   152  
   153  func (s *schemaBuilder) Build(definitions map[string]spec.Schema) error {
   154  	s.inferNames()
   155  
   156  	schema := definitions[s.Name]
   157  	err := s.buildFromDecl(s.decl, &schema)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	definitions[s.Name] = schema
   162  	return nil
   163  }
   164  
   165  func (s *schemaBuilder) buildFromDecl(decl *entityDecl, schema *spec.Schema) error {
   166  	// analyze doc comment for the model
   167  	sp := new(sectionedParser)
   168  	sp.setTitle = func(lines []string) { schema.Title = joinDropLast(lines) }
   169  	sp.setDescription = func(lines []string) {
   170  		schema.Description = joinDropLast(lines)
   171  		enumDesc := getEnumDesc(schema.VendorExtensible.Extensions)
   172  		if enumDesc != "" {
   173  			schema.Description += "\n" + enumDesc
   174  		}
   175  	}
   176  	if err := sp.Parse(s.decl.Comments); err != nil {
   177  		return err
   178  	}
   179  
   180  	// if the type is marked to ignore, just return
   181  	if sp.ignored {
   182  		return nil
   183  	}
   184  
   185  	switch tpe := s.decl.Type.Obj().Type().(type) {
   186  	case *types.Basic:
   187  		debugLog("basic: %v", tpe.Name())
   188  	case *types.Struct:
   189  		if err := s.buildFromStruct(s.decl, tpe, schema, make(map[string]string)); err != nil {
   190  			return err
   191  		}
   192  	case *types.Interface:
   193  		if err := s.buildFromInterface(s.decl, tpe, schema, make(map[string]string)); err != nil {
   194  			return err
   195  		}
   196  	case *types.Array:
   197  		debugLog("array: %v -> %v", s.decl.Ident.Name, tpe.Elem().String())
   198  	case *types.Slice:
   199  		debugLog("slice: %v -> %v", s.decl.Ident.Name, tpe.Elem().String())
   200  	case *types.Map:
   201  		debugLog("map: %v -> [%v]%v", s.decl.Ident.Name, tpe.Key().String(), tpe.Elem().String())
   202  	case *types.Named:
   203  		o := tpe.Obj()
   204  		if o != nil {
   205  			debugLog("got the named type object: %s.%s | isAlias: %t | exported: %t", o.Pkg().Path(), o.Name(), o.IsAlias(), o.Exported())
   206  			if o.Pkg().Name() == "time" && o.Name() == "Time" {
   207  				schema.Typed("string", "date-time")
   208  				return nil
   209  			}
   210  
   211  			ps := schemaTypable{schema, 0}
   212  			for {
   213  				ti := s.decl.Pkg.TypesInfo.Types[s.decl.Spec.Type]
   214  				if ti.IsBuiltin() {
   215  					break
   216  				}
   217  				if ti.IsType() {
   218  					if err := s.buildFromType(ti.Type, ps); err != nil {
   219  						return err
   220  					}
   221  					break
   222  				}
   223  			}
   224  		}
   225  	default:
   226  		log.Printf("WARNING: Missing parser for a %T, skipping model: %s\n", tpe, s.Name)
   227  		return nil
   228  	}
   229  
   230  	if schema.Ref.String() == "" {
   231  		if s.Name != s.GoName {
   232  			addExtension(&schema.VendorExtensible, "x-go-name", s.GoName)
   233  		}
   234  		addExtension(&schema.VendorExtensible, "x-go-package", s.decl.Type.Obj().Pkg().Path())
   235  	}
   236  	return nil
   237  }
   238  
   239  func (s *schemaBuilder) buildFromType(tpe types.Type, tgt swaggerTypable) error {
   240  	switch titpe := tpe.(type) {
   241  	case *types.Basic:
   242  		return swaggerSchemaForType(titpe.String(), tgt)
   243  	case *types.Pointer:
   244  		return s.buildFromType(titpe.Elem(), tgt)
   245  	case *types.Struct:
   246  		return s.buildFromStruct(s.decl, titpe, tgt.Schema(), make(map[string]string))
   247  	case *types.Interface:
   248  		return s.buildFromInterface(s.decl, titpe, tgt.Schema(), make(map[string]string))
   249  	case *types.Slice:
   250  		return s.buildFromType(titpe.Elem(), tgt.Items())
   251  	case *types.Array:
   252  		return s.buildFromType(titpe.Elem(), tgt.Items())
   253  	case *types.Map:
   254  		// debugLog("map: %v -> [%v]%v", fld.Name(), ftpe.Key().String(), ftpe.Elem().String())
   255  		// check if key is a string type, if not print a message
   256  		// and skip the map property. Only maps with string keys can go into additional properties
   257  		sch := tgt.Schema()
   258  		if sch == nil {
   259  			return errors.New("items doesn't support maps")
   260  		}
   261  		eleProp := schemaTypable{sch, tgt.Level()}
   262  		key := titpe.Key()
   263  		if key.Underlying().String() == "string" {
   264  			if err := s.buildFromType(titpe.Elem(), eleProp.AdditionalProperties()); err != nil {
   265  				return err
   266  			}
   267  			return nil
   268  		}
   269  	case *types.Named:
   270  		tio := titpe.Obj()
   271  		if tio.Pkg() == nil && tio.Name() == "error" {
   272  			return swaggerSchemaForType(tio.Name(), tgt)
   273  		}
   274  		debugLog("named refined type %s.%s", tio.Pkg().Path(), tio.Name())
   275  		pkg, found := s.ctx.PkgForType(tpe)
   276  		if !found {
   277  			// this must be a builtin
   278  			debugLog("skipping because package is nil: %s", tpe.String())
   279  			return nil
   280  		}
   281  		if pkg.Name == "time" && tio.Name() == "Time" {
   282  			tgt.Typed("string", "date-time")
   283  			return nil
   284  		}
   285  		if pkg.PkgPath == "encoding/json" && tio.Name() == "RawMessage" {
   286  			tgt.Typed("object", "")
   287  			return nil
   288  		}
   289  		cmt, hasComments := s.ctx.FindComments(pkg, tio.Name())
   290  		if !hasComments {
   291  			cmt = new(ast.CommentGroup)
   292  		}
   293  
   294  		switch utitpe := tpe.Underlying().(type) {
   295  		case *types.Struct:
   296  			if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
   297  				if decl.Type.Obj().Pkg().Path() == "time" && decl.Type.Obj().Name() == "Time" {
   298  					tgt.Typed("string", "date-time")
   299  					return nil
   300  				}
   301  				if sfnm, isf := strfmtName(cmt); isf {
   302  					tgt.Typed("string", sfnm)
   303  					return nil
   304  				}
   305  				if err := s.makeRef(decl, tgt); err != nil {
   306  					return err
   307  				}
   308  				return nil
   309  			}
   310  		case *types.Interface:
   311  			if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
   312  				if err := s.makeRef(decl, tgt); err != nil {
   313  					return err
   314  				}
   315  				return nil
   316  			}
   317  		case *types.Basic:
   318  			if sfnm, isf := strfmtName(cmt); isf {
   319  				tgt.Typed("string", sfnm)
   320  				return nil
   321  			}
   322  
   323  			if enumName, ok := enumName(cmt); ok {
   324  				enumValues, enumDesces, _ := s.ctx.FindEnumValues(pkg, enumName)
   325  				if len(enumValues) > 0 {
   326  					tgt.WithEnum(enumValues...)
   327  					enumTypeName := reflect.TypeOf(enumValues[0]).String()
   328  					_ = swaggerSchemaForType(enumTypeName, tgt)
   329  				}
   330  				if len(enumDesces) > 0 {
   331  					tgt.WithEnumDescription(strings.Join(enumDesces, "\n"))
   332  				}
   333  				return nil
   334  			}
   335  
   336  			if defaultName, ok := defaultName(cmt); ok {
   337  				debugLog(defaultName)
   338  				return nil
   339  			}
   340  
   341  			if typeName, ok := typeName(cmt); ok {
   342  				_ = swaggerSchemaForType(typeName, tgt)
   343  				return nil
   344  			}
   345  
   346  			if isAliasParam(tgt) || aliasParam(cmt) {
   347  				err := swaggerSchemaForType(utitpe.Name(), tgt)
   348  				if err == nil {
   349  					return nil
   350  				}
   351  			}
   352  			if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
   353  				if err := s.makeRef(decl, tgt); err != nil {
   354  					return err
   355  				}
   356  				return nil
   357  			}
   358  			return swaggerSchemaForType(utitpe.String(), tgt)
   359  		case *types.Array:
   360  			if sfnm, isf := strfmtName(cmt); isf {
   361  				if sfnm == "byte" {
   362  					tgt.Typed("string", sfnm)
   363  					return nil
   364  				}
   365  				tgt.Items().Typed("string", sfnm)
   366  				return nil
   367  			}
   368  			if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
   369  				if err := s.makeRef(decl, tgt); err != nil {
   370  					return err
   371  				}
   372  				return nil
   373  			}
   374  			return s.buildFromType(utitpe.Elem(), tgt.Items())
   375  		case *types.Slice:
   376  			if sfnm, isf := strfmtName(cmt); isf {
   377  				if sfnm == "byte" {
   378  					tgt.Typed("string", sfnm)
   379  					return nil
   380  				}
   381  				tgt.Items().Typed("string", sfnm)
   382  				return nil
   383  			}
   384  			if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
   385  				if err := s.makeRef(decl, tgt); err != nil {
   386  					return err
   387  				}
   388  				return nil
   389  			}
   390  			return s.buildFromType(utitpe.Elem(), tgt.Items())
   391  		case *types.Map:
   392  			if decl, ok := s.ctx.FindModel(tio.Pkg().Path(), tio.Name()); ok {
   393  				if err := s.makeRef(decl, tgt); err != nil {
   394  					return err
   395  				}
   396  				return nil
   397  			}
   398  			return nil
   399  
   400  		default:
   401  			log.Printf("WARNING: can't figure out object type for named type (%T): %v [alias: %t]", tpe.Underlying(), tpe.Underlying(), titpe.Obj().IsAlias())
   402  
   403  			return nil
   404  		}
   405  	default:
   406  		panic(fmt.Sprintf("WARNING: can't determine refined type %s (%T)", titpe.String(), titpe))
   407  	}
   408  
   409  	return nil
   410  }
   411  
   412  func (s *schemaBuilder) buildFromInterface(decl *entityDecl, it *types.Interface, schema *spec.Schema, seen map[string]string) error {
   413  	if it.Empty() {
   414  		schema.Typed("object", "")
   415  		return nil
   416  	}
   417  
   418  	var (
   419  		tgt      *spec.Schema
   420  		hasAllOf bool
   421  	)
   422  
   423  	flist := make([]*ast.Field, it.NumEmbeddeds()+it.NumExplicitMethods())
   424  	for i := range decl.Spec.Type.(*ast.InterfaceType).Methods.List {
   425  		flist[i] = decl.Spec.Type.(*ast.InterfaceType).Methods.List[i]
   426  	}
   427  
   428  	// First collect the embedded interfaces
   429  	// create refs when the embedded interface is decorated with an allOf annotation
   430  	for i := 0; i < it.NumEmbeddeds(); i++ {
   431  		fld := it.EmbeddedType(i)
   432  
   433  		switch ftpe := fld.(type) {
   434  		case *types.Named:
   435  			o := ftpe.Obj()
   436  			var afld *ast.Field
   437  			for _, an := range flist {
   438  				if len(an.Names) != 0 {
   439  					continue
   440  				}
   441  
   442  				tpp := decl.Pkg.TypesInfo.Types[an.Type]
   443  				if tpp.Type.String() != o.Type().String() {
   444  					continue
   445  				}
   446  
   447  				// decl.
   448  				debugLog("maybe interface field %s: %s(%T)", o.Name(), o.Type().String(), o.Type())
   449  				afld = an
   450  				break
   451  			}
   452  
   453  			if afld == nil {
   454  				debugLog("can't find source associated with %s for %s", fld.String(), it.String())
   455  				continue
   456  			}
   457  
   458  			// if the field is annotated with swagger:ignore, ignore it
   459  			if ignored(afld.Doc) {
   460  				continue
   461  			}
   462  
   463  			if !allOfMember(afld.Doc) {
   464  				var newSch spec.Schema
   465  				if err := s.buildEmbedded(o.Type(), &newSch, seen); err != nil {
   466  					return err
   467  				}
   468  				schema.AllOf = append(schema.AllOf, newSch)
   469  				hasAllOf = true
   470  				continue
   471  			}
   472  
   473  			hasAllOf = true
   474  			if tgt == nil {
   475  				tgt = &spec.Schema{}
   476  			}
   477  			var newSch spec.Schema
   478  			// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   479  			// otherwise the fields will just be included as normal properties
   480  			if err := s.buildAllOf(o.Type(), &newSch); err != nil {
   481  				return err
   482  			}
   483  			if afld.Doc != nil {
   484  				for _, cmt := range afld.Doc.List {
   485  					for _, ln := range strings.Split(cmt.Text, "\n") {
   486  						matches := rxAllOf.FindStringSubmatch(ln)
   487  						ml := len(matches)
   488  						if ml > 1 {
   489  							mv := matches[ml-1]
   490  							if mv != "" {
   491  								schema.AddExtension("x-class", mv)
   492  							}
   493  						}
   494  					}
   495  				}
   496  			}
   497  
   498  			schema.AllOf = append(schema.AllOf, newSch)
   499  		default:
   500  			log.Printf("WARNING: can't figure out object type for allOf named type (%T): %v", ftpe, ftpe.Underlying())
   501  		}
   502  		debugLog("got embedded interface: %s {%T}", fld.String(), fld)
   503  	}
   504  
   505  	if tgt == nil {
   506  		tgt = schema
   507  	}
   508  	// We can finally build the actual schema for the struct
   509  	if tgt.Properties == nil {
   510  		tgt.Properties = make(map[string]spec.Schema)
   511  	}
   512  	tgt.Typed("object", "")
   513  
   514  	for i := 0; i < it.NumExplicitMethods(); i++ {
   515  		fld := it.ExplicitMethod(i)
   516  		if !fld.Exported() {
   517  			continue
   518  		}
   519  		sig, isSignature := fld.Type().(*types.Signature)
   520  		if !isSignature {
   521  			continue
   522  		}
   523  		if sig.Params().Len() > 0 {
   524  			continue
   525  		}
   526  		if sig.Results() == nil || sig.Results().Len() != 1 {
   527  			continue
   528  		}
   529  
   530  		var afld *ast.Field
   531  		ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
   532  		// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
   533  		for _, an := range ans {
   534  			at, valid := an.(*ast.Field)
   535  			if !valid {
   536  				continue
   537  			}
   538  
   539  			debugLog("maybe interface field %s: %s(%T)", fld.Name(), fld.Type().String(), fld.Type())
   540  			afld = at
   541  			break
   542  		}
   543  
   544  		if afld == nil {
   545  			debugLog("can't find source associated with %s for %s", fld.String(), it.String())
   546  			continue
   547  		}
   548  
   549  		// if the field is annotated with swagger:ignore, ignore it
   550  		if ignored(afld.Doc) {
   551  			continue
   552  		}
   553  
   554  		name := fld.Name()
   555  		if afld.Doc != nil {
   556  			for _, cmt := range afld.Doc.List {
   557  				for _, ln := range strings.Split(cmt.Text, "\n") {
   558  					matches := rxName.FindStringSubmatch(ln)
   559  					ml := len(matches)
   560  					if ml > 1 {
   561  						name = matches[ml-1]
   562  					}
   563  				}
   564  			}
   565  		}
   566  		ps := tgt.Properties[name]
   567  		if err := s.buildFromType(sig.Results().At(0).Type(), schemaTypable{&ps, 0}); err != nil {
   568  			return err
   569  		}
   570  		if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt {
   571  			ps.Typed("string", sfName)
   572  			ps.Ref = spec.Ref{}
   573  			ps.Items = nil
   574  		}
   575  
   576  		if err := s.createParser(name, tgt, &ps, afld).Parse(afld.Doc); err != nil {
   577  			return err
   578  		}
   579  
   580  		if ps.Ref.String() == "" && name != fld.Name() {
   581  			ps.AddExtension("x-go-name", fld.Name())
   582  		}
   583  
   584  		seen[name] = fld.Name()
   585  		tgt.Properties[name] = ps
   586  	}
   587  
   588  	if tgt == nil {
   589  		return nil
   590  	}
   591  	if hasAllOf && len(tgt.Properties) > 0 {
   592  		schema.AllOf = append(schema.AllOf, *tgt)
   593  	}
   594  	for k := range tgt.Properties {
   595  		if _, ok := seen[k]; !ok {
   596  			delete(tgt.Properties, k)
   597  		}
   598  	}
   599  	return nil
   600  }
   601  
   602  func (s *schemaBuilder) buildFromStruct(decl *entityDecl, st *types.Struct, schema *spec.Schema, seen map[string]string) error {
   603  	// First check for all of schemas
   604  	var tgt *spec.Schema
   605  	hasAllOf := false
   606  
   607  	for i := 0; i < st.NumFields(); i++ {
   608  		fld := st.Field(i)
   609  		if !fld.Anonymous() {
   610  			debugLog("skipping field %q for allOf scan because not anonymous", fld.Name())
   611  			continue
   612  		}
   613  		tg := st.Tag(i)
   614  
   615  		debugLog("maybe allof field(%t) %s: %s (%T) [%q](anon: %t, embedded: %t)", fld.IsField(), fld.Name(), fld.Type().String(), fld.Type(), tg, fld.Anonymous(), fld.Embedded())
   616  		var afld *ast.Field
   617  		ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
   618  		// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
   619  		for _, an := range ans {
   620  			at, valid := an.(*ast.Field)
   621  			if !valid {
   622  				continue
   623  			}
   624  
   625  			debugLog("maybe allof field %s: %s(%T) [%q]", fld.Name(), fld.Type().String(), fld.Type(), tg)
   626  			afld = at
   627  			break
   628  		}
   629  
   630  		if afld == nil {
   631  			debugLog("can't find source associated with %s for %s", fld.String(), st.String())
   632  			continue
   633  		}
   634  
   635  		// if the field is annotated with swagger:ignore, ignore it
   636  		if ignored(afld.Doc) {
   637  			continue
   638  		}
   639  
   640  		_, ignore, _, err := parseJSONTag(afld)
   641  		if err != nil {
   642  			return err
   643  		}
   644  		if ignore {
   645  			continue
   646  		}
   647  
   648  		if !allOfMember(afld.Doc) {
   649  			if tgt == nil {
   650  				tgt = schema
   651  			}
   652  			if err := s.buildEmbedded(fld.Type(), tgt, seen); err != nil {
   653  				return err
   654  			}
   655  			continue
   656  		}
   657  		// if this created an allOf property then we have to rejig the schema var
   658  		// because all the fields collected that aren't from embedded structs should go in
   659  		// their own proper schema
   660  		// first process embedded structs in order of embedding
   661  		hasAllOf = true
   662  		if tgt == nil {
   663  			tgt = &spec.Schema{}
   664  		}
   665  		var newSch spec.Schema
   666  		// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   667  		// otherwise the fields will just be included as normal properties
   668  		if err := s.buildAllOf(fld.Type(), &newSch); err != nil {
   669  			return err
   670  		}
   671  
   672  		if afld.Doc != nil {
   673  			for _, cmt := range afld.Doc.List {
   674  				for _, ln := range strings.Split(cmt.Text, "\n") {
   675  					matches := rxAllOf.FindStringSubmatch(ln)
   676  					ml := len(matches)
   677  					if ml > 1 {
   678  						mv := matches[ml-1]
   679  						if mv != "" {
   680  							schema.AddExtension("x-class", mv)
   681  						}
   682  					}
   683  				}
   684  			}
   685  		}
   686  
   687  		schema.AllOf = append(schema.AllOf, newSch)
   688  	}
   689  
   690  	if tgt == nil {
   691  		tgt = schema
   692  	}
   693  	// We can finally build the actual schema for the struct
   694  	if tgt.Properties == nil {
   695  		tgt.Properties = make(map[string]spec.Schema)
   696  	}
   697  	tgt.Typed("object", "")
   698  
   699  	for i := 0; i < st.NumFields(); i++ {
   700  		fld := st.Field(i)
   701  		tg := st.Tag(i)
   702  
   703  		if fld.Embedded() {
   704  			continue
   705  		}
   706  
   707  		if !fld.Exported() {
   708  			debugLog("skipping field %s because it's not exported", fld.Name())
   709  			continue
   710  		}
   711  
   712  		var afld *ast.Field
   713  		ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
   714  		// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
   715  		for _, an := range ans {
   716  			at, valid := an.(*ast.Field)
   717  			if !valid {
   718  				continue
   719  			}
   720  
   721  			debugLog("field %s: %s(%T) [%q] ==> %s", fld.Name(), fld.Type().String(), fld.Type(), tg, at.Doc.Text())
   722  			afld = at
   723  			break
   724  		}
   725  
   726  		if afld == nil {
   727  			debugLog("can't find source associated with %s", fld.String())
   728  			continue
   729  		}
   730  
   731  		// if the field is annotated with swagger:ignore, ignore it
   732  		if ignored(afld.Doc) {
   733  			continue
   734  		}
   735  
   736  		name, ignore, isString, err := parseJSONTag(afld)
   737  		if err != nil {
   738  			return err
   739  		}
   740  		if ignore {
   741  			for seenTagName, seenFieldName := range seen {
   742  				if seenFieldName == fld.Name() {
   743  					delete(tgt.Properties, seenTagName)
   744  					break
   745  				}
   746  			}
   747  			continue
   748  		}
   749  
   750  		ps := tgt.Properties[name]
   751  		if err = s.buildFromType(fld.Type(), schemaTypable{&ps, 0}); err != nil {
   752  			return err
   753  		}
   754  		if isString {
   755  			ps.Typed("string", ps.Format)
   756  			ps.Ref = spec.Ref{}
   757  			ps.Items = nil
   758  		}
   759  		if sfName, isStrfmt := strfmtName(afld.Doc); isStrfmt {
   760  			ps.Typed("string", sfName)
   761  			ps.Ref = spec.Ref{}
   762  			ps.Items = nil
   763  		}
   764  
   765  		if err = s.createParser(name, tgt, &ps, afld).Parse(afld.Doc); err != nil {
   766  			return err
   767  		}
   768  
   769  		if ps.Ref.String() == "" && name != fld.Name() {
   770  			addExtension(&ps.VendorExtensible, "x-go-name", fld.Name())
   771  		}
   772  
   773  		// we have 2 cases:
   774  		// 1. field with different name override tag
   775  		// 2. field with different name removes tag
   776  		// so we need to save both tag&name
   777  		seen[name] = fld.Name()
   778  		tgt.Properties[name] = ps
   779  	}
   780  
   781  	if tgt == nil {
   782  		return nil
   783  	}
   784  	if hasAllOf && len(tgt.Properties) > 0 {
   785  		schema.AllOf = append(schema.AllOf, *tgt)
   786  	}
   787  	for k := range tgt.Properties {
   788  		if _, ok := seen[k]; !ok {
   789  			delete(tgt.Properties, k)
   790  		}
   791  	}
   792  	return nil
   793  }
   794  
   795  func (s *schemaBuilder) buildAllOf(tpe types.Type, schema *spec.Schema) error {
   796  	debugLog("allOf %s", tpe.Underlying())
   797  	switch ftpe := tpe.(type) {
   798  	case *types.Pointer:
   799  		return s.buildAllOf(ftpe.Elem(), schema)
   800  	case *types.Named:
   801  		switch utpe := ftpe.Underlying().(type) {
   802  		case *types.Struct:
   803  			decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name())
   804  			if found {
   805  				if ftpe.Obj().Pkg().Path() == "time" && ftpe.Obj().Name() == "Time" {
   806  					schema.Typed("string", "date-time")
   807  					return nil
   808  				}
   809  				if sfnm, isf := strfmtName(decl.Comments); isf {
   810  					schema.Typed("string", sfnm)
   811  					return nil
   812  				}
   813  				if decl.HasModelAnnotation() {
   814  					if err := s.makeRef(decl, schemaTypable{schema, 0}); err != nil {
   815  						return err
   816  					}
   817  					return nil
   818  				}
   819  				return s.buildFromStruct(decl, utpe, schema, make(map[string]string))
   820  			}
   821  			return errors.Errorf("can't find source file for struct: %s", ftpe.String())
   822  		case *types.Interface:
   823  			decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name())
   824  			if found {
   825  				if sfnm, isf := strfmtName(decl.Comments); isf {
   826  					schema.Typed("string", sfnm)
   827  					return nil
   828  				}
   829  				if decl.HasModelAnnotation() {
   830  					if err := s.makeRef(decl, schemaTypable{schema, 0}); err != nil {
   831  						return err
   832  					}
   833  					return nil
   834  				}
   835  				return s.buildFromInterface(decl, utpe, schema, make(map[string]string))
   836  			}
   837  			return errors.Errorf("can't find source file for interface: %s", ftpe.String())
   838  		default:
   839  			log.Printf("WARNING: can't figure out object type for allOf named type (%T): %v", ftpe, ftpe.Underlying())
   840  			return fmt.Errorf("unable to locate source file for allOf %s", utpe.String())
   841  		}
   842  	default:
   843  		log.Printf("WARNING: Missing allOf parser for a %T, skipping field", ftpe)
   844  		return fmt.Errorf("unable to resolve allOf member for: %v", ftpe)
   845  	}
   846  }
   847  
   848  func (s *schemaBuilder) buildEmbedded(tpe types.Type, schema *spec.Schema, seen map[string]string) error {
   849  	debugLog("embedded %s", tpe.Underlying())
   850  	switch ftpe := tpe.(type) {
   851  	case *types.Pointer:
   852  		return s.buildEmbedded(ftpe.Elem(), schema, seen)
   853  	case *types.Named:
   854  		debugLog("embedded named type: %T", ftpe.Underlying())
   855  		switch utpe := ftpe.Underlying().(type) {
   856  		case *types.Struct:
   857  			decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name())
   858  			if found {
   859  				return s.buildFromStruct(decl, utpe, schema, seen)
   860  			}
   861  			return errors.Errorf("can't find source file for struct: %s", ftpe.String())
   862  		case *types.Interface:
   863  			decl, found := s.ctx.FindModel(ftpe.Obj().Pkg().Path(), ftpe.Obj().Name())
   864  			if found {
   865  				return s.buildFromInterface(decl, utpe, schema, seen)
   866  			}
   867  			return errors.Errorf("can't find source file for struct: %s", ftpe.String())
   868  		default:
   869  			log.Printf("WARNING: can't figure out object type for embedded named type (%T): %v", ftpe, ftpe.Underlying())
   870  		}
   871  	default:
   872  		log.Printf("WARNING: Missing embedded parser for a %T, skipping model\n", ftpe)
   873  		return nil
   874  	}
   875  	return nil
   876  }
   877  
   878  func (s *schemaBuilder) makeRef(decl *entityDecl, prop swaggerTypable) error {
   879  	nm, _ := decl.Names()
   880  	ref, err := spec.NewRef("#/definitions/" + nm)
   881  	if err != nil {
   882  		return err
   883  	}
   884  	prop.SetRef(ref)
   885  	s.postDecls = append(s.postDecls, decl)
   886  	return nil
   887  }
   888  
   889  func (s *schemaBuilder) createParser(nm string, schema, ps *spec.Schema, fld *ast.Field) *sectionedParser {
   890  	sp := new(sectionedParser)
   891  
   892  	schemeType, err := ps.Type.MarshalJSON()
   893  	if err != nil {
   894  		return nil
   895  	}
   896  
   897  	if ps.Ref.String() == "" {
   898  		sp.setDescription = func(lines []string) {
   899  			ps.Description = joinDropLast(lines)
   900  			enumDesc := getEnumDesc(ps.VendorExtensible.Extensions)
   901  			if enumDesc != "" {
   902  				ps.Description += "\n" + enumDesc
   903  			}
   904  		}
   905  		sp.taggers = []tagParser{
   906  			newSingleLineTagParser("maximum", &setMaximum{schemaValidations{ps}, rxf(rxMaximumFmt, "")}),
   907  			newSingleLineTagParser("minimum", &setMinimum{schemaValidations{ps}, rxf(rxMinimumFmt, "")}),
   908  			newSingleLineTagParser("multipleOf", &setMultipleOf{schemaValidations{ps}, rxf(rxMultipleOfFmt, "")}),
   909  			newSingleLineTagParser("minLength", &setMinLength{schemaValidations{ps}, rxf(rxMinLengthFmt, "")}),
   910  			newSingleLineTagParser("maxLength", &setMaxLength{schemaValidations{ps}, rxf(rxMaxLengthFmt, "")}),
   911  			newSingleLineTagParser("pattern", &setPattern{schemaValidations{ps}, rxf(rxPatternFmt, "")}),
   912  			newSingleLineTagParser("minItems", &setMinItems{schemaValidations{ps}, rxf(rxMinItemsFmt, "")}),
   913  			newSingleLineTagParser("maxItems", &setMaxItems{schemaValidations{ps}, rxf(rxMaxItemsFmt, "")}),
   914  			newSingleLineTagParser("unique", &setUnique{schemaValidations{ps}, rxf(rxUniqueFmt, "")}),
   915  			newSingleLineTagParser("enum", &setEnum{schemaValidations{ps}, rxf(rxEnumFmt, "")}),
   916  			newSingleLineTagParser("default", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}),
   917  			newSingleLineTagParser("type", &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxDefaultFmt, "")}),
   918  			newSingleLineTagParser("example", &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{ps}, rxf(rxExampleFmt, "")}),
   919  			newSingleLineTagParser("required", &setRequiredSchema{schema, nm}),
   920  			newSingleLineTagParser("readOnly", &setReadOnlySchema{ps}),
   921  			newSingleLineTagParser("discriminator", &setDiscriminator{schema, nm}),
   922  			newMultiLineTagParser("YAMLExtensionsBlock", newYamlParser(rxExtensions, schemaVendorExtensibleSetter(ps)), true),
   923  		}
   924  
   925  		itemsTaggers := func(items *spec.Schema, level int) []tagParser {
   926  			schemeType, err := items.Type.MarshalJSON()
   927  			if err != nil {
   928  				return nil
   929  			}
   930  			// the expression is 1-index based not 0-index
   931  			itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1)
   932  			return []tagParser{
   933  				newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{schemaValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}),
   934  				newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{schemaValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}),
   935  				newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{schemaValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}),
   936  				newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{schemaValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}),
   937  				newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{schemaValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}),
   938  				newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{schemaValidations{items}, rxf(rxPatternFmt, itemsPrefix)}),
   939  				newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{schemaValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}),
   940  				newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{schemaValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}),
   941  				newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{schemaValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}),
   942  				newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{schemaValidations{items}, rxf(rxEnumFmt, itemsPrefix)}),
   943  				newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}),
   944  				newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&spec.SimpleSchema{Type: string(schemeType)}, schemaValidations{items}, rxf(rxExampleFmt, itemsPrefix)}),
   945  			}
   946  		}
   947  
   948  		var parseArrayTypes func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error)
   949  		parseArrayTypes = func(expr ast.Expr, items *spec.SchemaOrArray, level int) ([]tagParser, error) {
   950  			if items == nil || items.Schema == nil {
   951  				return []tagParser{}, nil
   952  			}
   953  			switch iftpe := expr.(type) {
   954  			case *ast.ArrayType:
   955  				eleTaggers := itemsTaggers(items.Schema, level)
   956  				sp.taggers = append(eleTaggers, sp.taggers...)
   957  				otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Schema.Items, level+1)
   958  				if err != nil {
   959  					return nil, err
   960  				}
   961  				return otherTaggers, nil
   962  			case *ast.Ident:
   963  				taggers := []tagParser{}
   964  				if iftpe.Obj == nil {
   965  					taggers = itemsTaggers(items.Schema, level)
   966  				}
   967  				otherTaggers, err := parseArrayTypes(expr, items.Schema.Items, level+1)
   968  				if err != nil {
   969  					return nil, err
   970  				}
   971  				return append(taggers, otherTaggers...), nil
   972  			case *ast.StarExpr:
   973  				otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
   974  				if err != nil {
   975  					return nil, err
   976  				}
   977  				return otherTaggers, nil
   978  			default:
   979  				return nil, fmt.Errorf("unknown field type ele for %q", nm)
   980  			}
   981  		}
   982  		// check if this is a primitive, if so parse the validations from the
   983  		// doc comments of the slice declaration.
   984  		if ftped, ok := fld.Type.(*ast.ArrayType); ok {
   985  			taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0)
   986  			if err != nil {
   987  				return sp
   988  			}
   989  			sp.taggers = append(taggers, sp.taggers...)
   990  		}
   991  
   992  	} else {
   993  		sp.taggers = []tagParser{
   994  			newSingleLineTagParser("required", &setRequiredSchema{schema, nm}),
   995  		}
   996  	}
   997  	return sp
   998  }
   999  
  1000  func schemaVendorExtensibleSetter(meta *spec.Schema) func(json.RawMessage) error {
  1001  	return func(jsonValue json.RawMessage) error {
  1002  		var jsonData spec.Extensions
  1003  		err := json.Unmarshal(jsonValue, &jsonData)
  1004  		if err != nil {
  1005  			return err
  1006  		}
  1007  		for k := range jsonData {
  1008  			if !rxAllowedExtensions.MatchString(k) {
  1009  				return fmt.Errorf("invalid schema extension name, should start from `x-`: %s", k)
  1010  			}
  1011  		}
  1012  		meta.Extensions = jsonData
  1013  		return nil
  1014  	}
  1015  }
  1016  
  1017  type tagOptions []string
  1018  
  1019  func (t tagOptions) Contain(option string) bool {
  1020  	for i := 1; i < len(t); i++ {
  1021  		if t[i] == option {
  1022  			return true
  1023  		}
  1024  	}
  1025  	return false
  1026  }
  1027  
  1028  func (t tagOptions) Name() string {
  1029  	return t[0]
  1030  }
  1031  
  1032  func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, err error) {
  1033  	if len(field.Names) > 0 {
  1034  		name = field.Names[0].Name
  1035  	}
  1036  	if field.Tag == nil || len(strings.TrimSpace(field.Tag.Value)) == 0 {
  1037  		return name, false, false, nil
  1038  	}
  1039  
  1040  	tv, err := strconv.Unquote(field.Tag.Value)
  1041  	if err != nil {
  1042  		return name, false, false, err
  1043  	}
  1044  
  1045  	if strings.TrimSpace(tv) != "" {
  1046  		st := reflect.StructTag(tv)
  1047  		jsonParts := tagOptions(strings.Split(st.Get("json"), ","))
  1048  
  1049  		if jsonParts.Contain("string") {
  1050  			// Need to check if the field type is a scalar. Otherwise, the
  1051  			// ",string" directive doesn't apply.
  1052  			isString = isFieldStringable(field.Type)
  1053  		}
  1054  
  1055  		switch jsonParts.Name() {
  1056  		case "-":
  1057  			return name, true, isString, nil
  1058  		case "":
  1059  			return name, false, isString, nil
  1060  		default:
  1061  			return jsonParts.Name(), false, isString, nil
  1062  		}
  1063  	}
  1064  	return name, false, false, nil
  1065  }
  1066  
  1067  // isFieldStringable check if the field type is a scalar. If the field type is
  1068  // *ast.StarExpr and is pointer type, check if it refers to a scalar.
  1069  // Otherwise, the ",string" directive doesn't apply.
  1070  func isFieldStringable(tpe ast.Expr) bool {
  1071  	if ident, ok := tpe.(*ast.Ident); ok {
  1072  		switch ident.Name {
  1073  		case "int", "int8", "int16", "int32", "int64",
  1074  			"uint", "uint8", "uint16", "uint32", "uint64",
  1075  			"float64", "string", "bool":
  1076  			return true
  1077  		}
  1078  	} else if starExpr, ok := tpe.(*ast.StarExpr); ok {
  1079  		return isFieldStringable(starExpr.X)
  1080  	} else {
  1081  		return false
  1082  	}
  1083  	return false
  1084  }