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 }