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