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