github.com/blp1526/goa@v1.4.0/goagen/gen_swagger/swagger.go (about)

     1  package genswagger
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/goadesign/goa/design"
    11  	"github.com/goadesign/goa/dslengine"
    12  	"github.com/goadesign/goa/goagen/gen_schema"
    13  )
    14  
    15  type (
    16  	// Swagger represents an instance of a swagger object.
    17  	// See https://swagger.io/specification/
    18  	Swagger struct {
    19  		Swagger             string                           `json:"swagger,omitempty"`
    20  		Info                *Info                            `json:"info,omitempty"`
    21  		Host                string                           `json:"host,omitempty"`
    22  		BasePath            string                           `json:"basePath,omitempty"`
    23  		Schemes             []string                         `json:"schemes,omitempty"`
    24  		Consumes            []string                         `json:"consumes,omitempty"`
    25  		Produces            []string                         `json:"produces,omitempty"`
    26  		Paths               map[string]interface{}           `json:"paths"`
    27  		Definitions         map[string]*genschema.JSONSchema `json:"definitions,omitempty"`
    28  		Parameters          map[string]*Parameter            `json:"parameters,omitempty"`
    29  		Responses           map[string]*Response             `json:"responses,omitempty"`
    30  		SecurityDefinitions map[string]*SecurityDefinition   `json:"securityDefinitions,omitempty"`
    31  		Tags                []*Tag                           `json:"tags,omitempty"`
    32  		ExternalDocs        *ExternalDocs                    `json:"externalDocs,omitempty"`
    33  	}
    34  
    35  	// Info provides metadata about the API. The metadata can be used by the clients if needed,
    36  	// and can be presented in the Swagger-UI for convenience.
    37  	Info struct {
    38  		Title          string                    `json:"title,omitempty"`
    39  		Description    string                    `json:"description,omitempty"`
    40  		TermsOfService string                    `json:"termsOfService,omitempty"`
    41  		Contact        *design.ContactDefinition `json:"contact,omitempty"`
    42  		License        *design.LicenseDefinition `json:"license,omitempty"`
    43  		Version        string                    `json:"version"`
    44  		Extensions     map[string]interface{}    `json:"-"`
    45  	}
    46  
    47  	// Path holds the relative paths to the individual endpoints.
    48  	Path struct {
    49  		// Ref allows for an external definition of this path item.
    50  		Ref string `json:"$ref,omitempty"`
    51  		// Get defines a GET operation on this path.
    52  		Get *Operation `json:"get,omitempty"`
    53  		// Put defines a PUT operation on this path.
    54  		Put *Operation `json:"put,omitempty"`
    55  		// Post defines a POST operation on this path.
    56  		Post *Operation `json:"post,omitempty"`
    57  		// Delete defines a DELETE operation on this path.
    58  		Delete *Operation `json:"delete,omitempty"`
    59  		// Options defines a OPTIONS operation on this path.
    60  		Options *Operation `json:"options,omitempty"`
    61  		// Head defines a HEAD operation on this path.
    62  		Head *Operation `json:"head,omitempty"`
    63  		// Patch defines a PATCH operation on this path.
    64  		Patch *Operation `json:"patch,omitempty"`
    65  		// Parameters is the list of parameters that are applicable for all the operations
    66  		// described under this path.
    67  		Parameters []*Parameter `json:"parameters,omitempty"`
    68  		// Extensions defines the swagger extensions.
    69  		Extensions map[string]interface{} `json:"-"`
    70  	}
    71  
    72  	// Operation describes a single API operation on a path.
    73  	Operation struct {
    74  		// Tags is a list of tags for API documentation control. Tags can be used for
    75  		// logical grouping of operations by resources or any other qualifier.
    76  		Tags []string `json:"tags,omitempty"`
    77  		// Summary is a short summary of what the operation does. For maximum readability
    78  		// in the swagger-ui, this field should be less than 120 characters.
    79  		Summary string `json:"summary,omitempty"`
    80  		// Description is a verbose explanation of the operation behavior.
    81  		// GFM syntax can be used for rich text representation.
    82  		Description string `json:"description,omitempty"`
    83  		// ExternalDocs points to additional external documentation for this operation.
    84  		ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
    85  		// OperationID is a unique string used to identify the operation.
    86  		OperationID string `json:"operationId,omitempty"`
    87  		// Consumes is a list of MIME types the operation can consume.
    88  		Consumes []string `json:"consumes,omitempty"`
    89  		// Produces is a list of MIME types the operation can produce.
    90  		Produces []string `json:"produces,omitempty"`
    91  		// Parameters is a list of parameters that are applicable for this operation.
    92  		Parameters []*Parameter `json:"parameters,omitempty"`
    93  		// Responses is the list of possible responses as they are returned from executing
    94  		// this operation.
    95  		Responses map[string]*Response `json:"responses,omitempty"`
    96  		// Schemes is the transfer protocol for the operation.
    97  		Schemes []string `json:"schemes,omitempty"`
    98  		// Deprecated declares this operation to be deprecated.
    99  		Deprecated bool `json:"deprecated,omitempty"`
   100  		// Secury is a declaration of which security schemes are applied for this operation.
   101  		Security []map[string][]string `json:"security,omitempty"`
   102  		// Extensions defines the swagger extensions.
   103  		Extensions map[string]interface{} `json:"-"`
   104  	}
   105  
   106  	// Parameter describes a single operation parameter.
   107  	Parameter struct {
   108  		// Name of the parameter. Parameter names are case sensitive.
   109  		Name string `json:"name"`
   110  		// In is the location of the parameter.
   111  		// Possible values are "query", "header", "path", "formData" or "body".
   112  		In string `json:"in"`
   113  		// Description is`a brief description of the parameter.
   114  		// GFM syntax can be used for rich text representation.
   115  		Description string `json:"description,omitempty"`
   116  		// Required determines whether this parameter is mandatory.
   117  		Required bool `json:"required"`
   118  		// Schema defining the type used for the body parameter, only if "in" is body
   119  		Schema *genschema.JSONSchema `json:"schema,omitempty"`
   120  
   121  		// properties below only apply if "in" is not body
   122  
   123  		//  Type of the parameter. Since the parameter is not located at the request body,
   124  		// it is limited to simple types (that is, not an object).
   125  		Type string `json:"type,omitempty"`
   126  		// Format is the extending format for the previously mentioned type.
   127  		Format string `json:"format,omitempty"`
   128  		// AllowEmptyValue sets the ability to pass empty-valued parameters.
   129  		// This is valid only for either query or formData parameters and allows you to
   130  		// send a parameter with a name only or an empty value. Default value is false.
   131  		AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
   132  		// Items describes the type of items in the array if type is "array".
   133  		Items *Items `json:"items,omitempty"`
   134  		// CollectionFormat determines the format of the array if type array is used.
   135  		// Possible values are csv, ssv, tsv, pipes and multi.
   136  		CollectionFormat string `json:"collectionFormat,omitempty"`
   137  		// Default declares the value of the parameter that the server will use if none is
   138  		// provided, for example a "count" to control the number of results per page might
   139  		// default to 100 if not supplied by the client in the request.
   140  		Default          interface{}   `json:"default,omitempty"`
   141  		Maximum          *float64      `json:"maximum,omitempty"`
   142  		ExclusiveMaximum bool          `json:"exclusiveMaximum,omitempty"`
   143  		Minimum          *float64      `json:"minimum,omitempty"`
   144  		ExclusiveMinimum bool          `json:"exclusiveMinimum,omitempty"`
   145  		MaxLength        *int          `json:"maxLength,omitempty"`
   146  		MinLength        *int          `json:"minLength,omitempty"`
   147  		Pattern          string        `json:"pattern,omitempty"`
   148  		MaxItems         *int          `json:"maxItems,omitempty"`
   149  		MinItems         *int          `json:"minItems,omitempty"`
   150  		UniqueItems      bool          `json:"uniqueItems,omitempty"`
   151  		Enum             []interface{} `json:"enum,omitempty"`
   152  		MultipleOf       float64       `json:"multipleOf,omitempty"`
   153  		// Extensions defines the swagger extensions.
   154  		Extensions map[string]interface{} `json:"-"`
   155  	}
   156  
   157  	// Response describes an operation response.
   158  	Response struct {
   159  		// Description of the response. GFM syntax can be used for rich text representation.
   160  		Description string `json:"description,omitempty"`
   161  		// Schema is a definition of the response structure. It can be a primitive,
   162  		// an array or an object. If this field does not exist, it means no content is
   163  		// returned as part of the response. As an extension to the Schema Object, its root
   164  		// type value may also be "file".
   165  		Schema *genschema.JSONSchema `json:"schema,omitempty"`
   166  		// Headers is a list of headers that are sent with the response.
   167  		Headers map[string]*Header `json:"headers,omitempty"`
   168  		// Ref references a global API response.
   169  		// This field is exclusive with the other fields of Response.
   170  		Ref string `json:"$ref,omitempty"`
   171  		// Extensions defines the swagger extensions.
   172  		Extensions map[string]interface{} `json:"-"`
   173  	}
   174  
   175  	// Header represents a header parameter.
   176  	Header struct {
   177  		// Description is`a brief description of the parameter.
   178  		// GFM syntax can be used for rich text representation.
   179  		Description string `json:"description,omitempty"`
   180  		//  Type of the header. it is limited to simple types (that is, not an object).
   181  		Type string `json:"type,omitempty"`
   182  		// Format is the extending format for the previously mentioned type.
   183  		Format string `json:"format,omitempty"`
   184  		// Items describes the type of items in the array if type is "array".
   185  		Items *Items `json:"items,omitempty"`
   186  		// CollectionFormat determines the format of the array if type array is used.
   187  		// Possible values are csv, ssv, tsv, pipes and multi.
   188  		CollectionFormat string `json:"collectionFormat,omitempty"`
   189  		// Default declares the value of the parameter that the server will use if none is
   190  		// provided, for example a "count" to control the number of results per page might
   191  		// default to 100 if not supplied by the client in the request.
   192  		Default          interface{}   `json:"default,omitempty"`
   193  		Maximum          *float64      `json:"maximum,omitempty"`
   194  		ExclusiveMaximum bool          `json:"exclusiveMaximum,omitempty"`
   195  		Minimum          *float64      `json:"minimum,omitempty"`
   196  		ExclusiveMinimum bool          `json:"exclusiveMinimum,omitempty"`
   197  		MaxLength        *int          `json:"maxLength,omitempty"`
   198  		MinLength        *int          `json:"minLength,omitempty"`
   199  		Pattern          string        `json:"pattern,omitempty"`
   200  		MaxItems         *int          `json:"maxItems,omitempty"`
   201  		MinItems         *int          `json:"minItems,omitempty"`
   202  		UniqueItems      bool          `json:"uniqueItems,omitempty"`
   203  		Enum             []interface{} `json:"enum,omitempty"`
   204  		MultipleOf       float64       `json:"multipleOf,omitempty"`
   205  	}
   206  
   207  	// SecurityDefinition allows the definition of a security scheme that can be used by the
   208  	// operations. Supported schemes are basic authentication, an API key (either as a header or
   209  	// as a query parameter) and OAuth2's common flows (implicit, password, application and
   210  	// access code).
   211  	SecurityDefinition struct {
   212  		// Type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
   213  		Type string `json:"type"`
   214  		// Description for security scheme
   215  		Description string `json:"description,omitempty"`
   216  		// Name of the header or query parameter to be used when type is "apiKey".
   217  		Name string `json:"name,omitempty"`
   218  		// In is the location of the API key when type is "apiKey".
   219  		// Valid values are "query" or "header".
   220  		In string `json:"in,omitempty"`
   221  		// Flow is the flow used by the OAuth2 security scheme when type is "oauth2"
   222  		// Valid values are "implicit", "password", "application" or "accessCode".
   223  		Flow string `json:"flow,omitempty"`
   224  		// The oauth2 authorization URL to be used for this flow.
   225  		AuthorizationURL string `json:"authorizationUrl,omitempty"`
   226  		// TokenURL  is the token URL to be used for this flow.
   227  		TokenURL string `json:"tokenUrl,omitempty"`
   228  		// Scopes list the  available scopes for the OAuth2 security scheme.
   229  		Scopes map[string]string `json:"scopes,omitempty"`
   230  		// Extensions defines the swagger extensions.
   231  		Extensions map[string]interface{} `json:"-"`
   232  	}
   233  
   234  	// Scope corresponds to an available scope for an OAuth2 security scheme.
   235  	Scope struct {
   236  		// Description for scope
   237  		Description string `json:"description,omitempty"`
   238  	}
   239  
   240  	// ExternalDocs allows referencing an external resource for extended documentation.
   241  	ExternalDocs struct {
   242  		// Description is a short description of the target documentation.
   243  		// GFM syntax can be used for rich text representation.
   244  		Description string `json:"description,omitempty"`
   245  		// URL for the target documentation.
   246  		URL string `json:"url"`
   247  	}
   248  
   249  	// Items is a limited subset of JSON-Schema's items object. It is used by parameter
   250  	// definitions that are not located in "body".
   251  	Items struct {
   252  		//  Type of the items. it is limited to simple types (that is, not an object).
   253  		Type string `json:"type,omitempty"`
   254  		// Format is the extending format for the previously mentioned type.
   255  		Format string `json:"format,omitempty"`
   256  		// Items describes the type of items in the array if type is "array".
   257  		Items *Items `json:"items,omitempty"`
   258  		// CollectionFormat determines the format of the array if type array is used.
   259  		// Possible values are csv, ssv, tsv, pipes and multi.
   260  		CollectionFormat string `json:"collectionFormat,omitempty"`
   261  		// Default declares the value of the parameter that the server will use if none is
   262  		// provided, for example a "count" to control the number of results per page might
   263  		// default to 100 if not supplied by the client in the request.
   264  		Default          interface{}   `json:"default,omitempty"`
   265  		Maximum          *float64      `json:"maximum,omitempty"`
   266  		ExclusiveMaximum bool          `json:"exclusiveMaximum,omitempty"`
   267  		Minimum          *float64      `json:"minimum,omitempty"`
   268  		ExclusiveMinimum bool          `json:"exclusiveMinimum,omitempty"`
   269  		MaxLength        *int          `json:"maxLength,omitempty"`
   270  		MinLength        *int          `json:"minLength,omitempty"`
   271  		Pattern          string        `json:"pattern,omitempty"`
   272  		MaxItems         *int          `json:"maxItems,omitempty"`
   273  		MinItems         *int          `json:"minItems,omitempty"`
   274  		UniqueItems      bool          `json:"uniqueItems,omitempty"`
   275  		Enum             []interface{} `json:"enum,omitempty"`
   276  		MultipleOf       float64       `json:"multipleOf,omitempty"`
   277  	}
   278  
   279  	// Tag allows adding meta data to a single tag that is used by the Operation Object. It is
   280  	// not mandatory to have a Tag Object per tag used there.
   281  	Tag struct {
   282  		// Name of the tag.
   283  		Name string `json:"name,omitempty"`
   284  		// Description is a short description of the tag.
   285  		// GFM syntax can be used for rich text representation.
   286  		Description string `json:"description,omitempty"`
   287  		// ExternalDocs is additional external documentation for this tag.
   288  		ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
   289  		// Extensions defines the swagger extensions.
   290  		Extensions map[string]interface{} `json:"-"`
   291  	}
   292  
   293  	// These types are used in marshalJSON() to avoid recursive call of json.Marshal().
   294  	_Info               Info
   295  	_Path               Path
   296  	_Operation          Operation
   297  	_Parameter          Parameter
   298  	_Response           Response
   299  	_SecurityDefinition SecurityDefinition
   300  	_Tag                Tag
   301  )
   302  
   303  func marshalJSON(v interface{}, extensions map[string]interface{}) ([]byte, error) {
   304  	marshaled, err := json.Marshal(v)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	if len(extensions) == 0 {
   309  		return marshaled, nil
   310  	}
   311  	var unmarshaled interface{}
   312  	if err := json.Unmarshal(marshaled, &unmarshaled); err != nil {
   313  		return nil, err
   314  	}
   315  	asserted := unmarshaled.(map[string]interface{})
   316  	for k, v := range extensions {
   317  		asserted[k] = v
   318  	}
   319  	merged, err := json.Marshal(asserted)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  	return merged, nil
   324  }
   325  
   326  // MarshalJSON returns the JSON encoding of i.
   327  func (i Info) MarshalJSON() ([]byte, error) {
   328  	return marshalJSON(_Info(i), i.Extensions)
   329  }
   330  
   331  // MarshalJSON returns the JSON encoding of p.
   332  func (p Path) MarshalJSON() ([]byte, error) {
   333  	return marshalJSON(_Path(p), p.Extensions)
   334  }
   335  
   336  // MarshalJSON returns the JSON encoding of o.
   337  func (o Operation) MarshalJSON() ([]byte, error) {
   338  	return marshalJSON(_Operation(o), o.Extensions)
   339  }
   340  
   341  // MarshalJSON returns the JSON encoding of p.
   342  func (p Parameter) MarshalJSON() ([]byte, error) {
   343  	return marshalJSON(_Parameter(p), p.Extensions)
   344  }
   345  
   346  // MarshalJSON returns the JSON encoding of r.
   347  func (r Response) MarshalJSON() ([]byte, error) {
   348  	return marshalJSON(_Response(r), r.Extensions)
   349  }
   350  
   351  // MarshalJSON returns the JSON encoding of s.
   352  func (s SecurityDefinition) MarshalJSON() ([]byte, error) {
   353  	return marshalJSON(_SecurityDefinition(s), s.Extensions)
   354  }
   355  
   356  // MarshalJSON returns the JSON encoding of t.
   357  func (t Tag) MarshalJSON() ([]byte, error) {
   358  	return marshalJSON(_Tag(t), t.Extensions)
   359  }
   360  
   361  // New creates a Swagger spec from an API definition.
   362  func New(api *design.APIDefinition) (*Swagger, error) {
   363  	if api == nil {
   364  		return nil, nil
   365  	}
   366  	tags := tagsFromDefinition(api.Metadata)
   367  	basePath := api.BasePath
   368  	if hasAbsoluteRoutes(api) {
   369  		basePath = ""
   370  	}
   371  	params, err := paramsFromDefinition(api.Params, basePath)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	var paramMap map[string]*Parameter
   376  	if len(params) > 0 {
   377  		paramMap = make(map[string]*Parameter, len(params))
   378  		for _, p := range params {
   379  			paramMap[p.Name] = p
   380  		}
   381  	}
   382  	var consumes []string
   383  	for _, c := range api.Consumes {
   384  		consumes = append(consumes, c.MIMETypes...)
   385  	}
   386  	var produces []string
   387  	for _, p := range api.Produces {
   388  		produces = append(produces, p.MIMETypes...)
   389  	}
   390  	s := &Swagger{
   391  		Swagger: "2.0",
   392  		Info: &Info{
   393  			Title:          api.Title,
   394  			Description:    api.Description,
   395  			TermsOfService: api.TermsOfService,
   396  			Contact:        api.Contact,
   397  			License:        api.License,
   398  			Version:        api.Version,
   399  			Extensions:     extensionsFromDefinition(api.Metadata),
   400  		},
   401  		Host:                api.Host,
   402  		BasePath:            basePath,
   403  		Paths:               make(map[string]interface{}),
   404  		Schemes:             api.Schemes,
   405  		Consumes:            consumes,
   406  		Produces:            produces,
   407  		Parameters:          paramMap,
   408  		Tags:                tags,
   409  		ExternalDocs:        docsFromDefinition(api.Docs),
   410  		SecurityDefinitions: securityDefsFromDefinition(api.SecuritySchemes),
   411  	}
   412  
   413  	err = api.IterateResponses(func(r *design.ResponseDefinition) error {
   414  		res, err := responseSpecFromDefinition(s, api, r)
   415  		if err != nil {
   416  			return err
   417  		}
   418  		if s.Responses == nil {
   419  			s.Responses = make(map[string]*Response)
   420  		}
   421  		s.Responses[r.Name] = res
   422  		return nil
   423  	})
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  	err = api.IterateResources(func(res *design.ResourceDefinition) error {
   428  		for k, v := range extensionsFromDefinition(res.Metadata) {
   429  			s.Paths[k] = v
   430  		}
   431  		err := res.IterateFileServers(func(fs *design.FileServerDefinition) error {
   432  			if !mustGenerate(fs.Metadata) {
   433  				return nil
   434  			}
   435  			return buildPathFromFileServer(s, api, fs)
   436  		})
   437  		if err != nil {
   438  			return err
   439  		}
   440  		return res.IterateActions(func(a *design.ActionDefinition) error {
   441  			if !mustGenerate(a.Metadata) {
   442  				return nil
   443  			}
   444  			for _, route := range a.Routes {
   445  				if err := buildPathFromDefinition(s, api, route, basePath); err != nil {
   446  					return err
   447  				}
   448  			}
   449  			return nil
   450  		})
   451  	})
   452  	if err != nil {
   453  		return nil, err
   454  	}
   455  	if len(genschema.Definitions) > 0 {
   456  		s.Definitions = make(map[string]*genschema.JSONSchema)
   457  		for n, d := range genschema.Definitions {
   458  			// sad but swagger doesn't support these
   459  			d.Media = nil
   460  			d.Links = nil
   461  			s.Definitions[n] = d
   462  		}
   463  	}
   464  	return s, nil
   465  }
   466  
   467  // mustGenerate returns true if the metadata indicates that a Swagger specification should be
   468  // generated, false otherwise.
   469  func mustGenerate(meta dslengine.MetadataDefinition) bool {
   470  	if m, ok := meta["swagger:generate"]; ok {
   471  		if len(m) > 0 && m[0] == "false" {
   472  			return false
   473  		}
   474  	}
   475  	return true
   476  }
   477  
   478  // hasAbsoluteRoutes returns true if any action exposed by the API uses an absolute route of if the
   479  // API has file servers. This is needed as Swagger does not support exceptions to the base path so
   480  // if the API has any absolute route the base path must be "/" and all routes must be absolutes.
   481  func hasAbsoluteRoutes(api *design.APIDefinition) bool {
   482  	hasAbsoluteRoutes := false
   483  	for _, res := range api.Resources {
   484  		for _, fs := range res.FileServers {
   485  			if !mustGenerate(fs.Metadata) {
   486  				continue
   487  			}
   488  			hasAbsoluteRoutes = true
   489  			break
   490  		}
   491  		for _, a := range res.Actions {
   492  			if !mustGenerate(a.Metadata) {
   493  				continue
   494  			}
   495  			for _, ro := range a.Routes {
   496  				if ro.IsAbsolute() {
   497  					hasAbsoluteRoutes = true
   498  					break
   499  				}
   500  			}
   501  			if hasAbsoluteRoutes {
   502  				break
   503  			}
   504  		}
   505  		if hasAbsoluteRoutes {
   506  			break
   507  		}
   508  	}
   509  	return hasAbsoluteRoutes
   510  }
   511  
   512  func securityDefsFromDefinition(schemes []*design.SecuritySchemeDefinition) map[string]*SecurityDefinition {
   513  	if len(schemes) == 0 {
   514  		return nil
   515  	}
   516  
   517  	defs := make(map[string]*SecurityDefinition)
   518  	for _, scheme := range schemes {
   519  		def := &SecurityDefinition{
   520  			Type:             scheme.Type,
   521  			Description:      scheme.Description,
   522  			Name:             scheme.Name,
   523  			In:               scheme.In,
   524  			Flow:             scheme.Flow,
   525  			AuthorizationURL: scheme.AuthorizationURL,
   526  			TokenURL:         scheme.TokenURL,
   527  			Scopes:           scheme.Scopes,
   528  			Extensions:       extensionsFromDefinition(scheme.Metadata),
   529  		}
   530  		if scheme.Kind == design.JWTSecurityKind {
   531  			if def.TokenURL != "" {
   532  				def.Description += fmt.Sprintf("\n\n**Token URL**: %s", def.TokenURL)
   533  				def.TokenURL = ""
   534  			}
   535  			if len(def.Scopes) != 0 {
   536  				def.Description += fmt.Sprintf("\n\n**Security Scopes**:\n%s", scopesMapList(def.Scopes))
   537  				def.Scopes = nil
   538  			}
   539  		}
   540  		defs[scheme.SchemeName] = def
   541  	}
   542  	return defs
   543  }
   544  
   545  func scopesMapList(scopes map[string]string) string {
   546  	names := []string{}
   547  	for name := range scopes {
   548  		names = append(names, name)
   549  	}
   550  	sort.Strings(names)
   551  
   552  	lines := []string{}
   553  	for _, name := range names {
   554  		lines = append(lines, fmt.Sprintf("  * `%s`: %s", name, scopes[name]))
   555  	}
   556  	return strings.Join(lines, "\n")
   557  }
   558  
   559  func tagsFromDefinition(mdata dslengine.MetadataDefinition) (tags []*Tag) {
   560  	var keys []string
   561  	for k := range mdata {
   562  		keys = append(keys, k)
   563  	}
   564  	sort.Strings(keys)
   565  	for _, key := range keys {
   566  		chunks := strings.Split(key, ":")
   567  		if len(chunks) != 3 {
   568  			continue
   569  		}
   570  		if chunks[0] != "swagger" || chunks[1] != "tag" {
   571  			continue
   572  		}
   573  
   574  		tag := &Tag{Name: chunks[2]}
   575  
   576  		mdata[key] = mdata[fmt.Sprintf("%s:desc", key)]
   577  		if len(mdata[key]) != 0 {
   578  			tag.Description = mdata[key][0]
   579  		}
   580  
   581  		hasDocs := false
   582  		docs := &ExternalDocs{}
   583  
   584  		mdata[key] = mdata[fmt.Sprintf("%s:url", key)]
   585  		if len(mdata[key]) != 0 {
   586  			docs.URL = mdata[key][0]
   587  			hasDocs = true
   588  		}
   589  
   590  		mdata[key] = mdata[fmt.Sprintf("%s:url:desc", key)]
   591  		if len(mdata[key]) != 0 {
   592  			docs.Description = mdata[key][0]
   593  			hasDocs = true
   594  		}
   595  
   596  		if hasDocs {
   597  			tag.ExternalDocs = docs
   598  		}
   599  
   600  		tag.Extensions = extensionsFromDefinition(mdata)
   601  
   602  		tags = append(tags, tag)
   603  	}
   604  
   605  	return
   606  }
   607  
   608  func tagNamesFromDefinitions(mdatas ...dslengine.MetadataDefinition) (tagNames []string) {
   609  	for _, mdata := range mdatas {
   610  		tags := tagsFromDefinition(mdata)
   611  		for _, tag := range tags {
   612  			tagNames = append(tagNames, tag.Name)
   613  		}
   614  	}
   615  	return
   616  }
   617  
   618  func summaryFromDefinition(name string, metadata dslengine.MetadataDefinition) string {
   619  	for n, mdata := range metadata {
   620  		if n == "swagger:summary" && len(mdata) > 0 {
   621  			return mdata[0]
   622  		}
   623  	}
   624  	return name
   625  }
   626  
   627  func extensionsFromDefinition(mdata dslengine.MetadataDefinition) map[string]interface{} {
   628  	extensions := make(map[string]interface{})
   629  	for key, value := range mdata {
   630  		chunks := strings.Split(key, ":")
   631  		if len(chunks) != 3 {
   632  			continue
   633  		}
   634  		if chunks[0] != "swagger" || chunks[1] != "extension" {
   635  			continue
   636  		}
   637  		if strings.HasPrefix(chunks[2], "x-") != true {
   638  			continue
   639  		}
   640  		val := value[0]
   641  		ival := interface{}(val)
   642  		if err := json.Unmarshal([]byte(val), &ival); err != nil {
   643  			extensions[chunks[2]] = val
   644  			continue
   645  		}
   646  		extensions[chunks[2]] = ival
   647  	}
   648  	if len(extensions) == 0 {
   649  		return nil
   650  	}
   651  	return extensions
   652  }
   653  
   654  func paramsFromDefinition(params *design.AttributeDefinition, path string) ([]*Parameter, error) {
   655  	if params == nil {
   656  		return nil, nil
   657  	}
   658  	obj := params.Type.ToObject()
   659  	if obj == nil {
   660  		return nil, fmt.Errorf("invalid parameters definition, not an object")
   661  	}
   662  	res := make([]*Parameter, len(obj))
   663  	i := 0
   664  	wildcards := design.ExtractWildcards(path)
   665  	obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error {
   666  		in := "query"
   667  		required := params.IsRequired(n)
   668  		for _, w := range wildcards {
   669  			if n == w {
   670  				in = "path"
   671  				required = true
   672  				break
   673  			}
   674  		}
   675  		param := paramFor(at, n, in, required)
   676  		res[i] = param
   677  		i++
   678  		return nil
   679  	})
   680  	return res, nil
   681  }
   682  
   683  func paramsFromHeaders(action *design.ActionDefinition) []*Parameter {
   684  	params := []*Parameter{}
   685  	action.IterateHeaders(func(name string, required bool, header *design.AttributeDefinition) error {
   686  		p := paramFor(header, name, "header", required)
   687  		params = append(params, p)
   688  		return nil
   689  	})
   690  	return params
   691  }
   692  
   693  func paramsFromPayload(payload *design.UserTypeDefinition) ([]*Parameter, error) {
   694  	if payload == nil {
   695  		return nil, nil
   696  	}
   697  	obj := payload.Type.ToObject()
   698  	if obj == nil {
   699  		return nil, fmt.Errorf("invalid parameters definition, not an object")
   700  	}
   701  	res := make([]*Parameter, len(obj))
   702  	i := 0
   703  	obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error {
   704  		in := "formData"
   705  		required := payload.IsRequired(n)
   706  		param := paramFor(at, n, in, required)
   707  		res[i] = param
   708  		i++
   709  		return nil
   710  	})
   711  	return res, nil
   712  }
   713  
   714  func paramFor(at *design.AttributeDefinition, name, in string, required bool) *Parameter {
   715  	p := &Parameter{
   716  		In:          in,
   717  		Name:        name,
   718  		Default:     toStringMap(at.DefaultValue),
   719  		Description: at.Description,
   720  		Required:    required,
   721  		Type:        at.Type.Name(),
   722  	}
   723  	if at.Type.IsArray() {
   724  		p.Items = itemsFromDefinition(at.Type.ToArray().ElemType)
   725  	}
   726  	p.Extensions = extensionsFromDefinition(at.Metadata)
   727  	initValidations(at, p)
   728  	return p
   729  }
   730  
   731  // toStringMap converts map[interface{}]interface{} to a map[string]interface{} when possible.
   732  func toStringMap(val interface{}) interface{} {
   733  	switch actual := val.(type) {
   734  	case map[interface{}]interface{}:
   735  		m := make(map[string]interface{})
   736  		for k, v := range actual {
   737  			m[toString(k)] = toStringMap(v)
   738  		}
   739  		return m
   740  	case []interface{}:
   741  		mapSlice := make([]interface{}, len(actual))
   742  		for i, e := range actual {
   743  			mapSlice[i] = toStringMap(e)
   744  		}
   745  		return mapSlice
   746  	default:
   747  		return actual
   748  	}
   749  }
   750  
   751  // toString returns the string representation of the given type.
   752  func toString(val interface{}) string {
   753  	switch actual := val.(type) {
   754  	case string:
   755  		return actual
   756  	case int:
   757  		return strconv.Itoa(actual)
   758  	case float64:
   759  		return strconv.FormatFloat(actual, 'f', -1, 64)
   760  	case bool:
   761  		return strconv.FormatBool(actual)
   762  	default:
   763  		panic("unexpected key type")
   764  	}
   765  }
   766  
   767  func itemsFromDefinition(at *design.AttributeDefinition) *Items {
   768  	items := &Items{Type: at.Type.Name()}
   769  	initValidations(at, items)
   770  	if at.Type.IsArray() {
   771  		items.Items = itemsFromDefinition(at.Type.ToArray().ElemType)
   772  	}
   773  	return items
   774  }
   775  
   776  func responseSpecFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) {
   777  	var schema *genschema.JSONSchema
   778  	if r.MediaType != "" {
   779  		if mt, ok := api.MediaTypes[design.CanonicalIdentifier(r.MediaType)]; ok {
   780  			view := r.ViewName
   781  			if view == "" {
   782  				view = design.DefaultView
   783  			}
   784  			schema = genschema.NewJSONSchema()
   785  			schema.Ref = genschema.MediaTypeRef(api, mt, view)
   786  		}
   787  	}
   788  	headers, err := headersFromDefinition(r.Headers)
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  	return &Response{
   793  		Description: r.Description,
   794  		Schema:      schema,
   795  		Headers:     headers,
   796  		Extensions:  extensionsFromDefinition(r.Metadata),
   797  	}, nil
   798  }
   799  
   800  func responseFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) {
   801  	var (
   802  		response *Response
   803  		err      error
   804  	)
   805  	response, err = responseSpecFromDefinition(s, api, r)
   806  	if err != nil {
   807  		return nil, err
   808  	}
   809  	if r.Standard {
   810  		if s.Responses == nil {
   811  			s.Responses = make(map[string]*Response)
   812  		}
   813  		if _, ok := s.Responses[r.Name]; !ok {
   814  			sp, err := responseSpecFromDefinition(s, api, r)
   815  			if err != nil {
   816  				return nil, err
   817  			}
   818  			s.Responses[r.Name] = sp
   819  		}
   820  	}
   821  	return response, nil
   822  }
   823  
   824  func headersFromDefinition(headers *design.AttributeDefinition) (map[string]*Header, error) {
   825  	if headers == nil {
   826  		return nil, nil
   827  	}
   828  	obj := headers.Type.ToObject()
   829  	if obj == nil {
   830  		return nil, fmt.Errorf("invalid headers definition, not an object")
   831  	}
   832  	res := make(map[string]*Header)
   833  	obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error {
   834  		header := &Header{
   835  			Default:     at.DefaultValue,
   836  			Description: at.Description,
   837  			Type:        at.Type.Name(),
   838  		}
   839  		initValidations(at, header)
   840  		res[n] = header
   841  		return nil
   842  	})
   843  	return res, nil
   844  }
   845  
   846  func buildPathFromFileServer(s *Swagger, api *design.APIDefinition, fs *design.FileServerDefinition) error {
   847  	wcs := design.ExtractWildcards(fs.RequestPath)
   848  	var param []*Parameter
   849  	if len(wcs) > 0 {
   850  		param = []*Parameter{{
   851  			In:          "path",
   852  			Name:        wcs[0],
   853  			Description: "Relative file path",
   854  			Required:    true,
   855  			Type:        "string",
   856  		}}
   857  	}
   858  
   859  	responses := map[string]*Response{
   860  		"200": {
   861  			Description: "File downloaded",
   862  			Schema:      &genschema.JSONSchema{Type: genschema.JSONFile},
   863  		},
   864  	}
   865  	if len(wcs) > 0 {
   866  		schema := genschema.TypeSchema(api, design.ErrorMedia)
   867  		responses["404"] = &Response{Description: "File not found", Schema: schema}
   868  	}
   869  
   870  	operationID := fmt.Sprintf("%s#%s", fs.Parent.Name, fs.RequestPath)
   871  	schemes := api.Schemes
   872  
   873  	operation := &Operation{
   874  		Description:  fs.Description,
   875  		Summary:      summaryFromDefinition(fmt.Sprintf("Download %s", fs.FilePath), fs.Metadata),
   876  		ExternalDocs: docsFromDefinition(fs.Docs),
   877  		OperationID:  operationID,
   878  		Parameters:   param,
   879  		Responses:    responses,
   880  		Schemes:      schemes,
   881  	}
   882  
   883  	applySecurity(operation, fs.Security)
   884  
   885  	key := design.WildcardRegex.ReplaceAllStringFunc(
   886  		fs.RequestPath,
   887  		func(w string) string {
   888  			return fmt.Sprintf("/{%s}", w[2:])
   889  		},
   890  	)
   891  	if key == "" {
   892  		key = "/"
   893  	}
   894  	var path interface{}
   895  	var ok bool
   896  	if path, ok = s.Paths[key]; !ok {
   897  		path = new(Path)
   898  		s.Paths[key] = path
   899  	}
   900  	p := path.(*Path)
   901  	p.Get = operation
   902  	p.Extensions = extensionsFromDefinition(fs.Metadata)
   903  
   904  	return nil
   905  }
   906  
   907  func buildPathFromDefinition(s *Swagger, api *design.APIDefinition, route *design.RouteDefinition, basePath string) error {
   908  	action := route.Parent
   909  
   910  	tagNames := tagNamesFromDefinitions(action.Parent.Metadata, action.Metadata)
   911  	if len(tagNames) == 0 {
   912  		// By default tag with resource name
   913  		tagNames = []string{route.Parent.Parent.Name}
   914  	}
   915  	params, err := paramsFromDefinition(action.AllParams(), route.FullPath())
   916  	if err != nil {
   917  		return err
   918  	}
   919  
   920  	params = append(params, paramsFromHeaders(action)...)
   921  
   922  	responses := make(map[string]*Response, len(action.Responses))
   923  	for _, r := range action.Responses {
   924  		resp, err := responseFromDefinition(s, api, r)
   925  		if err != nil {
   926  			return err
   927  		}
   928  		responses[strconv.Itoa(r.Status)] = resp
   929  	}
   930  
   931  	consumesMultipart := false
   932  	if action.Payload != nil {
   933  		if action.PayloadMultipart {
   934  			p, err := paramsFromPayload(action.Payload)
   935  			if err != nil {
   936  				return err
   937  			}
   938  			params = append(params, p...)
   939  			consumesMultipart = true
   940  		} else {
   941  			payloadSchema := genschema.TypeSchema(api, action.Payload)
   942  			pp := &Parameter{
   943  				Name:        "payload",
   944  				In:          "body",
   945  				Description: action.Payload.Description,
   946  				Required:    !action.PayloadOptional,
   947  				Schema:      payloadSchema,
   948  			}
   949  			params = append(params, pp)
   950  		}
   951  	}
   952  
   953  	operationID := fmt.Sprintf("%s#%s", action.Parent.Name, action.Name)
   954  	index := 0
   955  	for i, rt := range action.Routes {
   956  		if rt == route {
   957  			index = i
   958  			break
   959  		}
   960  	}
   961  	if index > 0 {
   962  		operationID = fmt.Sprintf("%s#%d", operationID, index)
   963  	}
   964  
   965  	schemes := action.Schemes
   966  	if len(schemes) == 0 {
   967  		schemes = api.Schemes
   968  	}
   969  
   970  	operation := &Operation{
   971  		Tags:         tagNames,
   972  		Description:  action.Description,
   973  		Summary:      summaryFromDefinition(action.Name+" "+action.Parent.Name, action.Metadata),
   974  		ExternalDocs: docsFromDefinition(action.Docs),
   975  		OperationID:  operationID,
   976  		Parameters:   params,
   977  		Responses:    responses,
   978  		Schemes:      schemes,
   979  		Deprecated:   false,
   980  		Extensions:   extensionsFromDefinition(route.Metadata),
   981  	}
   982  
   983  	if consumesMultipart {
   984  		operation.Consumes = append(operation.Consumes, "multipart/form-data")
   985  	}
   986  
   987  	computeProduces(operation, s, action)
   988  	applySecurity(operation, action.Security)
   989  
   990  	computePaths(operation, s, route, basePath)
   991  	return nil
   992  }
   993  
   994  func computeProduces(operation *Operation, s *Swagger, action *design.ActionDefinition) {
   995  	produces := make(map[string]struct{})
   996  	action.IterateResponses(func(resp *design.ResponseDefinition) error {
   997  		if resp.MediaType != "" {
   998  			produces[resp.MediaType] = struct{}{}
   999  		}
  1000  		return nil
  1001  	})
  1002  	subset := true
  1003  	for p := range produces {
  1004  		found := false
  1005  		for _, p2 := range s.Produces {
  1006  			if p == p2 {
  1007  				found = true
  1008  				break
  1009  			}
  1010  		}
  1011  		if !found {
  1012  			subset = false
  1013  			break
  1014  		}
  1015  	}
  1016  	if !subset {
  1017  		operation.Produces = make([]string, len(produces))
  1018  		i := 0
  1019  		for p := range produces {
  1020  			operation.Produces[i] = p
  1021  			i++
  1022  		}
  1023  		sort.Strings(operation.Produces)
  1024  	}
  1025  }
  1026  
  1027  func computePaths(operation *Operation, s *Swagger, route *design.RouteDefinition, basePath string) {
  1028  	key := design.WildcardRegex.ReplaceAllStringFunc(
  1029  		route.FullPath(),
  1030  		func(w string) string {
  1031  			return fmt.Sprintf("/{%s}", w[2:])
  1032  		},
  1033  	)
  1034  	bp := design.WildcardRegex.ReplaceAllStringFunc(
  1035  		basePath,
  1036  		func(w string) string {
  1037  			return fmt.Sprintf("/{%s}", w[2:])
  1038  		},
  1039  	)
  1040  	if bp != "/" {
  1041  		key = strings.TrimPrefix(key, bp)
  1042  	}
  1043  	if key == "" {
  1044  		key = "/"
  1045  	}
  1046  	var path interface{}
  1047  	var ok bool
  1048  	if path, ok = s.Paths[key]; !ok {
  1049  		path = new(Path)
  1050  		s.Paths[key] = path
  1051  	}
  1052  	p := path.(*Path)
  1053  	switch route.Verb {
  1054  	case "GET":
  1055  		p.Get = operation
  1056  	case "PUT":
  1057  		p.Put = operation
  1058  	case "POST":
  1059  		p.Post = operation
  1060  	case "DELETE":
  1061  		p.Delete = operation
  1062  	case "OPTIONS":
  1063  		p.Options = operation
  1064  	case "HEAD":
  1065  		p.Head = operation
  1066  	case "PATCH":
  1067  		p.Patch = operation
  1068  	}
  1069  	p.Extensions = extensionsFromDefinition(route.Parent.Metadata)
  1070  }
  1071  
  1072  func applySecurity(operation *Operation, security *design.SecurityDefinition) {
  1073  	if security != nil && security.Scheme.Kind != design.NoSecurityKind {
  1074  		if security.Scheme.Kind == design.JWTSecurityKind && len(security.Scopes) > 0 {
  1075  			if operation.Description != "" {
  1076  				operation.Description += "\n\n"
  1077  			}
  1078  			operation.Description += fmt.Sprintf("Required security scopes:\n%s", scopesList(security.Scopes))
  1079  		}
  1080  		scopes := security.Scopes
  1081  		if scopes == nil {
  1082  			scopes = make([]string, 0)
  1083  		}
  1084  		sec := []map[string][]string{{security.Scheme.SchemeName: scopes}}
  1085  		operation.Security = sec
  1086  	}
  1087  }
  1088  
  1089  func scopesList(scopes []string) string {
  1090  	sort.Strings(scopes)
  1091  
  1092  	var lines []string
  1093  	for _, scope := range scopes {
  1094  		lines = append(lines, fmt.Sprintf("  * `%s`", scope))
  1095  	}
  1096  	return strings.Join(lines, "\n")
  1097  }
  1098  
  1099  func docsFromDefinition(docs *design.DocsDefinition) *ExternalDocs {
  1100  	if docs == nil {
  1101  		return nil
  1102  	}
  1103  	return &ExternalDocs{
  1104  		Description: docs.Description,
  1105  		URL:         docs.URL,
  1106  	}
  1107  }
  1108  
  1109  func initEnumValidation(def interface{}, values []interface{}) {
  1110  	switch actual := def.(type) {
  1111  	case *Parameter:
  1112  		actual.Enum = values
  1113  	case *Header:
  1114  		actual.Enum = values
  1115  	case *Items:
  1116  		actual.Enum = values
  1117  	}
  1118  }
  1119  
  1120  func initFormatValidation(def interface{}, format string) {
  1121  	switch actual := def.(type) {
  1122  	case *Parameter:
  1123  		actual.Format = format
  1124  	case *Header:
  1125  		actual.Format = format
  1126  	case *Items:
  1127  		actual.Format = format
  1128  	}
  1129  }
  1130  
  1131  func initPatternValidation(def interface{}, pattern string) {
  1132  	switch actual := def.(type) {
  1133  	case *Parameter:
  1134  		actual.Pattern = pattern
  1135  	case *Header:
  1136  		actual.Pattern = pattern
  1137  	case *Items:
  1138  		actual.Pattern = pattern
  1139  	}
  1140  }
  1141  
  1142  func initMinimumValidation(def interface{}, min *float64) {
  1143  	switch actual := def.(type) {
  1144  	case *Parameter:
  1145  		actual.Minimum = min
  1146  		actual.ExclusiveMinimum = false
  1147  	case *Header:
  1148  		actual.Minimum = min
  1149  		actual.ExclusiveMinimum = false
  1150  	case *Items:
  1151  		actual.Minimum = min
  1152  		actual.ExclusiveMinimum = false
  1153  	}
  1154  }
  1155  
  1156  func initMaximumValidation(def interface{}, max *float64) {
  1157  	switch actual := def.(type) {
  1158  	case *Parameter:
  1159  		actual.Maximum = max
  1160  		actual.ExclusiveMaximum = false
  1161  	case *Header:
  1162  		actual.Maximum = max
  1163  		actual.ExclusiveMaximum = false
  1164  	case *Items:
  1165  		actual.Maximum = max
  1166  		actual.ExclusiveMaximum = false
  1167  	}
  1168  }
  1169  
  1170  func initMinLengthValidation(def interface{}, isArray bool, min *int) {
  1171  	switch actual := def.(type) {
  1172  	case *Parameter:
  1173  		if isArray {
  1174  			actual.MinItems = min
  1175  		} else {
  1176  			actual.MinLength = min
  1177  		}
  1178  	case *Header:
  1179  		actual.MinLength = min
  1180  	case *Items:
  1181  		actual.MinLength = min
  1182  	}
  1183  }
  1184  
  1185  func initMaxLengthValidation(def interface{}, isArray bool, max *int) {
  1186  	switch actual := def.(type) {
  1187  	case *Parameter:
  1188  		if isArray {
  1189  			actual.MaxItems = max
  1190  		} else {
  1191  			actual.MaxLength = max
  1192  		}
  1193  	case *Header:
  1194  		actual.MaxLength = max
  1195  	case *Items:
  1196  		actual.MaxLength = max
  1197  	}
  1198  }
  1199  
  1200  func initValidations(attr *design.AttributeDefinition, def interface{}) {
  1201  	val := attr.Validation
  1202  	if val == nil {
  1203  		return
  1204  	}
  1205  	initEnumValidation(def, val.Values)
  1206  	initFormatValidation(def, val.Format)
  1207  	initPatternValidation(def, val.Pattern)
  1208  	if val.Minimum != nil {
  1209  		initMinimumValidation(def, val.Minimum)
  1210  	}
  1211  	if val.Maximum != nil {
  1212  		initMaximumValidation(def, val.Maximum)
  1213  	}
  1214  	if val.MinLength != nil {
  1215  		initMinLengthValidation(def, attr.Type.IsArray(), val.MinLength)
  1216  	}
  1217  	if val.MaxLength != nil {
  1218  		initMaxLengthValidation(def, attr.Type.IsArray(), val.MaxLength)
  1219  	}
  1220  }