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  }