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

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