github.com/ManabuSeki/goa-v1@v1.4.3/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  	genschema "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  		p.CollectionFormat = "multi"
   726  	}
   727  	p.Extensions = extensionsFromDefinition(at.Metadata)
   728  	initValidations(at, p)
   729  	return p
   730  }
   731  
   732  // toStringMap converts map[interface{}]interface{} to a map[string]interface{} when possible.
   733  func toStringMap(val interface{}) interface{} {
   734  	switch actual := val.(type) {
   735  	case map[interface{}]interface{}:
   736  		m := make(map[string]interface{})
   737  		for k, v := range actual {
   738  			m[toString(k)] = toStringMap(v)
   739  		}
   740  		return m
   741  	case []interface{}:
   742  		mapSlice := make([]interface{}, len(actual))
   743  		for i, e := range actual {
   744  			mapSlice[i] = toStringMap(e)
   745  		}
   746  		return mapSlice
   747  	default:
   748  		return actual
   749  	}
   750  }
   751  
   752  // toString returns the string representation of the given type.
   753  func toString(val interface{}) string {
   754  	switch actual := val.(type) {
   755  	case string:
   756  		return actual
   757  	case int:
   758  		return strconv.Itoa(actual)
   759  	case float64:
   760  		return strconv.FormatFloat(actual, 'f', -1, 64)
   761  	case bool:
   762  		return strconv.FormatBool(actual)
   763  	default:
   764  		panic("unexpected key type")
   765  	}
   766  }
   767  
   768  func itemsFromDefinition(at *design.AttributeDefinition) *Items {
   769  	items := &Items{Type: at.Type.Name()}
   770  	initValidations(at, items)
   771  	if at.Type.IsArray() {
   772  		items.Items = itemsFromDefinition(at.Type.ToArray().ElemType)
   773  	}
   774  	return items
   775  }
   776  
   777  func responseSpecFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) {
   778  	var schema *genschema.JSONSchema
   779  	if r.MediaType != "" {
   780  		if mt, ok := api.MediaTypes[design.CanonicalIdentifier(r.MediaType)]; ok {
   781  			view := r.ViewName
   782  			if view == "" {
   783  				view = design.DefaultView
   784  			}
   785  			schema = genschema.NewJSONSchema()
   786  			schema.Ref = genschema.MediaTypeRef(api, mt, view)
   787  		}
   788  	}
   789  	headers, err := headersFromDefinition(r.Headers)
   790  	if err != nil {
   791  		return nil, err
   792  	}
   793  	return &Response{
   794  		Description: r.Description,
   795  		Schema:      schema,
   796  		Headers:     headers,
   797  		Extensions:  extensionsFromDefinition(r.Metadata),
   798  	}, nil
   799  }
   800  
   801  func responseFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) {
   802  	var (
   803  		response *Response
   804  		err      error
   805  	)
   806  	response, err = responseSpecFromDefinition(s, api, r)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	if r.Standard {
   811  		if s.Responses == nil {
   812  			s.Responses = make(map[string]*Response)
   813  		}
   814  		if _, ok := s.Responses[r.Name]; !ok {
   815  			sp, err := responseSpecFromDefinition(s, api, r)
   816  			if err != nil {
   817  				return nil, err
   818  			}
   819  			s.Responses[r.Name] = sp
   820  		}
   821  	}
   822  	return response, nil
   823  }
   824  
   825  func headersFromDefinition(headers *design.AttributeDefinition) (map[string]*Header, error) {
   826  	if headers == nil {
   827  		return nil, nil
   828  	}
   829  	obj := headers.Type.ToObject()
   830  	if obj == nil {
   831  		return nil, fmt.Errorf("invalid headers definition, not an object")
   832  	}
   833  	res := make(map[string]*Header)
   834  	obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error {
   835  		header := &Header{
   836  			Default:     at.DefaultValue,
   837  			Description: at.Description,
   838  			Type:        at.Type.Name(),
   839  		}
   840  		initValidations(at, header)
   841  		res[n] = header
   842  		return nil
   843  	})
   844  	return res, nil
   845  }
   846  
   847  func buildPathFromFileServer(s *Swagger, api *design.APIDefinition, fs *design.FileServerDefinition) error {
   848  	wcs := design.ExtractWildcards(fs.RequestPath)
   849  	var param []*Parameter
   850  	if len(wcs) > 0 {
   851  		param = []*Parameter{{
   852  			In:          "path",
   853  			Name:        wcs[0],
   854  			Description: "Relative file path",
   855  			Required:    true,
   856  			Type:        "string",
   857  		}}
   858  	}
   859  
   860  	responses := map[string]*Response{
   861  		"200": {
   862  			Description: "File downloaded",
   863  			Schema:      &genschema.JSONSchema{Type: genschema.JSONFile},
   864  		},
   865  	}
   866  	if len(wcs) > 0 {
   867  		schema := genschema.TypeSchema(api, design.ErrorMedia)
   868  		responses["404"] = &Response{Description: "File not found", Schema: schema}
   869  	}
   870  
   871  	operationID := fmt.Sprintf("%s#%s", fs.Parent.Name, fs.RequestPath)
   872  	schemes := api.Schemes
   873  
   874  	operation := &Operation{
   875  		Description:  fs.Description,
   876  		Summary:      summaryFromDefinition(fmt.Sprintf("Download %s", fs.FilePath), fs.Metadata),
   877  		ExternalDocs: docsFromDefinition(fs.Docs),
   878  		OperationID:  operationID,
   879  		Parameters:   param,
   880  		Responses:    responses,
   881  		Schemes:      schemes,
   882  	}
   883  
   884  	applySecurity(operation, fs.Security)
   885  
   886  	key := design.WildcardRegex.ReplaceAllStringFunc(
   887  		fs.RequestPath,
   888  		func(w string) string {
   889  			return fmt.Sprintf("/{%s}", w[2:])
   890  		},
   891  	)
   892  	if key == "" {
   893  		key = "/"
   894  	}
   895  	var path interface{}
   896  	var ok bool
   897  	if path, ok = s.Paths[key]; !ok {
   898  		path = new(Path)
   899  		s.Paths[key] = path
   900  	}
   901  	p := path.(*Path)
   902  	p.Get = operation
   903  	p.Extensions = extensionsFromDefinition(fs.Metadata)
   904  
   905  	return nil
   906  }
   907  
   908  func buildPathFromDefinition(s *Swagger, api *design.APIDefinition, route *design.RouteDefinition, basePath string) error {
   909  	action := route.Parent
   910  
   911  	tagNames := tagNamesFromDefinitions(action.Parent.Metadata, action.Metadata)
   912  	if len(tagNames) == 0 {
   913  		// By default tag with resource name
   914  		tagNames = []string{route.Parent.Parent.Name}
   915  	}
   916  	params, err := paramsFromDefinition(action.AllParams(), route.FullPath())
   917  	if err != nil {
   918  		return err
   919  	}
   920  
   921  	params = append(params, paramsFromHeaders(action)...)
   922  
   923  	responses := make(map[string]*Response, len(action.Responses))
   924  	for _, r := range action.Responses {
   925  		resp, err := responseFromDefinition(s, api, r)
   926  		if err != nil {
   927  			return err
   928  		}
   929  		responses[strconv.Itoa(r.Status)] = resp
   930  	}
   931  
   932  	consumesMultipart := false
   933  	if action.Payload != nil {
   934  		if action.PayloadMultipart {
   935  			p, err := paramsFromPayload(action.Payload)
   936  			if err != nil {
   937  				return err
   938  			}
   939  			params = append(params, p...)
   940  			consumesMultipart = true
   941  		} else {
   942  			payloadSchema := genschema.TypeSchema(api, action.Payload)
   943  			pp := &Parameter{
   944  				Name:        "payload",
   945  				In:          "body",
   946  				Description: action.Payload.Description,
   947  				Required:    !action.PayloadOptional,
   948  				Schema:      payloadSchema,
   949  			}
   950  			params = append(params, pp)
   951  		}
   952  	}
   953  
   954  	operationID := fmt.Sprintf("%s#%s", action.Parent.Name, action.Name)
   955  	index := 0
   956  	for i, rt := range action.Routes {
   957  		if rt == route {
   958  			index = i
   959  			break
   960  		}
   961  	}
   962  	if index > 0 {
   963  		operationID = fmt.Sprintf("%s#%d", operationID, index)
   964  	}
   965  
   966  	schemes := action.Schemes
   967  	if len(schemes) == 0 {
   968  		schemes = api.Schemes
   969  	}
   970  
   971  	operation := &Operation{
   972  		Tags:         tagNames,
   973  		Description:  action.Description,
   974  		Summary:      summaryFromDefinition(action.Name+" "+action.Parent.Name, action.Metadata),
   975  		ExternalDocs: docsFromDefinition(action.Docs),
   976  		OperationID:  operationID,
   977  		Parameters:   params,
   978  		Responses:    responses,
   979  		Schemes:      schemes,
   980  		Deprecated:   false,
   981  		Extensions:   extensionsFromDefinition(route.Metadata),
   982  	}
   983  
   984  	if consumesMultipart {
   985  		operation.Consumes = append(operation.Consumes, "multipart/form-data")
   986  	}
   987  
   988  	computeProduces(operation, s, action)
   989  	applySecurity(operation, action.Security)
   990  
   991  	computePaths(operation, s, route, basePath)
   992  	return nil
   993  }
   994  
   995  func computeProduces(operation *Operation, s *Swagger, action *design.ActionDefinition) {
   996  	produces := make(map[string]struct{})
   997  	action.IterateResponses(func(resp *design.ResponseDefinition) error {
   998  		if resp.MediaType != "" {
   999  			produces[resp.MediaType] = struct{}{}
  1000  		}
  1001  		return nil
  1002  	})
  1003  	subset := true
  1004  	for p := range produces {
  1005  		found := false
  1006  		for _, p2 := range s.Produces {
  1007  			if p == p2 {
  1008  				found = true
  1009  				break
  1010  			}
  1011  		}
  1012  		if !found {
  1013  			subset = false
  1014  			break
  1015  		}
  1016  	}
  1017  	if !subset {
  1018  		operation.Produces = make([]string, len(produces))
  1019  		i := 0
  1020  		for p := range produces {
  1021  			operation.Produces[i] = p
  1022  			i++
  1023  		}
  1024  		sort.Strings(operation.Produces)
  1025  	}
  1026  }
  1027  
  1028  func computePaths(operation *Operation, s *Swagger, route *design.RouteDefinition, basePath string) {
  1029  	key := design.WildcardRegex.ReplaceAllStringFunc(
  1030  		route.FullPath(),
  1031  		func(w string) string {
  1032  			return fmt.Sprintf("/{%s}", w[2:])
  1033  		},
  1034  	)
  1035  	bp := design.WildcardRegex.ReplaceAllStringFunc(
  1036  		basePath,
  1037  		func(w string) string {
  1038  			return fmt.Sprintf("/{%s}", w[2:])
  1039  		},
  1040  	)
  1041  	if bp != "/" {
  1042  		key = strings.TrimPrefix(key, bp)
  1043  	}
  1044  	if key == "" {
  1045  		key = "/"
  1046  	}
  1047  	var path interface{}
  1048  	var ok bool
  1049  	if path, ok = s.Paths[key]; !ok {
  1050  		path = new(Path)
  1051  		s.Paths[key] = path
  1052  	}
  1053  	p := path.(*Path)
  1054  	switch route.Verb {
  1055  	case "GET":
  1056  		p.Get = operation
  1057  	case "PUT":
  1058  		p.Put = operation
  1059  	case "POST":
  1060  		p.Post = operation
  1061  	case "DELETE":
  1062  		p.Delete = operation
  1063  	case "OPTIONS":
  1064  		p.Options = operation
  1065  	case "HEAD":
  1066  		p.Head = operation
  1067  	case "PATCH":
  1068  		p.Patch = operation
  1069  	}
  1070  	p.Extensions = extensionsFromDefinition(route.Parent.Metadata)
  1071  }
  1072  
  1073  func applySecurity(operation *Operation, security *design.SecurityDefinition) {
  1074  	if security != nil && security.Scheme.Kind != design.NoSecurityKind {
  1075  		if security.Scheme.Kind == design.JWTSecurityKind && len(security.Scopes) > 0 {
  1076  			if operation.Description != "" {
  1077  				operation.Description += "\n\n"
  1078  			}
  1079  			operation.Description += fmt.Sprintf("Required security scopes:\n%s", scopesList(security.Scopes))
  1080  		}
  1081  		scopes := security.Scopes
  1082  		if scopes == nil {
  1083  			scopes = make([]string, 0)
  1084  		}
  1085  		sec := []map[string][]string{{security.Scheme.SchemeName: scopes}}
  1086  		operation.Security = sec
  1087  	}
  1088  }
  1089  
  1090  func scopesList(scopes []string) string {
  1091  	sort.Strings(scopes)
  1092  
  1093  	var lines []string
  1094  	for _, scope := range scopes {
  1095  		lines = append(lines, fmt.Sprintf("  * `%s`", scope))
  1096  	}
  1097  	return strings.Join(lines, "\n")
  1098  }
  1099  
  1100  func docsFromDefinition(docs *design.DocsDefinition) *ExternalDocs {
  1101  	if docs == nil {
  1102  		return nil
  1103  	}
  1104  	return &ExternalDocs{
  1105  		Description: docs.Description,
  1106  		URL:         docs.URL,
  1107  	}
  1108  }
  1109  
  1110  func initEnumValidation(def interface{}, values []interface{}) {
  1111  	switch actual := def.(type) {
  1112  	case *Parameter:
  1113  		actual.Enum = values
  1114  	case *Header:
  1115  		actual.Enum = values
  1116  	case *Items:
  1117  		actual.Enum = values
  1118  	}
  1119  }
  1120  
  1121  func initFormatValidation(def interface{}, format string) {
  1122  	switch actual := def.(type) {
  1123  	case *Parameter:
  1124  		actual.Format = format
  1125  	case *Header:
  1126  		actual.Format = format
  1127  	case *Items:
  1128  		actual.Format = format
  1129  	}
  1130  }
  1131  
  1132  func initPatternValidation(def interface{}, pattern string) {
  1133  	switch actual := def.(type) {
  1134  	case *Parameter:
  1135  		actual.Pattern = pattern
  1136  	case *Header:
  1137  		actual.Pattern = pattern
  1138  	case *Items:
  1139  		actual.Pattern = pattern
  1140  	}
  1141  }
  1142  
  1143  func initMinimumValidation(def interface{}, min *float64) {
  1144  	switch actual := def.(type) {
  1145  	case *Parameter:
  1146  		actual.Minimum = min
  1147  		actual.ExclusiveMinimum = false
  1148  	case *Header:
  1149  		actual.Minimum = min
  1150  		actual.ExclusiveMinimum = false
  1151  	case *Items:
  1152  		actual.Minimum = min
  1153  		actual.ExclusiveMinimum = false
  1154  	}
  1155  }
  1156  
  1157  func initMaximumValidation(def interface{}, max *float64) {
  1158  	switch actual := def.(type) {
  1159  	case *Parameter:
  1160  		actual.Maximum = max
  1161  		actual.ExclusiveMaximum = false
  1162  	case *Header:
  1163  		actual.Maximum = max
  1164  		actual.ExclusiveMaximum = false
  1165  	case *Items:
  1166  		actual.Maximum = max
  1167  		actual.ExclusiveMaximum = false
  1168  	}
  1169  }
  1170  
  1171  func initMinLengthValidation(def interface{}, isArray bool, min *int) {
  1172  	switch actual := def.(type) {
  1173  	case *Parameter:
  1174  		if isArray {
  1175  			actual.MinItems = min
  1176  		} else {
  1177  			actual.MinLength = min
  1178  		}
  1179  	case *Header:
  1180  		actual.MinLength = min
  1181  	case *Items:
  1182  		actual.MinLength = min
  1183  	}
  1184  }
  1185  
  1186  func initMaxLengthValidation(def interface{}, isArray bool, max *int) {
  1187  	switch actual := def.(type) {
  1188  	case *Parameter:
  1189  		if isArray {
  1190  			actual.MaxItems = max
  1191  		} else {
  1192  			actual.MaxLength = max
  1193  		}
  1194  	case *Header:
  1195  		actual.MaxLength = max
  1196  	case *Items:
  1197  		actual.MaxLength = max
  1198  	}
  1199  }
  1200  
  1201  func initValidations(attr *design.AttributeDefinition, def interface{}) {
  1202  	val := attr.Validation
  1203  	if val == nil {
  1204  		return
  1205  	}
  1206  	initEnumValidation(def, val.Values)
  1207  	initFormatValidation(def, val.Format)
  1208  	initPatternValidation(def, val.Pattern)
  1209  	if val.Minimum != nil {
  1210  		initMinimumValidation(def, val.Minimum)
  1211  	}
  1212  	if val.Maximum != nil {
  1213  		initMaximumValidation(def, val.Maximum)
  1214  	}
  1215  	if val.MinLength != nil {
  1216  		initMinLengthValidation(def, attr.Type.IsArray(), val.MinLength)
  1217  	}
  1218  	if val.MaxLength != nil {
  1219  		initMaxLengthValidation(def, attr.Type.IsArray(), val.MaxLength)
  1220  	}
  1221  }