github.com/go-swagger/go-swagger@v0.31.0/codescan/parameters.go (about) 1 package codescan 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 "strings" 8 9 "golang.org/x/tools/go/ast/astutil" 10 11 "github.com/go-openapi/spec" 12 ) 13 14 type paramTypable struct { 15 param *spec.Parameter 16 } 17 18 func (pt paramTypable) Level() int { return 0 } 19 20 func (pt paramTypable) Typed(tpe, format string) { 21 pt.param.Typed(tpe, format) 22 } 23 24 func (pt paramTypable) SetRef(ref spec.Ref) { 25 pt.param.Ref = ref 26 } 27 28 func (pt paramTypable) Items() swaggerTypable { 29 bdt, schema := bodyTypable(pt.param.In, pt.param.Schema) 30 if bdt != nil { 31 pt.param.Schema = schema 32 return bdt 33 } 34 35 if pt.param.Items == nil { 36 pt.param.Items = new(spec.Items) 37 } 38 pt.param.Type = "array" 39 return itemsTypable{pt.param.Items, 1} 40 } 41 42 func (pt paramTypable) Schema() *spec.Schema { 43 if pt.param.In != "body" { 44 return nil 45 } 46 if pt.param.Schema == nil { 47 pt.param.Schema = new(spec.Schema) 48 } 49 return pt.param.Schema 50 } 51 52 func (pt paramTypable) AddExtension(key string, value interface{}) { 53 if pt.param.In == "body" { 54 pt.Schema().AddExtension(key, value) 55 } else { 56 pt.param.AddExtension(key, value) 57 } 58 } 59 60 func (pt paramTypable) WithEnum(values ...interface{}) { 61 pt.param.WithEnum(values...) 62 } 63 64 func (pt paramTypable) WithEnumDescription(desc string) { 65 if desc == "" { 66 return 67 } 68 pt.param.AddExtension(extEnumDesc, desc) 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 func (pt itemsTypable) AddExtension(key string, value interface{}) { 99 pt.items.AddExtension(key, value) 100 } 101 102 func (pt itemsTypable) WithEnum(values ...interface{}) { 103 pt.items.WithEnum(values...) 104 } 105 106 func (pt itemsTypable) WithEnumDescription(_ string) { 107 // no 108 } 109 110 type paramValidations struct { 111 current *spec.Parameter 112 } 113 114 func (sv paramValidations) SetMaximum(val float64, exclusive bool) { 115 sv.current.Maximum = &val 116 sv.current.ExclusiveMaximum = exclusive 117 } 118 119 func (sv paramValidations) SetMinimum(val float64, exclusive bool) { 120 sv.current.Minimum = &val 121 sv.current.ExclusiveMinimum = exclusive 122 } 123 func (sv paramValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val } 124 func (sv paramValidations) SetMinItems(val int64) { sv.current.MinItems = &val } 125 func (sv paramValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val } 126 func (sv paramValidations) SetMinLength(val int64) { sv.current.MinLength = &val } 127 func (sv paramValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val } 128 func (sv paramValidations) SetPattern(val string) { sv.current.Pattern = val } 129 func (sv paramValidations) SetUnique(val bool) { sv.current.UniqueItems = val } 130 func (sv paramValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val } 131 func (sv paramValidations) SetEnum(val string) { 132 sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format}) 133 } 134 func (sv paramValidations) SetDefault(val interface{}) { sv.current.Default = val } 135 func (sv paramValidations) SetExample(val interface{}) { sv.current.Example = val } 136 137 type itemsValidations struct { 138 current *spec.Items 139 } 140 141 func (sv itemsValidations) SetMaximum(val float64, exclusive bool) { 142 sv.current.Maximum = &val 143 sv.current.ExclusiveMaximum = exclusive 144 } 145 146 func (sv itemsValidations) SetMinimum(val float64, exclusive bool) { 147 sv.current.Minimum = &val 148 sv.current.ExclusiveMinimum = exclusive 149 } 150 func (sv itemsValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val } 151 func (sv itemsValidations) SetMinItems(val int64) { sv.current.MinItems = &val } 152 func (sv itemsValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val } 153 func (sv itemsValidations) SetMinLength(val int64) { sv.current.MinLength = &val } 154 func (sv itemsValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val } 155 func (sv itemsValidations) SetPattern(val string) { sv.current.Pattern = val } 156 func (sv itemsValidations) SetUnique(val bool) { sv.current.UniqueItems = val } 157 func (sv itemsValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val } 158 func (sv itemsValidations) SetEnum(val string) { 159 sv.current.Enum = parseEnum(val, &spec.SimpleSchema{Type: sv.current.Type, Format: sv.current.Format}) 160 } 161 func (sv itemsValidations) SetDefault(val interface{}) { sv.current.Default = val } 162 func (sv itemsValidations) SetExample(val interface{}) { sv.current.Example = val } 163 164 type parameterBuilder struct { 165 ctx *scanCtx 166 decl *entityDecl 167 postDecls []*entityDecl 168 } 169 170 func (p *parameterBuilder) Build(operations map[string]*spec.Operation) error { 171 // check if there is a swagger:parameters tag that is followed by one or more words, 172 // these words are the ids of the operations this parameter struct applies to 173 // once type name is found convert it to a schema, by looking up the schema in the 174 // parameters dictionary that got passed into this parse method 175 for _, opid := range p.decl.OperationIDs() { 176 operation, ok := operations[opid] 177 if !ok { 178 operation = new(spec.Operation) 179 operations[opid] = operation 180 operation.ID = opid 181 } 182 debugLog("building parameters for: %s", opid) 183 184 // analyze struct body for fields etc 185 // each exported struct field: 186 // * gets a type mapped to a go primitive 187 // * perhaps gets a format 188 // * has to document the validations that apply for the type and the field 189 // * when the struct field points to a model it becomes a ref: #/definitions/ModelName 190 // * comments that aren't tags is used as the description 191 if err := p.buildFromType(p.decl.Type, operation, make(map[string]spec.Parameter)); err != nil { 192 return err 193 } 194 } 195 return nil 196 } 197 198 func (p *parameterBuilder) buildFromType(otpe types.Type, op *spec.Operation, seen map[string]spec.Parameter) error { 199 switch tpe := otpe.(type) { 200 case *types.Pointer: 201 return p.buildFromType(tpe.Elem(), op, seen) 202 case *types.Named: 203 o := tpe.Obj() 204 switch stpe := o.Type().Underlying().(type) { 205 case *types.Struct: 206 debugLog("build from type %s: %T", tpe.Obj().Name(), otpe) 207 if decl, found := p.ctx.DeclForType(o.Type()); found { 208 return p.buildFromStruct(decl, stpe, op, seen) 209 } 210 return p.buildFromStruct(p.decl, stpe, op, seen) 211 default: 212 return fmt.Errorf("unhandled type (%T): %s", stpe, o.Type().Underlying().String()) 213 } 214 default: 215 return fmt.Errorf("unhandled type (%T): %s", otpe, tpe.String()) 216 } 217 } 218 219 func (p *parameterBuilder) buildFromField(fld *types.Var, tpe types.Type, typable swaggerTypable, seen map[string]spec.Parameter) error { 220 debugLog("build from field %s: %T", fld.Name(), tpe) 221 switch ftpe := tpe.(type) { 222 case *types.Basic: 223 return swaggerSchemaForType(ftpe.Name(), typable) 224 case *types.Struct: 225 sb := schemaBuilder{ 226 decl: p.decl, 227 ctx: p.ctx, 228 } 229 if err := sb.buildFromType(tpe, typable); err != nil { 230 return err 231 } 232 p.postDecls = append(p.postDecls, sb.postDecls...) 233 return nil 234 case *types.Pointer: 235 return p.buildFromField(fld, ftpe.Elem(), typable, seen) 236 case *types.Interface: 237 sb := schemaBuilder{ 238 decl: p.decl, 239 ctx: p.ctx, 240 } 241 if err := sb.buildFromType(tpe, typable); err != nil { 242 return err 243 } 244 p.postDecls = append(p.postDecls, sb.postDecls...) 245 return nil 246 case *types.Array: 247 return p.buildFromField(fld, ftpe.Elem(), typable.Items(), seen) 248 case *types.Slice: 249 return p.buildFromField(fld, ftpe.Elem(), typable.Items(), seen) 250 case *types.Map: 251 schema := new(spec.Schema) 252 typable.Schema().Typed("object", "").AdditionalProperties = &spec.SchemaOrBool{ 253 Schema: schema, 254 } 255 sb := schemaBuilder{ 256 decl: p.decl, 257 ctx: p.ctx, 258 } 259 if err := sb.buildFromType(ftpe.Elem(), schemaTypable{schema, typable.Level() + 1}); err != nil { 260 return err 261 } 262 return nil 263 case *types.Named: 264 if decl, found := p.ctx.DeclForType(ftpe.Obj().Type()); found { 265 if decl.Type.Obj().Pkg().Path() == "time" && decl.Type.Obj().Name() == "Time" { 266 typable.Typed("string", "date-time") 267 return nil 268 } 269 if sfnm, isf := strfmtName(decl.Comments); isf { 270 typable.Typed("string", sfnm) 271 return nil 272 } 273 sb := &schemaBuilder{ctx: p.ctx, decl: decl} 274 sb.inferNames() 275 if err := sb.buildFromType(decl.Type, typable); err != nil { 276 return err 277 } 278 p.postDecls = append(p.postDecls, sb.postDecls...) 279 return nil 280 } 281 return fmt.Errorf("unable to find package and source file for: %s", ftpe.String()) 282 default: 283 return fmt.Errorf("unknown type for %s: %T", fld.String(), fld.Type()) 284 } 285 } 286 287 func spExtensionsSetter(ps *spec.Parameter) func(*spec.Extensions) { 288 return func(exts *spec.Extensions) { 289 for name, value := range *exts { 290 addExtension(&ps.VendorExtensible, name, value) 291 } 292 } 293 } 294 295 func (p *parameterBuilder) buildFromStruct(decl *entityDecl, tpe *types.Struct, op *spec.Operation, seen map[string]spec.Parameter) error { 296 if tpe.NumFields() == 0 { 297 return nil 298 } 299 300 var sequence []string 301 302 for i := 0; i < tpe.NumFields(); i++ { 303 fld := tpe.Field(i) 304 305 if fld.Embedded() { 306 if err := p.buildFromType(fld.Type(), op, seen); err != nil { 307 return err 308 } 309 continue 310 } 311 312 if !fld.Exported() { 313 debugLog("skipping field %s because it's not exported", fld.Name()) 314 continue 315 } 316 317 tg := tpe.Tag(i) 318 319 var afld *ast.Field 320 ans, _ := astutil.PathEnclosingInterval(decl.File, fld.Pos(), fld.Pos()) 321 for _, an := range ans { 322 at, valid := an.(*ast.Field) 323 if !valid { 324 continue 325 } 326 327 debugLog("field %s: %s(%T) [%q] ==> %s", fld.Name(), fld.Type().String(), fld.Type(), tg, at.Doc.Text()) 328 afld = at 329 break 330 } 331 332 if afld == nil { 333 debugLog("can't find source associated with %s for %s", fld.String(), tpe.String()) 334 continue 335 } 336 337 // if the field is annotated with swagger:ignore, ignore it 338 if ignored(afld.Doc) { 339 continue 340 } 341 342 name, ignore, _, err := parseJSONTag(afld) 343 if err != nil { 344 return err 345 } 346 if ignore { 347 continue 348 } 349 350 in := "query" 351 // scan for param location first, this changes some behavior down the line 352 if afld.Doc != nil { 353 for _, cmt := range afld.Doc.List { 354 for _, line := range strings.Split(cmt.Text, "\n") { 355 matches := rxIn.FindStringSubmatch(line) 356 if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 { 357 in = strings.TrimSpace(matches[1]) 358 } 359 } 360 } 361 } 362 363 ps := seen[name] 364 ps.In = in 365 var pty swaggerTypable = paramTypable{&ps} 366 if in == "body" { 367 pty = schemaTypable{pty.Schema(), 0} 368 } 369 if in == "formData" && afld.Doc != nil && fileParam(afld.Doc) { 370 pty.Typed("file", "") 371 } else if err := p.buildFromField(fld, fld.Type(), pty, seen); err != nil { 372 return err 373 } 374 375 if strfmtName, ok := strfmtName(afld.Doc); ok { 376 ps.Typed("string", strfmtName) 377 ps.Ref = spec.Ref{} 378 ps.Items = nil 379 } 380 381 sp := new(sectionedParser) 382 sp.setDescription = func(lines []string) { 383 ps.Description = joinDropLast(lines) 384 enumDesc := getEnumDesc(ps.Extensions) 385 if enumDesc != "" { 386 ps.Description += "\n" + enumDesc 387 } 388 } 389 if ps.Ref.String() == "" { 390 sp.taggers = []tagParser{ 391 newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}), 392 newSingleLineTagParser("maximum", &setMaximum{paramValidations{&ps}, rxf(rxMaximumFmt, "")}), 393 newSingleLineTagParser("minimum", &setMinimum{paramValidations{&ps}, rxf(rxMinimumFmt, "")}), 394 newSingleLineTagParser("multipleOf", &setMultipleOf{paramValidations{&ps}, rxf(rxMultipleOfFmt, "")}), 395 newSingleLineTagParser("minLength", &setMinLength{paramValidations{&ps}, rxf(rxMinLengthFmt, "")}), 396 newSingleLineTagParser("maxLength", &setMaxLength{paramValidations{&ps}, rxf(rxMaxLengthFmt, "")}), 397 newSingleLineTagParser("pattern", &setPattern{paramValidations{&ps}, rxf(rxPatternFmt, "")}), 398 newSingleLineTagParser("collectionFormat", &setCollectionFormat{paramValidations{&ps}, rxf(rxCollectionFormatFmt, "")}), 399 newSingleLineTagParser("minItems", &setMinItems{paramValidations{&ps}, rxf(rxMinItemsFmt, "")}), 400 newSingleLineTagParser("maxItems", &setMaxItems{paramValidations{&ps}, rxf(rxMaxItemsFmt, "")}), 401 newSingleLineTagParser("unique", &setUnique{paramValidations{&ps}, rxf(rxUniqueFmt, "")}), 402 newSingleLineTagParser("enum", &setEnum{paramValidations{&ps}, rxf(rxEnumFmt, "")}), 403 newSingleLineTagParser("default", &setDefault{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxDefaultFmt, "")}), 404 newSingleLineTagParser("example", &setExample{&ps.SimpleSchema, paramValidations{&ps}, rxf(rxExampleFmt, "")}), 405 newSingleLineTagParser("required", &setRequiredParam{&ps}), 406 newMultiLineTagParser("Extensions", newSetExtensions(spExtensionsSetter(&ps)), true), 407 } 408 409 itemsTaggers := func(items *spec.Items, level int) []tagParser { 410 // the expression is 1-index based not 0-index 411 itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1) 412 413 return []tagParser{ 414 newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{itemsValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}), 415 newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{itemsValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}), 416 newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{itemsValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}), 417 newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{itemsValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}), 418 newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{itemsValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}), 419 newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{itemsValidations{items}, rxf(rxPatternFmt, itemsPrefix)}), 420 newSingleLineTagParser(fmt.Sprintf("items%dCollectionFormat", level), &setCollectionFormat{itemsValidations{items}, rxf(rxCollectionFormatFmt, itemsPrefix)}), 421 newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{itemsValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}), 422 newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{itemsValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}), 423 newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{itemsValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}), 424 newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{itemsValidations{items}, rxf(rxEnumFmt, itemsPrefix)}), 425 newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&items.SimpleSchema, itemsValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}), 426 newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&items.SimpleSchema, itemsValidations{items}, rxf(rxExampleFmt, itemsPrefix)}), 427 } 428 } 429 430 var parseArrayTypes func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) 431 parseArrayTypes = func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) { 432 if items == nil { 433 return []tagParser{}, nil 434 } 435 switch iftpe := expr.(type) { 436 case *ast.ArrayType: 437 eleTaggers := itemsTaggers(items, level) 438 sp.taggers = append(eleTaggers, sp.taggers...) 439 otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Items, level+1) 440 if err != nil { 441 return nil, err 442 } 443 return otherTaggers, nil 444 case *ast.SelectorExpr: 445 otherTaggers, err := parseArrayTypes(iftpe.Sel, items.Items, level+1) 446 if err != nil { 447 return nil, err 448 } 449 return otherTaggers, nil 450 case *ast.Ident: 451 taggers := []tagParser{} 452 if iftpe.Obj == nil { 453 taggers = itemsTaggers(items, level) 454 } 455 otherTaggers, err := parseArrayTypes(expr, items.Items, level+1) 456 if err != nil { 457 return nil, err 458 } 459 return append(taggers, otherTaggers...), nil 460 case *ast.StarExpr: 461 otherTaggers, err := parseArrayTypes(iftpe.X, items, level) 462 if err != nil { 463 return nil, err 464 } 465 return otherTaggers, nil 466 default: 467 return nil, fmt.Errorf("unknown field type ele for %q", name) 468 } 469 } 470 471 // check if this is a primitive, if so parse the validations from the 472 // doc comments of the slice declaration. 473 if ftped, ok := afld.Type.(*ast.ArrayType); ok { 474 taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0) 475 if err != nil { 476 return err 477 } 478 sp.taggers = append(taggers, sp.taggers...) 479 } 480 481 } else { 482 sp.taggers = []tagParser{ 483 newSingleLineTagParser("in", &matchOnlyParam{&ps, rxIn}), 484 newSingleLineTagParser("required", &matchOnlyParam{&ps, rxRequired}), 485 newMultiLineTagParser("Extensions", newSetExtensions(spExtensionsSetter(&ps)), true), 486 } 487 } 488 if err := sp.Parse(afld.Doc); err != nil { 489 return err 490 } 491 if ps.In == "path" { 492 ps.Required = true 493 } 494 495 if ps.Name == "" { 496 ps.Name = name 497 } 498 499 if name != fld.Name() { 500 addExtension(&ps.VendorExtensible, "x-go-name", fld.Name()) 501 } 502 seen[name] = ps 503 sequence = append(sequence, name) 504 } 505 506 for _, k := range sequence { 507 p := seen[k] 508 for i, v := range op.Parameters { 509 if v.Name == k { 510 op.Parameters = append(op.Parameters[:i], op.Parameters[i+1:]...) 511 break 512 } 513 } 514 op.Parameters = append(op.Parameters, p) 515 } 516 return nil 517 }