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  }