github.com/jamescostian/go-swagger@v0.30.4-0.20221130163922-68364d6b567b/scan/parameters.go (about)

     1  //go:build !go1.11
     2  // +build !go1.11
     3  
     4  // Copyright 2015 go-swagger maintainers
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //    http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package scan
    19  
    20  import (
    21  	"fmt"
    22  	"go/ast"
    23  	"strings"
    24  
    25  	"github.com/go-openapi/spec"
    26  	"golang.org/x/tools/go/loader"
    27  )
    28  
    29  type operationValidationBuilder interface {
    30  	validationBuilder
    31  	SetCollectionFormat(string)
    32  }
    33  
    34  type paramTypable struct {
    35  	param *spec.Parameter
    36  }
    37  
    38  func (pt paramTypable) Level() int { return 0 }
    39  
    40  func (pt paramTypable) Typed(tpe, format string) {
    41  	pt.param.Typed(tpe, format)
    42  }
    43  
    44  func (pt paramTypable) WithEnum(values ...interface{}) {
    45  	pt.param.WithEnum(values...)
    46  }
    47  
    48  func (pt paramTypable) SetRef(ref spec.Ref) {
    49  	pt.param.Ref = ref
    50  }
    51  
    52  func (pt paramTypable) Items() swaggerTypable {
    53  	bdt, schema := bodyTypable(pt.param.In, pt.param.Schema)
    54  	if bdt != nil {
    55  		pt.param.Schema = schema
    56  		return bdt
    57  	}
    58  
    59  	if pt.param.Items == nil {
    60  		pt.param.Items = new(spec.Items)
    61  	}
    62  	pt.param.Type = "array"
    63  	return itemsTypable{pt.param.Items, 1}
    64  }
    65  
    66  func (pt paramTypable) Schema() *spec.Schema {
    67  	if pt.param.In != "body" {
    68  		return nil
    69  	}
    70  	if pt.param.Schema == nil {
    71  		pt.param.Schema = new(spec.Schema)
    72  	}
    73  	return pt.param.Schema
    74  }
    75  
    76  type itemsTypable struct {
    77  	items *spec.Items
    78  	level int
    79  }
    80  
    81  func (pt itemsTypable) Level() int { return pt.level }
    82  
    83  func (pt itemsTypable) Typed(tpe, format string) {
    84  	pt.items.Typed(tpe, format)
    85  }
    86  
    87  func (pt itemsTypable) SetRef(ref spec.Ref) {
    88  	pt.items.Ref = ref
    89  }
    90  
    91  func (pt itemsTypable) WithEnum(values ...interface{}) {
    92  	pt.items.WithEnum(values...)
    93  }
    94  
    95  func (pt itemsTypable) Schema() *spec.Schema {
    96  	return nil
    97  }
    98  
    99  func (pt itemsTypable) Items() swaggerTypable {
   100  	if pt.items.Items == nil {
   101  		pt.items.Items = new(spec.Items)
   102  	}
   103  	pt.items.Type = "array"
   104  	return itemsTypable{pt.items.Items, pt.level + 1}
   105  }
   106  
   107  type paramValidations struct {
   108  	current *spec.Parameter
   109  }
   110  
   111  func (sv paramValidations) SetMaximum(val float64, exclusive bool) {
   112  	sv.current.Maximum = &val
   113  	sv.current.ExclusiveMaximum = exclusive
   114  }
   115  func (sv paramValidations) SetMinimum(val float64, exclusive bool) {
   116  	sv.current.Minimum = &val
   117  	sv.current.ExclusiveMinimum = exclusive
   118  }
   119  func (sv paramValidations) SetMultipleOf(val float64)      { sv.current.MultipleOf = &val }
   120  func (sv paramValidations) SetMinItems(val int64)          { sv.current.MinItems = &val }
   121  func (sv paramValidations) SetMaxItems(val int64)          { sv.current.MaxItems = &val }
   122  func (sv paramValidations) SetMinLength(val int64)         { sv.current.MinLength = &val }
   123  func (sv paramValidations) SetMaxLength(val int64)         { sv.current.MaxLength = &val }
   124  func (sv paramValidations) SetPattern(val string)          { sv.current.Pattern = val }
   125  func (sv paramValidations) SetUnique(val bool)             { sv.current.UniqueItems = val }
   126  func (sv paramValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
   127  func (sv paramValidations) SetEnum(val string) {
   128  	sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
   129  }
   130  func (sv paramValidations) SetDefault(val interface{}) { sv.current.Default = val }
   131  func (sv paramValidations) SetExample(val interface{}) { sv.current.Example = val }
   132  
   133  type itemsValidations struct {
   134  	current *spec.Items
   135  }
   136  
   137  func (sv itemsValidations) SetMaximum(val float64, exclusive bool) {
   138  	sv.current.Maximum = &val
   139  	sv.current.ExclusiveMaximum = exclusive
   140  }
   141  func (sv itemsValidations) SetMinimum(val float64, exclusive bool) {
   142  	sv.current.Minimum = &val
   143  	sv.current.ExclusiveMinimum = exclusive
   144  }
   145  func (sv itemsValidations) SetMultipleOf(val float64)      { sv.current.MultipleOf = &val }
   146  func (sv itemsValidations) SetMinItems(val int64)          { sv.current.MinItems = &val }
   147  func (sv itemsValidations) SetMaxItems(val int64)          { sv.current.MaxItems = &val }
   148  func (sv itemsValidations) SetMinLength(val int64)         { sv.current.MinLength = &val }
   149  func (sv itemsValidations) SetMaxLength(val int64)         { sv.current.MaxLength = &val }
   150  func (sv itemsValidations) SetPattern(val string)          { sv.current.Pattern = val }
   151  func (sv itemsValidations) SetUnique(val bool)             { sv.current.UniqueItems = val }
   152  func (sv itemsValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val }
   153  func (sv itemsValidations) SetEnum(val string) {
   154  	sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format})
   155  }
   156  func (sv itemsValidations) SetDefault(val interface{}) { sv.current.Default = val }
   157  func (sv itemsValidations) SetExample(val interface{}) { sv.current.Example = val }
   158  
   159  type paramDecl struct {
   160  	File         *ast.File
   161  	Decl         *ast.GenDecl
   162  	TypeSpec     *ast.TypeSpec
   163  	OperationIDs []string
   164  }
   165  
   166  func (sd *paramDecl) inferOperationIDs() (opids []string) {
   167  	if len(sd.OperationIDs) > 0 {
   168  		opids = sd.OperationIDs
   169  		return
   170  	}
   171  
   172  	if sd.Decl.Doc != nil {
   173  		for _, cmt := range sd.Decl.Doc.List {
   174  			for _, ln := range strings.Split(cmt.Text, "\n") {
   175  				matches := rxParametersOverride.FindStringSubmatch(ln)
   176  				if len(matches) > 1 && len(matches[1]) > 0 {
   177  					for _, pt := range strings.Split(matches[1], " ") {
   178  						tr := strings.TrimSpace(pt)
   179  						if len(tr) > 0 {
   180  							opids = append(opids, tr)
   181  						}
   182  					}
   183  				}
   184  			}
   185  		}
   186  	}
   187  	sd.OperationIDs = append(sd.OperationIDs, opids...)
   188  	return
   189  }
   190  
   191  func newParameterParser(prog *loader.Program) *paramStructParser {
   192  	scp := new(paramStructParser)
   193  	scp.program = prog
   194  	scp.scp = newSchemaParser(prog)
   195  	return scp
   196  }
   197  
   198  type paramStructParser struct {
   199  	program   *loader.Program
   200  	postDecls []schemaDecl
   201  	scp       *schemaParser
   202  }
   203  
   204  // Parse will traverse a file and look for parameters.
   205  func (pp *paramStructParser) Parse(gofile *ast.File, target interface{}) error {
   206  	tgt := target.(map[string]*spec.Operation)
   207  	for _, decl := range gofile.Decls {
   208  		switch x1 := decl.(type) {
   209  		// Check for parameters at the package level.
   210  		case *ast.GenDecl:
   211  			for _, spc := range x1.Specs {
   212  				switch x2 := spc.(type) {
   213  				case *ast.TypeSpec:
   214  					sd := paramDecl{gofile, x1, x2, nil}
   215  					sd.inferOperationIDs()
   216  					if err := pp.parseDecl(tgt, sd); err != nil {
   217  						return err
   218  					}
   219  				}
   220  			}
   221  		// Check for parameters inside functions.
   222  		case *ast.FuncDecl:
   223  			for _, b := range x1.Body.List {
   224  				switch x2 := b.(type) {
   225  				case *ast.DeclStmt:
   226  					switch x3 := x2.Decl.(type) {
   227  					case *ast.GenDecl:
   228  						for _, spc := range x3.Specs {
   229  							switch x4 := spc.(type) {
   230  							case *ast.TypeSpec:
   231  								sd := paramDecl{gofile, x3, x4, nil}
   232  								sd.inferOperationIDs()
   233  								if err := pp.parseDecl(tgt, sd); err != nil {
   234  									return err
   235  								}
   236  							}
   237  						}
   238  					}
   239  				}
   240  			}
   241  		}
   242  	}
   243  	return nil
   244  }
   245  
   246  func (pp *paramStructParser) parseDecl(operations map[string]*spec.Operation, decl paramDecl) error {
   247  	// check if there is a swagger:parameters tag that is followed by one or more words,
   248  	// these words are the ids of the operations this parameter struct applies to
   249  	// once type name is found convert it to a schema, by looking up the schema in the
   250  	// parameters dictionary that got passed into this parse method
   251  	for _, opid := range decl.inferOperationIDs() {
   252  		operation, ok := operations[opid]
   253  		if !ok {
   254  			operation = new(spec.Operation)
   255  			operations[opid] = operation
   256  			operation.ID = opid
   257  		}
   258  
   259  		// analyze struct body for fields etc
   260  		// each exported struct field:
   261  		// * gets a type mapped to a go primitive
   262  		// * perhaps gets a format
   263  		// * has to document the validations that apply for the type and the field
   264  		// * when the struct field points to a model it becomes a ref: #/definitions/ModelName
   265  		// * comments that aren't tags is used as the description
   266  		if tpe, ok := decl.TypeSpec.Type.(*ast.StructType); ok {
   267  			if err := pp.parseStructType(decl.File, operation, tpe, make(map[string]spec.Parameter)); err != nil {
   268  				return err
   269  			}
   270  		}
   271  
   272  		//operations[opid] = operation
   273  	}
   274  	return nil
   275  }
   276  
   277  func (pp *paramStructParser) parseEmbeddedStruct(gofile *ast.File, operation *spec.Operation, expr ast.Expr, seenPreviously map[string]spec.Parameter) error {
   278  	switch tpe := expr.(type) {
   279  	case *ast.Ident:
   280  		// do lookup of type
   281  		// take primitives into account, they should result in an error for swagger
   282  		pkg, err := pp.scp.packageForFile(gofile, tpe)
   283  		if err != nil {
   284  			return fmt.Errorf("embedded struct: %v", err)
   285  		}
   286  		file, _, ts, err := findSourceFile(pkg, tpe.Name)
   287  		if err != nil {
   288  			return fmt.Errorf("embedded struct: %v", err)
   289  		}
   290  		if st, ok := ts.Type.(*ast.StructType); ok {
   291  			return pp.parseStructType(file, operation, st, seenPreviously)
   292  		}
   293  	case *ast.SelectorExpr:
   294  		// look up package, file and then type
   295  		pkg, err := pp.scp.packageForSelector(gofile, tpe.X)
   296  		if err != nil {
   297  			return fmt.Errorf("embedded struct: %v", err)
   298  		}
   299  		file, _, ts, err := findSourceFile(pkg, tpe.Sel.Name)
   300  		if err != nil {
   301  			return fmt.Errorf("embedded struct: %v", err)
   302  		}
   303  		if st, ok := ts.Type.(*ast.StructType); ok {
   304  			return pp.parseStructType(file, operation, st, seenPreviously)
   305  		}
   306  	case *ast.StarExpr:
   307  		return pp.parseEmbeddedStruct(gofile, operation, tpe.X, seenPreviously)
   308  	}
   309  	fmt.Printf("3%#v\n", expr)
   310  	return fmt.Errorf("unable to resolve embedded struct for: %v", expr)
   311  }
   312  
   313  func (pp *paramStructParser) parseStructType(gofile *ast.File, operation *spec.Operation, tpe *ast.StructType, seenPreviously map[string]spec.Parameter) error {
   314  	if tpe.Fields != nil {
   315  		pt := seenPreviously
   316  
   317  		for _, fld := range tpe.Fields.List {
   318  			if len(fld.Names) == 0 {
   319  				// when the embedded struct is annotated with swagger:allOf it will be used as allOf property
   320  				// otherwise the fields will just be included as normal properties
   321  				if err := pp.parseEmbeddedStruct(gofile, operation, fld.Type, pt); err != nil {
   322  					return err
   323  				}
   324  			}
   325  		}
   326  
   327  		// a slice used to keep track of the sequence of the map keys, as maps does not keep to any specific sequence (since Go-1.4)
   328  		sequence := []string{}
   329  
   330  		for _, fld := range tpe.Fields.List {
   331  			if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() {
   332  				gnm := fld.Names[0].Name
   333  				nm, ignore, _, err := parseJSONTag(fld)
   334  				if err != nil {
   335  					return err
   336  				}
   337  				if ignore {
   338  					continue
   339  				}
   340  
   341  				in := "query"
   342  				// scan for param location first, this changes some behavior down the line
   343  				if fld.Doc != nil {
   344  					for _, cmt := range fld.Doc.List {
   345  						for _, line := range strings.Split(cmt.Text, "\n") {
   346  							matches := rxIn.FindStringSubmatch(line)
   347  							if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 {
   348  								in = strings.TrimSpace(matches[1])
   349  							}
   350  						}
   351  					}
   352  				}
   353  
   354  				ps := pt[nm]
   355  				ps.In = in
   356  				var pty swaggerTypable = paramTypable{&ps}
   357  				if in == "body" {
   358  					pty = schemaTypable{pty.Schema(), 0}
   359  				}
   360  				if in == "formData" && fld.Doc != nil && fileParam(fld.Doc) {
   361  					pty.Typed("file", "")
   362  				} else {
   363  					if err := pp.scp.parseNamedType(gofile, fld.Type, pty); err != nil {
   364  						return err
   365  					}
   366  				}
   367  
   368  				if strfmtName, ok := strfmtName(fld.Doc); ok {
   369  					ps.Typed("string", strfmtName)
   370  					ps.Ref = spec.Ref{}
   371  				}
   372  
   373  				sp := new(sectionedParser)
   374  				sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) }
   375  				if ps.Ref.String() == "" {
   376  					sp.taggers = []tagParser{
   377  						newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}),
   378  						newSingleLineTagParser("maximum", &setMaximum{paramValidations{&ps}, rxf(rxMaximumFmt, "")}),
   379  						newSingleLineTagParser("minimum", &setMinimum{paramValidations{&ps}, rxf(rxMinimumFmt, "")}),
   380  						newSingleLineTagParser("multipleOf", &setMultipleOf{paramValidations{&ps}, rxf(rxMultipleOfFmt, "")}),
   381  						newSingleLineTagParser("minLength", &setMinLength{paramValidations{&ps}, rxf(rxMinLengthFmt, "")}),
   382  						newSingleLineTagParser("maxLength", &setMaxLength{paramValidations{&ps}, rxf(rxMaxLengthFmt, "")}),
   383  						newSingleLineTagParser("pattern", &setPattern{paramValidations{&ps}, rxf(rxPatternFmt, "")}),
   384  						newSingleLineTagParser("collectionFormat", &setCollectionFormat{paramValidations{&ps}, rxf(rxCollectionFormatFmt, "")}),
   385  						newSingleLineTagParser("minItems", &setMinItems{paramValidations{&ps}, rxf(rxMinItemsFmt, "")}),
   386  						newSingleLineTagParser("maxItems", &setMaxItems{paramValidations{&ps}, rxf(rxMaxItemsFmt, "")}),
   387  						newSingleLineTagParser("unique", &setUnique{paramValidations{&ps}, rxf(rxUniqueFmt, "")}),
   388  						newSingleLineTagParser("enum", &setEnum{paramValidations{&ps}, rxf(rxEnumFmt, "")}),
   389  						newSingleLineTagParser("default", &setDefault{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxDefaultFmt, "")}),
   390  						newSingleLineTagParser("example", &setExample{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxExampleFmt, "")}),
   391  						newSingleLineTagParser("required", &setRequiredParam{&ps}),
   392  					}
   393  
   394  					itemsTaggers := func(items *spec.Items, level int) []tagParser {
   395  						// the expression is 1-index based not 0-index
   396  						itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1)
   397  
   398  						return []tagParser{
   399  							newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{itemsValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}),
   400  							newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{itemsValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}),
   401  							newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{itemsValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}),
   402  							newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{itemsValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}),
   403  							newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{itemsValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}),
   404  							newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{itemsValidations{items}, rxf(rxPatternFmt, itemsPrefix)}),
   405  							newSingleLineTagParser(fmt.Sprintf("items%dCollectionFormat", level), &setCollectionFormat{itemsValidations{items}, rxf(rxCollectionFormatFmt, itemsPrefix)}),
   406  							newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{itemsValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}),
   407  							newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{itemsValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}),
   408  							newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{itemsValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}),
   409  							newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{itemsValidations{items}, rxf(rxEnumFmt, itemsPrefix)}),
   410  							newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&items.SimpleSchema, itemsValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}),
   411  							newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&items.SimpleSchema, itemsValidations{items}, rxf(rxExampleFmt, itemsPrefix)}),
   412  						}
   413  					}
   414  
   415  					var parseArrayTypes func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error)
   416  					parseArrayTypes = func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) {
   417  						if items == nil {
   418  							return []tagParser{}, nil
   419  						}
   420  						switch iftpe := expr.(type) {
   421  						case *ast.ArrayType:
   422  							eleTaggers := itemsTaggers(items, level)
   423  							sp.taggers = append(eleTaggers, sp.taggers...)
   424  							otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Items, level+1)
   425  							if err != nil {
   426  								return nil, err
   427  							}
   428  							return otherTaggers, nil
   429  						case *ast.SelectorExpr:
   430  							otherTaggers, err := parseArrayTypes(iftpe.Sel, items.Items, level+1)
   431  							if err != nil {
   432  								return nil, err
   433  							}
   434  							return otherTaggers, nil
   435  						case *ast.Ident:
   436  							taggers := []tagParser{}
   437  							if iftpe.Obj == nil {
   438  								taggers = itemsTaggers(items, level)
   439  							}
   440  							otherTaggers, err := parseArrayTypes(expr, items.Items, level+1)
   441  							if err != nil {
   442  								return nil, err
   443  							}
   444  							return append(taggers, otherTaggers...), nil
   445  						case *ast.StarExpr:
   446  							otherTaggers, err := parseArrayTypes(iftpe.X, items, level)
   447  							if err != nil {
   448  								return nil, err
   449  							}
   450  							return otherTaggers, nil
   451  						default:
   452  							return nil, fmt.Errorf("unknown field type ele for %q", nm)
   453  						}
   454  					}
   455  
   456  					// check if this is a primitive, if so parse the validations from the
   457  					// doc comments of the slice declaration.
   458  					if ftped, ok := fld.Type.(*ast.ArrayType); ok {
   459  						taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0)
   460  						if err != nil {
   461  							return err
   462  						}
   463  						sp.taggers = append(taggers, sp.taggers...)
   464  					}
   465  
   466  				} else {
   467  
   468  					sp.taggers = []tagParser{
   469  						newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}),
   470  						newSingleLineTagParser("required", &matchOnlyParam{&ps, rxRequired}),
   471  					}
   472  				}
   473  				if err := sp.Parse(fld.Doc); err != nil {
   474  					return err
   475  				}
   476  				if ps.In == "path" {
   477  					ps.Required = true
   478  				}
   479  
   480  				if ps.Name == "" {
   481  					ps.Name = nm
   482  				}
   483  
   484  				if nm != gnm {
   485  					addExtension(&ps.VendorExtensible, "x-go-name", gnm)
   486  				}
   487  				pt[nm] = ps
   488  				sequence = append(sequence, nm)
   489  			}
   490  		}
   491  
   492  		for _, k := range sequence {
   493  			p := pt[k]
   494  			for i, v := range operation.Parameters {
   495  				if v.Name == k {
   496  					operation.Parameters = append(operation.Parameters[:i], operation.Parameters[i+1:]...)
   497  					break
   498  				}
   499  			}
   500  			operation.Parameters = append(operation.Parameters, p)
   501  		}
   502  	}
   503  
   504  	return nil
   505  }
   506  
   507  func isAliasParam(prop swaggerTypable) bool {
   508  	var isParam bool
   509  	if param, ok := prop.(paramTypable); ok {
   510  		isParam = param.param.In == "query" ||
   511  			param.param.In == "path" ||
   512  			param.param.In == "formData"
   513  	}
   514  	return isParam
   515  }