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

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