github.com/yilecjw/go-swagger@v0.19.0/scan/responses.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 "golang.org/x/tools/go/loader" 23 24 "github.com/go-openapi/spec" 25 ) 26 27 type responseTypable struct { 28 in string 29 header *spec.Header 30 response *spec.Response 31 } 32 33 func (ht responseTypable) Level() int { return 0 } 34 35 func (ht responseTypable) Typed(tpe, format string) { 36 ht.header.Typed(tpe, format) 37 } 38 39 func bodyTypable(in string, schema *spec.Schema) (swaggerTypable, *spec.Schema) { 40 if in == "body" { 41 // get the schema for items on the schema property 42 if schema == nil { 43 schema = new(spec.Schema) 44 } 45 if schema.Items == nil { 46 schema.Items = new(spec.SchemaOrArray) 47 } 48 if schema.Items.Schema == nil { 49 schema.Items.Schema = new(spec.Schema) 50 } 51 schema.Typed("array", "") 52 return schemaTypable{schema.Items.Schema, 0}, schema 53 } 54 return nil, nil 55 } 56 57 func (ht responseTypable) Items() swaggerTypable { 58 bdt, schema := bodyTypable(ht.in, ht.response.Schema) 59 if bdt != nil { 60 ht.response.Schema = schema 61 return bdt 62 } 63 64 if ht.header.Items == nil { 65 ht.header.Items = new(spec.Items) 66 } 67 ht.header.Type = "array" 68 return itemsTypable{ht.header.Items, 1} 69 } 70 71 func (ht responseTypable) SetRef(ref spec.Ref) { 72 // having trouble seeing the usefulness of this one here 73 ht.Schema().Ref = ref 74 } 75 76 func (ht responseTypable) Schema() *spec.Schema { 77 if ht.response.Schema == nil { 78 ht.response.Schema = new(spec.Schema) 79 } 80 return ht.response.Schema 81 } 82 83 func (ht responseTypable) SetSchema(schema *spec.Schema) { 84 ht.response.Schema = schema 85 } 86 func (ht responseTypable) CollectionOf(items *spec.Items, format string) { 87 ht.header.CollectionOf(items, format) 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 func (sv headerValidations) SetMinimum(val float64, exclusive bool) { 99 sv.current.Minimum = &val 100 sv.current.ExclusiveMinimum = exclusive 101 } 102 func (sv headerValidations) SetMultipleOf(val float64) { sv.current.MultipleOf = &val } 103 func (sv headerValidations) SetMinItems(val int64) { sv.current.MinItems = &val } 104 func (sv headerValidations) SetMaxItems(val int64) { sv.current.MaxItems = &val } 105 func (sv headerValidations) SetMinLength(val int64) { sv.current.MinLength = &val } 106 func (sv headerValidations) SetMaxLength(val int64) { sv.current.MaxLength = &val } 107 func (sv headerValidations) SetPattern(val string) { sv.current.Pattern = val } 108 func (sv headerValidations) SetUnique(val bool) { sv.current.UniqueItems = val } 109 func (sv headerValidations) SetCollectionFormat(val string) { sv.current.CollectionFormat = val } 110 func (sv headerValidations) SetEnum(val string) { 111 list := strings.Split(val, ",") 112 interfaceSlice := make([]interface{}, len(list)) 113 for i, d := range list { 114 interfaceSlice[i] = d 115 } 116 sv.current.Enum = interfaceSlice 117 } 118 func (sv headerValidations) SetDefault(val interface{}) { sv.current.Default = val } 119 func (sv headerValidations) SetExample(val interface{}) { sv.current.Example = val } 120 121 func newResponseDecl(file *ast.File, decl *ast.GenDecl, ts *ast.TypeSpec) responseDecl { 122 var rd responseDecl 123 rd.File = file 124 rd.Decl = decl 125 rd.TypeSpec = ts 126 rd.inferNames() 127 return rd 128 } 129 130 type responseDecl struct { 131 File *ast.File 132 Decl *ast.GenDecl 133 TypeSpec *ast.TypeSpec 134 GoName string 135 Name string 136 annotated bool 137 } 138 139 func (sd *responseDecl) hasAnnotation() bool { 140 sd.inferNames() 141 return sd.annotated 142 } 143 144 func (sd *responseDecl) inferNames() (goName string, name string) { 145 if sd.GoName != "" { 146 goName, name = sd.GoName, sd.Name 147 return 148 } 149 goName = sd.TypeSpec.Name.Name 150 name = goName 151 if sd.Decl.Doc != nil { 152 DECLS: 153 for _, cmt := range sd.Decl.Doc.List { 154 for _, ln := range strings.Split(cmt.Text, "\n") { 155 matches := rxResponseOverride.FindStringSubmatch(ln) 156 if len(matches) > 0 { 157 sd.annotated = true 158 } 159 if len(matches) > 1 && len(matches[1]) > 0 { 160 name = matches[1] 161 break DECLS 162 } 163 } 164 } 165 } 166 sd.GoName = goName 167 sd.Name = name 168 return 169 } 170 171 func newResponseParser(prog *loader.Program) *responseParser { 172 return &responseParser{prog, nil, newSchemaParser(prog)} 173 } 174 175 type responseParser struct { 176 program *loader.Program 177 postDecls []schemaDecl 178 scp *schemaParser 179 } 180 181 func (rp *responseParser) Parse(gofile *ast.File, target interface{}) error { 182 tgt := target.(map[string]spec.Response) 183 for _, decl := range gofile.Decls { 184 switch x1 := decl.(type) { 185 // Check for parameters at the package level. 186 case *ast.GenDecl: 187 for _, spc := range x1.Specs { 188 switch x2 := spc.(type) { 189 case *ast.TypeSpec: 190 sd := newResponseDecl(gofile, x1, x2) 191 if sd.hasAnnotation() { 192 if err := rp.parseDecl(tgt, sd); err != nil { 193 return err 194 } 195 } 196 } 197 } 198 // Check for parameters inside functions. 199 case *ast.FuncDecl: 200 for _, b := range x1.Body.List { 201 switch x2 := b.(type) { 202 case *ast.DeclStmt: 203 switch x3 := x2.Decl.(type) { 204 case *ast.GenDecl: 205 for _, spc := range x3.Specs { 206 switch x4 := spc.(type) { 207 case *ast.TypeSpec: 208 sd := newResponseDecl(gofile, x3, x4) 209 if sd.hasAnnotation() { 210 if err := rp.parseDecl(tgt, sd); err != nil { 211 return err 212 } 213 } 214 } 215 } 216 } 217 } 218 } 219 } 220 } 221 return nil 222 } 223 224 func (rp *responseParser) parseDecl(responses map[string]spec.Response, decl responseDecl) error { 225 // check if there is a swagger:parameters tag that is followed by one or more words, 226 // these words are the ids of the operations this parameter struct applies to 227 // once type name is found convert it to a schema, by looking up the schema in the 228 // parameters dictionary that got passed into this parse method 229 response := responses[decl.Name] 230 resPtr := &response 231 232 // analyze doc comment for the model 233 sp := new(sectionedParser) 234 sp.setDescription = func(lines []string) { resPtr.Description = joinDropLast(lines) } 235 if err := sp.Parse(decl.Decl.Doc); err != nil { 236 return err 237 } 238 239 // analyze struct body for fields etc 240 // each exported struct field: 241 // * gets a type mapped to a go primitive 242 // * perhaps gets a format 243 // * has to document the validations that apply for the type and the field 244 // * when the struct field points to a model it becomes a ref: #/definitions/ModelName 245 // * comments that aren't tags is used as the description 246 if tpe, ok := decl.TypeSpec.Type.(*ast.StructType); ok { 247 if err := rp.parseStructType(decl.File, resPtr, tpe, make(map[string]struct{})); err != nil { 248 return err 249 } 250 } 251 252 responses[decl.Name] = response 253 return nil 254 } 255 256 func (rp *responseParser) parseEmbeddedStruct(gofile *ast.File, response *spec.Response, expr ast.Expr, seenPreviously map[string]struct{}) error { 257 switch tpe := expr.(type) { 258 case *ast.Ident: 259 // do lookup of type 260 // take primitives into account, they should result in an error for swagger 261 pkg, err := rp.scp.packageForFile(gofile, tpe) 262 if err != nil { 263 return fmt.Errorf("embedded struct: %v", err) 264 } 265 file, _, ts, err := findSourceFile(pkg, tpe.Name) 266 if err != nil { 267 return fmt.Errorf("embedded struct: %v", err) 268 } 269 if st, ok := ts.Type.(*ast.StructType); ok { 270 return rp.parseStructType(file, response, st, seenPreviously) 271 } 272 case *ast.SelectorExpr: 273 // look up package, file and then type 274 pkg, err := rp.scp.packageForSelector(gofile, tpe.X) 275 if err != nil { 276 return fmt.Errorf("embedded struct: %v", err) 277 } 278 file, _, ts, err := findSourceFile(pkg, tpe.Sel.Name) 279 if err != nil { 280 return fmt.Errorf("embedded struct: %v", err) 281 } 282 if st, ok := ts.Type.(*ast.StructType); ok { 283 return rp.parseStructType(file, response, st, seenPreviously) 284 } 285 case *ast.StarExpr: 286 return rp.parseEmbeddedStruct(gofile, response, tpe.X, seenPreviously) 287 } 288 fmt.Printf("1%#v\n", expr) 289 return fmt.Errorf("unable to resolve embedded struct for: %v", expr) 290 } 291 292 func (rp *responseParser) parseStructType(gofile *ast.File, response *spec.Response, tpe *ast.StructType, seenPreviously map[string]struct{}) error { 293 if tpe.Fields != nil { 294 295 seenProperties := seenPreviously 296 297 for _, fld := range tpe.Fields.List { 298 if len(fld.Names) == 0 { 299 // when the embedded struct is annotated with swagger:allOf it will be used as allOf property 300 // otherwise the fields will just be included as normal properties 301 if err := rp.parseEmbeddedStruct(gofile, response, fld.Type, seenProperties); err != nil { 302 return err 303 } 304 } 305 } 306 307 for _, fld := range tpe.Fields.List { 308 if len(fld.Names) > 0 && fld.Names[0] != nil && fld.Names[0].IsExported() { 309 nm, ignore, _, err := parseJSONTag(fld) 310 if err != nil { 311 return err 312 } 313 if ignore { 314 continue 315 } 316 317 var in string 318 // scan for param location first, this changes some behavior down the line 319 if fld.Doc != nil { 320 for _, cmt := range fld.Doc.List { 321 for _, line := range strings.Split(cmt.Text, "\n") { 322 matches := rxIn.FindStringSubmatch(line) 323 if len(matches) > 0 && len(strings.TrimSpace(matches[1])) > 0 { 324 in = strings.TrimSpace(matches[1]) 325 } 326 } 327 } 328 } 329 330 ps := response.Headers[nm] 331 332 // support swagger:file for response 333 // An API operation can return a file, such as an image or PDF. In this case, 334 // define the response schema with type: file and specify the appropriate MIME types in the produces section. 335 if fld.Doc != nil && fileParam(fld.Doc) { 336 response.Schema = &spec.Schema{} 337 response.Schema.Typed("file", "") 338 } else if err := rp.scp.parseNamedType(gofile, fld.Type, responseTypable{in, &ps, response}); err != nil { 339 return err 340 } 341 342 if strfmtName, ok := strfmtName(fld.Doc); ok { 343 ps.Typed("string", strfmtName) 344 } 345 346 sp := new(sectionedParser) 347 sp.setDescription = func(lines []string) { ps.Description = joinDropLast(lines) } 348 sp.taggers = []tagParser{ 349 newSingleLineTagParser("maximum", &setMaximum{headerValidations{&ps}, rxf(rxMaximumFmt, "")}), 350 newSingleLineTagParser("minimum", &setMinimum{headerValidations{&ps}, rxf(rxMinimumFmt, "")}), 351 newSingleLineTagParser("multipleOf", &setMultipleOf{headerValidations{&ps}, rxf(rxMultipleOfFmt, "")}), 352 newSingleLineTagParser("minLength", &setMinLength{headerValidations{&ps}, rxf(rxMinLengthFmt, "")}), 353 newSingleLineTagParser("maxLength", &setMaxLength{headerValidations{&ps}, rxf(rxMaxLengthFmt, "")}), 354 newSingleLineTagParser("pattern", &setPattern{headerValidations{&ps}, rxf(rxPatternFmt, "")}), 355 newSingleLineTagParser("collectionFormat", &setCollectionFormat{headerValidations{&ps}, rxf(rxCollectionFormatFmt, "")}), 356 newSingleLineTagParser("minItems", &setMinItems{headerValidations{&ps}, rxf(rxMinItemsFmt, "")}), 357 newSingleLineTagParser("maxItems", &setMaxItems{headerValidations{&ps}, rxf(rxMaxItemsFmt, "")}), 358 newSingleLineTagParser("unique", &setUnique{headerValidations{&ps}, rxf(rxUniqueFmt, "")}), 359 newSingleLineTagParser("enum", &setEnum{headerValidations{&ps}, rxf(rxEnumFmt, "")}), 360 newSingleLineTagParser("default", &setDefault{&ps.SimpleSchema, headerValidations{&ps}, rxf(rxDefaultFmt, "")}), 361 newSingleLineTagParser("example", &setExample{&ps.SimpleSchema, headerValidations{&ps}, rxf(rxExampleFmt, "")}), 362 } 363 itemsTaggers := func(items *spec.Items, level int) []tagParser { 364 // the expression is 1-index based not 0-index 365 itemsPrefix := fmt.Sprintf(rxItemsPrefixFmt, level+1) 366 367 return []tagParser{ 368 newSingleLineTagParser(fmt.Sprintf("items%dMaximum", level), &setMaximum{itemsValidations{items}, rxf(rxMaximumFmt, itemsPrefix)}), 369 newSingleLineTagParser(fmt.Sprintf("items%dMinimum", level), &setMinimum{itemsValidations{items}, rxf(rxMinimumFmt, itemsPrefix)}), 370 newSingleLineTagParser(fmt.Sprintf("items%dMultipleOf", level), &setMultipleOf{itemsValidations{items}, rxf(rxMultipleOfFmt, itemsPrefix)}), 371 newSingleLineTagParser(fmt.Sprintf("items%dMinLength", level), &setMinLength{itemsValidations{items}, rxf(rxMinLengthFmt, itemsPrefix)}), 372 newSingleLineTagParser(fmt.Sprintf("items%dMaxLength", level), &setMaxLength{itemsValidations{items}, rxf(rxMaxLengthFmt, itemsPrefix)}), 373 newSingleLineTagParser(fmt.Sprintf("items%dPattern", level), &setPattern{itemsValidations{items}, rxf(rxPatternFmt, itemsPrefix)}), 374 newSingleLineTagParser(fmt.Sprintf("items%dCollectionFormat", level), &setCollectionFormat{itemsValidations{items}, rxf(rxCollectionFormatFmt, itemsPrefix)}), 375 newSingleLineTagParser(fmt.Sprintf("items%dMinItems", level), &setMinItems{itemsValidations{items}, rxf(rxMinItemsFmt, itemsPrefix)}), 376 newSingleLineTagParser(fmt.Sprintf("items%dMaxItems", level), &setMaxItems{itemsValidations{items}, rxf(rxMaxItemsFmt, itemsPrefix)}), 377 newSingleLineTagParser(fmt.Sprintf("items%dUnique", level), &setUnique{itemsValidations{items}, rxf(rxUniqueFmt, itemsPrefix)}), 378 newSingleLineTagParser(fmt.Sprintf("items%dEnum", level), &setEnum{itemsValidations{items}, rxf(rxEnumFmt, itemsPrefix)}), 379 newSingleLineTagParser(fmt.Sprintf("items%dDefault", level), &setDefault{&items.SimpleSchema, itemsValidations{items}, rxf(rxDefaultFmt, itemsPrefix)}), 380 newSingleLineTagParser(fmt.Sprintf("items%dExample", level), &setExample{&items.SimpleSchema, itemsValidations{items}, rxf(rxExampleFmt, itemsPrefix)}), 381 } 382 } 383 384 var parseArrayTypes func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) 385 parseArrayTypes = func(expr ast.Expr, items *spec.Items, level int) ([]tagParser, error) { 386 if items == nil { 387 return []tagParser{}, nil 388 } 389 switch iftpe := expr.(type) { 390 case *ast.ArrayType: 391 eleTaggers := itemsTaggers(items, level) 392 sp.taggers = append(eleTaggers, sp.taggers...) 393 otherTaggers, err := parseArrayTypes(iftpe.Elt, items.Items, level+1) 394 if err != nil { 395 return nil, err 396 } 397 return otherTaggers, nil 398 case *ast.Ident: 399 taggers := []tagParser{} 400 if iftpe.Obj == nil { 401 taggers = itemsTaggers(items, level) 402 } 403 otherTaggers, err := parseArrayTypes(expr, items.Items, level+1) 404 if err != nil { 405 return nil, err 406 } 407 return append(taggers, otherTaggers...), nil 408 case *ast.StarExpr: 409 otherTaggers, err := parseArrayTypes(iftpe.X, items, level) 410 if err != nil { 411 return nil, err 412 } 413 return otherTaggers, nil 414 default: 415 return nil, fmt.Errorf("unknown field type ele for %q", nm) 416 } 417 } 418 // check if this is a primitive, if so parse the validations from the 419 // doc comments of the slice declaration. 420 if ftped, ok := fld.Type.(*ast.ArrayType); ok { 421 taggers, err := parseArrayTypes(ftped.Elt, ps.Items, 0) 422 if err != nil { 423 return err 424 } 425 sp.taggers = append(taggers, sp.taggers...) 426 } 427 428 if err := sp.Parse(fld.Doc); err != nil { 429 return err 430 } 431 432 if in != "body" { 433 seenProperties[nm] = struct{}{} 434 if response.Headers == nil { 435 response.Headers = make(map[string]spec.Header) 436 } 437 response.Headers[nm] = ps 438 } 439 } 440 } 441 442 for k := range response.Headers { 443 if _, ok := seenProperties[k]; !ok { 444 delete(response.Headers, k) 445 } 446 } 447 } 448 449 return nil 450 }