github.com/djarvur/go-swagger@v0.18.0/scan/parameters.go (about)

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