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 }