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