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 }