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