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

     1  package codescan
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  	"strings"
     8  
     9  	"golang.org/x/tools/go/ast/astutil"
    10  
    11  	"github.com/go-openapi/spec"
    12  )
    13  
    14  type paramTypable struct {
    15  	param *spec.Parameter
    16  }
    17  
    18  func (pt paramTypable) Level() int { return 0 }
    19  
    20  func (pt paramTypable) Typed(tpe, format string) {
    21  	pt.param.Typed(tpe, format)
    22  }
    23  
    24  func (pt paramTypable) SetRef(ref spec.Ref) {
    25  	pt.param.Ref = ref
    26  }
    27  
    28  func (pt paramTypable) Items() swaggerTypable {
    29  	bdt, schema := bodyTypable(pt.param.In, pt.param.Schema)
    30  	if bdt != nil {
    31  		pt.param.Schema = schema
    32  		return bdt
    33  	}
    34  
    35  	if pt.param.Items == nil {
    36  		pt.param.Items = new(spec.Items)
    37  	}
    38  	pt.param.Type = "array"
    39  	return itemsTypable{pt.param.Items, 1}
    40  }
    41  
    42  func (pt paramTypable) Schema() *spec.Schema {
    43  	if pt.param.In != "body" {
    44  		return nil
    45  	}
    46  	if pt.param.Schema == nil {
    47  		pt.param.Schema = new(spec.Schema)
    48  	}
    49  	return pt.param.Schema
    50  }
    51  
    52  func (pt paramTypable) AddExtension(key string, value interface{}) {
    53  	if pt.param.In == "body" {
    54  		pt.Schema().AddExtension(key, value)
    55  	} else {
    56  		pt.param.AddExtension(key, value)
    57  	}
    58  }
    59  
    60  func (pt paramTypable) WithEnum(values ...interface{}) {
    61  	pt.param.WithEnum(values...)
    62  }
    63  
    64  func (pt paramTypable) WithEnumDescription(desc string) {
    65  	if desc == "" {
    66  		return
    67  	}
    68  	pt.param.AddExtension(extEnumDesc, desc)
    69  }
    70  
    71  type itemsTypable struct {
    72  	items *spec.Items
    73  	level int
    74  }
    75  
    76  func (pt itemsTypable) Level() int { return pt.level }
    77  
    78  func (pt itemsTypable) Typed(tpe, format string) {
    79  	pt.items.Typed(tpe, format)
    80  }
    81  
    82  func (pt itemsTypable) SetRef(ref spec.Ref) {
    83  	pt.items.Ref = ref
    84  }
    85  
    86  func (pt itemsTypable) Schema() *spec.Schema {
    87  	return nil
    88  }
    89  
    90  func (pt itemsTypable) Items() swaggerTypable {
    91  	if pt.items.Items == nil {
    92  		pt.items.Items = new(spec.Items)
    93  	}
    94  	pt.items.Type = "array"
    95  	return itemsTypable{pt.items.Items, pt.level + 1}
    96  }
    97  
    98  func (pt itemsTypable) AddExtension(key string, value interface{}) {
    99  	pt.items.AddExtension(key, value)
   100  }
   101  
   102  func (pt itemsTypable) WithEnum(values ...interface{}) {
   103  	pt.items.WithEnum(values...)
   104  }
   105  
   106  func (pt itemsTypable) WithEnumDescription(_ string) {
   107  	// no
   108  }
   109  
   110  type paramValidations struct {
   111  	current *spec.Parameter
   112  }
   113  
   114  func (sv paramValidations) SetMaximum(val float64, exclusive bool) {
   115  	sv.current.Maximum = &val
   116  	sv.current.ExclusiveMaximum = exclusive
   117  }
   118  
   119  func (sv paramValidations) SetMinimum(val float64, exclusive bool) {
   120  	sv.current.Minimum = &val
   121  	sv.current.ExclusiveMinimum = exclusive
   122  }
   123  func (sv paramValidations) SetMultipleOf(val float64)      { sv.current.MultipleOf = &val }
   124  func (sv paramValidations) SetMinItems(val int64)          { sv.current.MinItems = &val }
   125  func (sv paramValidations) SetMaxItems(val int64)          { sv.current.MaxItems = &val }
   126  func (sv paramValidations) SetMinLength(val int64)         { sv.current.MinLength = &val }
   127  func (sv paramValidations) SetMaxLength(val int64)         { sv.current.MaxLength = &val }
   128  func (sv paramValidations) SetPattern(val string)          { sv.current.Pattern = val }
   129  func (sv paramValidations) SetUnique(val bool)             { sv.current.UniqueItems = val }
   130  func (sv paramValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
   131  func (sv paramValidations) SetEnum(val string) {
   132  	sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
   133  }
   134  func (sv paramValidations) SetDefault(val interface{}) { sv.current.Default = val }
   135  func (sv paramValidations) SetExample(val interface{}) { sv.current.Example = val }
   136  
   137  type itemsValidations struct {
   138  	current *spec.Items
   139  }
   140  
   141  func (sv itemsValidations) SetMaximum(val float64, exclusive bool) {
   142  	sv.current.Maximum = &val
   143  	sv.current.ExclusiveMaximum = exclusive
   144  }
   145  
   146  func (sv itemsValidations) SetMinimum(val float64, exclusive bool) {
   147  	sv.current.Minimum = &val
   148  	sv.current.ExclusiveMinimum = exclusive
   149  }
   150  func (sv itemsValidations) SetMultipleOf(val float64)      { sv.current.MultipleOf = &val }
   151  func (sv itemsValidations) SetMinItems(val int64)          { sv.current.MinItems = &val }
   152  func (sv itemsValidations) SetMaxItems(val int64)          { sv.current.MaxItems = &val }
   153  func (sv itemsValidations) SetMinLength(val int64)         { sv.current.MinLength = &val }
   154  func (sv itemsValidations) SetMaxLength(val int64)         { sv.current.MaxLength = &val }
   155  func (sv itemsValidations) SetPattern(val string)          { sv.current.Pattern = val }
   156  func (sv itemsValidations) SetUnique(val bool)             { sv.current.UniqueItems = val }
   157  func (sv itemsValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
   158  func (sv itemsValidations) SetEnum(val string) {
   159  	sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
   160  }
   161  func (sv itemsValidations) SetDefault(val interface{}) { sv.current.Default = val }
   162  func (sv itemsValidations) SetExample(val interface{}) { sv.current.Example = val }
   163  
   164  type parameterBuilder struct {
   165  	ctx       *scanCtx
   166  	decl      *entityDecl
   167  	postDecls []*entityDecl
   168  }
   169  
   170  func (p *parameterBuilder) Build(operations map[string]*spec.Operation) error {
   171  	// check if there is a swagger:parameters tag that is followed by one or more words,
   172  	// these words are the ids of the operations this parameter struct applies to
   173  	// once type name is found convert it to a schema, by looking up the schema in the
   174  	// parameters dictionary that got passed into this parse method
   175  	for _, opid := range p.decl.OperationIDs() {
   176  		operation, ok := operations[opid]
   177  		if !ok {
   178  			operation = new(spec.Operation)
   179  			operations[opid] = operation
   180  			operation.ID = opid
   181  		}
   182  		debugLog("building parameters for: %s", opid)
   183  
   184  		// analyze struct body for fields etc
   185  		// each exported struct field:
   186  		// * gets a type mapped to a go primitive
   187  		// * perhaps gets a format
   188  		// * has to document the validations that apply for the type and the field
   189  		// * when the struct field points to a model it becomes a ref: #/definitions/ModelName
   190  		// * comments that aren't tags is used as the description
   191  		if err := p.buildFromType(p.decl.Type, operation, make(map[string]spec.Parameter)); err != nil {
   192  			return err
   193  		}
   194  	}
   195  	return nil
   196  }
   197  
   198  func (p *parameterBuilder) buildFromType(otpe types.Type, op *spec.Operation, seen map[string]spec.Parameter) error {
   199  	switch tpe := otpe.(type) {
   200  	case *types.Pointer:
   201  		return p.buildFromType(tpe.Elem(), op, seen)
   202  	case *types.Named:
   203  		o := tpe.Obj()
   204  		switch stpe := o.Type().Underlying().(type) {
   205  		case *types.Struct:
   206  			debugLog("build from type %s: %T", tpe.Obj().Name(), otpe)
   207  			if decl, found := p.ctx.DeclForType(o.Type()); found {
   208  				return p.buildFromStruct(decl, stpe, op, seen)
   209  			}
   210  			return p.buildFromStruct(p.decl, stpe, op, seen)
   211  		default:
   212  			return fmt.Errorf("unhandled type (%T): %s", stpe, o.Type().Underlying().String())
   213  		}
   214  	default:
   215  		return fmt.Errorf("unhandled type (%T): %s", otpe, tpe.String())
   216  	}
   217  }
   218  
   219  func (p *parameterBuilder) buildFromField(fld *types.Var, tpe types.Type, typable swaggerTypable, seen map[string]spec.Parameter) error {
   220  	debugLog("build from field %s: %T", fld.Name(), tpe)
   221  	switch ftpe := tpe.(type) {
   222  	case *types.Basic:
   223  		return swaggerSchemaForType(ftpe.Name(), typable)
   224  	case *types.Struct:
   225  		sb := schemaBuilder{
   226  			decl: p.decl,
   227  			ctx:  p.ctx,
   228  		}
   229  		if err := sb.buildFromType(tpe, typable); err != nil {
   230  			return err
   231  		}
   232  		p.postDecls = append(p.postDecls, sb.postDecls...)
   233  		return nil
   234  	case *types.Pointer:
   235  		return p.buildFromField(fld, ftpe.Elem(), typable, seen)
   236  	case *types.Interface:
   237  		sb := schemaBuilder{
   238  			decl: p.decl,
   239  			ctx:  p.ctx,
   240  		}
   241  		if err := sb.buildFromType(tpe, typable); err != nil {
   242  			return err
   243  		}
   244  		p.postDecls = append(p.postDecls, sb.postDecls...)
   245  		return nil
   246  	case *types.Array:
   247  		return p.buildFromField(fld, ftpe.Elem(), typable.Items(), seen)
   248  	case *types.Slice:
   249  		return p.buildFromField(fld, ftpe.Elem(), typable.Items(), seen)
   250  	case *types.Map:
   251  		schema := new(spec.Schema)
   252  		typable.Schema().Typed("object", "").AdditionalProperties = &spec.SchemaOrBool{
   253  			Schema: schema,
   254  		}
   255  		sb := schemaBuilder{
   256  			decl: p.decl,
   257  			ctx:  p.ctx,
   258  		}
   259  		if err := sb.buildFromType(ftpe.Elem(), schemaTypable{schema, typable.Level() + 1}); err != nil {
   260  			return err
   261  		}
   262  		return nil
   263  	case *types.Named:
   264  		if decl, found := p.ctx.DeclForType(ftpe.Obj().Type()); found {
   265  			if decl.Type.Obj().Pkg().Path() == "time" && decl.Type.Obj().Name() == "Time" {
   266  				typable.Typed("string", "date-time")
   267  				return nil
   268  			}
   269  			if sfnm, isf := strfmtName(decl.Comments); isf {
   270  				typable.Typed("string", sfnm)
   271  				return nil
   272  			}
   273  			sb := &schemaBuilder{ctx: p.ctx, decl: decl}
   274  			sb.inferNames()
   275  			if err := sb.buildFromType(decl.Type, typable); err != nil {
   276  				return err
   277  			}
   278  			p.postDecls = append(p.postDecls, sb.postDecls...)
   279  			return nil
   280  		}
   281  		return fmt.Errorf("unable to find package and source file for: %s", ftpe.String())
   282  	default:
   283  		return fmt.Errorf("unknown type for %s: %T", fld.String(), fld.Type())
   284  	}
   285  }
   286  
   287  func spExtensionsSetter(ps *spec.Parameter) func(*spec.Extensions) {
   288  	return func(exts *spec.Extensions) {
   289  		for name, value := range *exts {
   290  			addExtension(&ps.VendorExtensible, name, value)
   291  		}
   292  	}
   293  }
   294  
   295  func (p *parameterBuilder) buildFromStruct(decl *entityDecl, tpe *types.Struct, op *spec.Operation, seen map[string]spec.Parameter) error {
   296  	if tpe.NumFields() == 0 {
   297  		return nil
   298  	}
   299  
   300  	var sequence []string
   301  
   302  	for i := 0; i < tpe.NumFields(); i++ {
   303  		fld := tpe.Field(i)
   304  
   305  		if fld.Embedded() {
   306  			if err := p.buildFromType(fld.Type(), op, seen); err != nil {
   307  				return err
   308  			}
   309  			continue
   310  		}
   311  
   312  		if !fld.Exported() {
   313  			debugLog("skipping field %s because it's not exported", fld.Name())
   314  			continue
   315  		}
   316  
   317  		tg := tpe.Tag(i)
   318  
   319  		var afld *ast.Field
   320  		ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos())
   321  		for _, an := range ans {
   322  			at, valid := an.(*ast.Field)
   323  			if !valid {
   324  				continue
   325  			}
   326  
   327  			debugLog("field %s: %s(%T) [%q] ==> %s", fld.Name(), fld.Type().String(), fld.Type(), tg, at.Doc.Text())
   328  			afld = at
   329  			break
   330  		}
   331  
   332  		if afld == nil {
   333  			debugLog("can't find source associated with %s for %s", fld.String(), tpe.String())
   334  			continue
   335  		}
   336  
   337  		// if the field is annotated with swagger:ignore, ignore it
   338  		if ignored(afld.Doc) {
   339  			continue
   340  		}
   341  
   342  		name, ignore, _, err := parseJSONTag(afld)
   343  		if err != nil {
   344  			return err
   345  		}
   346  		if ignore {
   347  			continue
   348  		}
   349  
   350  		in := "query"
   351  		// scan for param location first, this changes some behavior down the line
   352  		if afld.Doc != nil {
   353  			for _, cmt := range afld.Doc.List {
   354  				for _, line := range strings.Split(cmt.Text, "\n") {
   355  					matches := rxIn.FindStringSubmatch(line)
   356  					if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 {
   357  						in = strings.TrimSpace(matches[1])
   358  					}
   359  				}
   360  			}
   361  		}
   362  
   363  		ps := seen[name]
   364  		ps.In = in
   365  		var pty swaggerTypable = paramTypable{&ps}
   366  		if in == "body" {
   367  			pty = schemaTypable{pty.Schema(), 0}
   368  		}
   369  		if in == "formData" && afld.Doc != nil && fileParam(afld.Doc) {
   370  			pty.Typed("file", "")
   371  		} else if err := p.buildFromField(fld, fld.Type(), pty, seen); err != nil {
   372  			return err
   373  		}
   374  
   375  		if strfmtName, ok := strfmtName(afld.Doc); ok {
   376  			ps.Typed("string", strfmtName)
   377  			ps.Ref = spec.Ref{}
   378  			ps.Items = nil
   379  		}
   380  
   381  		sp := new(sectionedParser)
   382  		sp.setDescription = func(lines []string) {
   383  			ps.Description = joinDropLast(lines)
   384  			enumDesc := getEnumDesc(ps.Extensions)
   385  			if enumDesc != "" {
   386  				ps.Description += "\n" + enumDesc
   387  			}
   388  		}
   389  		if ps.Ref.String() == "" {
   390  			sp.taggers = []tagParser{
   391  				newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}),
   392  				newSingleLineTagParser("maximum", &setMaximum{paramValidations{&ps}, rxf(rxMaximumFmt, "")}),
   393  				newSingleLineTagParser("minimum", &setMinimum{paramValidations{&ps}, rxf(rxMinimumFmt, "")}),
   394  				newSingleLineTagParser("multipleOf", &setMultipleOf{paramValidations{&ps}, rxf(rxMultipleOfFmt, "")}),
   395  				newSingleLineTagParser("minLength", &setMinLength{paramValidations{&ps}, rxf(rxMinLengthFmt, "")}),
   396  				newSingleLineTagParser("maxLength", &setMaxLength{paramValidations{&ps}, rxf(rxMaxLengthFmt, "")}),
   397  				newSingleLineTagParser("pattern", &setPattern{paramValidations{&ps}, rxf(rxPatternFmt, "")}),
   398  				newSingleLineTagParser("collectionFormat", &setCollectionFormat{paramValidations{&ps}, rxf(rxCollectionFormatFmt, "")}),
   399  				newSingleLineTagParser("minItems", &setMinItems{paramValidations{&ps}, rxf(rxMinItemsFmt, "")}),
   400  				newSingleLineTagParser("maxItems", &setMaxItems{paramValidations{&ps}, rxf(rxMaxItemsFmt, "")}),
   401  				newSingleLineTagParser("unique", &setUnique{paramValidations{&ps}, rxf(rxUniqueFmt, "")}),
   402  				newSingleLineTagParser("enum", &setEnum{paramValidations{&ps}, rxf(rxEnumFmt, "")}),
   403  				newSingleLineTagParser("default", &setDefault{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxDefaultFmt, "")}),
   404  				newSingleLineTagParser("example", &setExample{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxExampleFmt, "")}),
   405  				newSingleLineTagParser("required", &setRequiredParam{&ps}),
   406  				newMultiLineTagParser("Extensions", newSetExtensions(spExtensionsSetter(&ps)), true),
   407  			}
   408  
   409  			itemsTaggers := func(items *spec.Items, level int) []tagParser {
   410  				// the expression is 1-index based not 0-index
   411  				itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1)
   412  
   413  				return []tagParser{
   414  					newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{itemsValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}),
   415  					newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{itemsValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}),
   416  					newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{itemsValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}),
   417  					newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{itemsValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}),
   418  					newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{itemsValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}),
   419  					newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{itemsValidations{items}, rxf(rxPatternFmt, itemsPrefix)}),
   420  					newSingleLineTagParser(fmt.Sprintf("items%dCollectionFormat", level), &setCollectionFormat{itemsValidations{items}, rxf(rxCollectionFormatFmt, itemsPrefix)}),
   421  					newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{itemsValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}),
   422  					newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{itemsValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}),
   423  					newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{itemsValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}),
   424  					newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{itemsValidations{items}, rxf(rxEnumFmt, itemsPrefix)}),
   425  					newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&items.SimpleSchema, itemsValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}),
   426  					newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&items.SimpleSchema, itemsValidations{items}, rxf(rxExampleFmt, itemsPrefix)}),
   427  				}
   428  			}
   429  
   430  			var parseArrayTypes func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error)
   431  			parseArrayTypes = func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) {
   432  				if items == nil {
   433  					return []tagParser{}, nil
   434  				}
   435  				switch iftpe := expr.(type) {
   436  				case *ast.ArrayType:
   437  					eleTaggers := itemsTaggers(items, level)
   438  					sp.taggers = append(eleTaggers, sp.taggers...)
   439  					otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Items, level+1)
   440  					if err != nil {
   441  						return nil, err
   442  					}
   443  					return otherTaggers, nil
   444  				case *ast.SelectorExpr:
   445  					otherTaggers, err := parseArrayTypes(iftpe.Sel, items.Items, level+1)
   446  					if err != nil {
   447  						return nil, err
   448  					}
   449  					return otherTaggers, nil
   450  				case *ast.Ident:
   451  					taggers := []tagParser{}
   452  					if iftpe.Obj == nil {
   453  						taggers = itemsTaggers(items, level)
   454  					}
   455  					otherTaggers, err := parseArrayTypes(expr, items.Items, level+1)
   456  					if err != nil {
   457  						return nil, err
   458  					}
   459  					return append(taggers, otherTaggers...), nil
   460  				case *ast.StarExpr:
   461  					otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
   462  					if err != nil {
   463  						return nil, err
   464  					}
   465  					return otherTaggers, nil
   466  				default:
   467  					return nil, fmt.Errorf("unknown field type ele for %q", name)
   468  				}
   469  			}
   470  
   471  			// check if this is a primitive, if so parse the validations from the
   472  			// doc comments of the slice declaration.
   473  			if ftped, ok := afld.Type.(*ast.ArrayType); ok {
   474  				taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0)
   475  				if err != nil {
   476  					return err
   477  				}
   478  				sp.taggers = append(taggers, sp.taggers...)
   479  			}
   480  
   481  		} else {
   482  			sp.taggers = []tagParser{
   483  				newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}),
   484  				newSingleLineTagParser("required", &matchOnlyParam{&ps, rxRequired}),
   485  				newMultiLineTagParser("Extensions", newSetExtensions(spExtensionsSetter(&ps)), true),
   486  			}
   487  		}
   488  		if err := sp.Parse(afld.Doc); err != nil {
   489  			return err
   490  		}
   491  		if ps.In == "path" {
   492  			ps.Required = true
   493  		}
   494  
   495  		if ps.Name == "" {
   496  			ps.Name = name
   497  		}
   498  
   499  		if name != fld.Name() {
   500  			addExtension(&ps.VendorExtensible, "x-go-name", fld.Name())
   501  		}
   502  		seen[name] = ps
   503  		sequence = append(sequence, name)
   504  	}
   505  
   506  	for _, k := range sequence {
   507  		p := seen[k]
   508  		for i, v := range op.Parameters {
   509  			if v.Name == k {
   510  				op.Parameters = append(op.Parameters[:i], op.Parameters[i+1:]...)
   511  				break
   512  			}
   513  		}
   514  		op.Parameters = append(op.Parameters, p)
   515  	}
   516  	return nil
   517  }