github.com/emreu/go-swagger@v0.22.1/scan/parameters.go (about)

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