...

Source file src/github.com/go-openapi/analysis/analyzer.go

Documentation: github.com/go-openapi/analysis

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package analysis
    16  
    17  import (
    18  	"fmt"
    19  	slashpath "path"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/go-openapi/jsonpointer"
    24  	"github.com/go-openapi/spec"
    25  	"github.com/go-openapi/swag"
    26  )
    27  
    28  type referenceAnalysis struct {
    29  	schemas        map[string]spec.Ref
    30  	responses      map[string]spec.Ref
    31  	parameters     map[string]spec.Ref
    32  	items          map[string]spec.Ref
    33  	headerItems    map[string]spec.Ref
    34  	parameterItems map[string]spec.Ref
    35  	allRefs        map[string]spec.Ref
    36  	pathItems      map[string]spec.Ref
    37  }
    38  
    39  func (r *referenceAnalysis) addRef(key string, ref spec.Ref) {
    40  	r.allRefs["#"+key] = ref
    41  }
    42  
    43  func (r *referenceAnalysis) addItemsRef(key string, items *spec.Items, location string) {
    44  	r.items["#"+key] = items.Ref
    45  	r.addRef(key, items.Ref)
    46  	if location == "header" {
    47  		// NOTE: in swagger 2.0, headers and parameters (but not body param schemas) are simple schemas
    48  		// and $ref are not supported here. However it is possible to analyze this.
    49  		r.headerItems["#"+key] = items.Ref
    50  	} else {
    51  		r.parameterItems["#"+key] = items.Ref
    52  	}
    53  }
    54  
    55  func (r *referenceAnalysis) addSchemaRef(key string, ref SchemaRef) {
    56  	r.schemas["#"+key] = ref.Schema.Ref
    57  	r.addRef(key, ref.Schema.Ref)
    58  }
    59  
    60  func (r *referenceAnalysis) addResponseRef(key string, resp *spec.Response) {
    61  	r.responses["#"+key] = resp.Ref
    62  	r.addRef(key, resp.Ref)
    63  }
    64  
    65  func (r *referenceAnalysis) addParamRef(key string, param *spec.Parameter) {
    66  	r.parameters["#"+key] = param.Ref
    67  	r.addRef(key, param.Ref)
    68  }
    69  
    70  func (r *referenceAnalysis) addPathItemRef(key string, pathItem *spec.PathItem) {
    71  	r.pathItems["#"+key] = pathItem.Ref
    72  	r.addRef(key, pathItem.Ref)
    73  }
    74  
    75  type patternAnalysis struct {
    76  	parameters  map[string]string
    77  	headers     map[string]string
    78  	items       map[string]string
    79  	schemas     map[string]string
    80  	allPatterns map[string]string
    81  }
    82  
    83  func (p *patternAnalysis) addPattern(key, pattern string) {
    84  	p.allPatterns["#"+key] = pattern
    85  }
    86  
    87  func (p *patternAnalysis) addParameterPattern(key, pattern string) {
    88  	p.parameters["#"+key] = pattern
    89  	p.addPattern(key, pattern)
    90  }
    91  
    92  func (p *patternAnalysis) addHeaderPattern(key, pattern string) {
    93  	p.headers["#"+key] = pattern
    94  	p.addPattern(key, pattern)
    95  }
    96  
    97  func (p *patternAnalysis) addItemsPattern(key, pattern string) {
    98  	p.items["#"+key] = pattern
    99  	p.addPattern(key, pattern)
   100  }
   101  
   102  func (p *patternAnalysis) addSchemaPattern(key, pattern string) {
   103  	p.schemas["#"+key] = pattern
   104  	p.addPattern(key, pattern)
   105  }
   106  
   107  type enumAnalysis struct {
   108  	parameters map[string][]interface{}
   109  	headers    map[string][]interface{}
   110  	items      map[string][]interface{}
   111  	schemas    map[string][]interface{}
   112  	allEnums   map[string][]interface{}
   113  }
   114  
   115  func (p *enumAnalysis) addEnum(key string, enum []interface{}) {
   116  	p.allEnums["#"+key] = enum
   117  }
   118  
   119  func (p *enumAnalysis) addParameterEnum(key string, enum []interface{}) {
   120  	p.parameters["#"+key] = enum
   121  	p.addEnum(key, enum)
   122  }
   123  
   124  func (p *enumAnalysis) addHeaderEnum(key string, enum []interface{}) {
   125  	p.headers["#"+key] = enum
   126  	p.addEnum(key, enum)
   127  }
   128  
   129  func (p *enumAnalysis) addItemsEnum(key string, enum []interface{}) {
   130  	p.items["#"+key] = enum
   131  	p.addEnum(key, enum)
   132  }
   133  
   134  func (p *enumAnalysis) addSchemaEnum(key string, enum []interface{}) {
   135  	p.schemas["#"+key] = enum
   136  	p.addEnum(key, enum)
   137  }
   138  
   139  // New takes a swagger spec object and returns an analyzed spec document.
   140  // The analyzed document contains a number of indices that make it easier to
   141  // reason about semantics of a swagger specification for use in code generation
   142  // or validation etc.
   143  func New(doc *spec.Swagger) *Spec {
   144  	a := &Spec{
   145  		spec:       doc,
   146  		references: referenceAnalysis{},
   147  		patterns:   patternAnalysis{},
   148  		enums:      enumAnalysis{},
   149  	}
   150  	a.reset()
   151  	a.initialize()
   152  
   153  	return a
   154  }
   155  
   156  // Spec is an analyzed specification object. It takes a swagger spec object and turns it into a registry
   157  // with a bunch of utility methods to act on the information in the spec.
   158  type Spec struct {
   159  	spec        *spec.Swagger
   160  	consumes    map[string]struct{}
   161  	produces    map[string]struct{}
   162  	authSchemes map[string]struct{}
   163  	operations  map[string]map[string]*spec.Operation
   164  	references  referenceAnalysis
   165  	patterns    patternAnalysis
   166  	enums       enumAnalysis
   167  	allSchemas  map[string]SchemaRef
   168  	allOfs      map[string]SchemaRef
   169  }
   170  
   171  func (s *Spec) reset() {
   172  	s.consumes = make(map[string]struct{}, 150)
   173  	s.produces = make(map[string]struct{}, 150)
   174  	s.authSchemes = make(map[string]struct{}, 150)
   175  	s.operations = make(map[string]map[string]*spec.Operation, 150)
   176  	s.allSchemas = make(map[string]SchemaRef, 150)
   177  	s.allOfs = make(map[string]SchemaRef, 150)
   178  	s.references.schemas = make(map[string]spec.Ref, 150)
   179  	s.references.pathItems = make(map[string]spec.Ref, 150)
   180  	s.references.responses = make(map[string]spec.Ref, 150)
   181  	s.references.parameters = make(map[string]spec.Ref, 150)
   182  	s.references.items = make(map[string]spec.Ref, 150)
   183  	s.references.headerItems = make(map[string]spec.Ref, 150)
   184  	s.references.parameterItems = make(map[string]spec.Ref, 150)
   185  	s.references.allRefs = make(map[string]spec.Ref, 150)
   186  	s.patterns.parameters = make(map[string]string, 150)
   187  	s.patterns.headers = make(map[string]string, 150)
   188  	s.patterns.items = make(map[string]string, 150)
   189  	s.patterns.schemas = make(map[string]string, 150)
   190  	s.patterns.allPatterns = make(map[string]string, 150)
   191  	s.enums.parameters = make(map[string][]interface{}, 150)
   192  	s.enums.headers = make(map[string][]interface{}, 150)
   193  	s.enums.items = make(map[string][]interface{}, 150)
   194  	s.enums.schemas = make(map[string][]interface{}, 150)
   195  	s.enums.allEnums = make(map[string][]interface{}, 150)
   196  }
   197  
   198  func (s *Spec) reload() {
   199  	s.reset()
   200  	s.initialize()
   201  }
   202  
   203  func (s *Spec) initialize() {
   204  	for _, c := range s.spec.Consumes {
   205  		s.consumes[c] = struct{}{}
   206  	}
   207  	for _, c := range s.spec.Produces {
   208  		s.produces[c] = struct{}{}
   209  	}
   210  	for _, ss := range s.spec.Security {
   211  		for k := range ss {
   212  			s.authSchemes[k] = struct{}{}
   213  		}
   214  	}
   215  	for path, pathItem := range s.AllPaths() {
   216  		s.analyzeOperations(path, &pathItem) //#nosec
   217  	}
   218  
   219  	for name, parameter := range s.spec.Parameters {
   220  		refPref := slashpath.Join("/parameters", jsonpointer.Escape(name))
   221  		if parameter.Items != nil {
   222  			s.analyzeItems("items", parameter.Items, refPref, "parameter")
   223  		}
   224  		if parameter.In == "body" && parameter.Schema != nil {
   225  			s.analyzeSchema("schema", parameter.Schema, refPref)
   226  		}
   227  		if parameter.Pattern != "" {
   228  			s.patterns.addParameterPattern(refPref, parameter.Pattern)
   229  		}
   230  		if len(parameter.Enum) > 0 {
   231  			s.enums.addParameterEnum(refPref, parameter.Enum)
   232  		}
   233  	}
   234  
   235  	for name, response := range s.spec.Responses {
   236  		refPref := slashpath.Join("/responses", jsonpointer.Escape(name))
   237  		for k, v := range response.Headers {
   238  			hRefPref := slashpath.Join(refPref, "headers", k)
   239  			if v.Items != nil {
   240  				s.analyzeItems("items", v.Items, hRefPref, "header")
   241  			}
   242  			if v.Pattern != "" {
   243  				s.patterns.addHeaderPattern(hRefPref, v.Pattern)
   244  			}
   245  			if len(v.Enum) > 0 {
   246  				s.enums.addHeaderEnum(hRefPref, v.Enum)
   247  			}
   248  		}
   249  		if response.Schema != nil {
   250  			s.analyzeSchema("schema", response.Schema, refPref)
   251  		}
   252  	}
   253  
   254  	for name := range s.spec.Definitions {
   255  		schema := s.spec.Definitions[name]
   256  		s.analyzeSchema(name, &schema, "/definitions")
   257  	}
   258  	// TODO: after analyzing all things and flattening schemas etc
   259  	// resolve all the collected references to their final representations
   260  	// best put in a separate method because this could get expensive
   261  }
   262  
   263  func (s *Spec) analyzeOperations(path string, pi *spec.PathItem) {
   264  	// TODO: resolve refs here?
   265  	// Currently, operations declared via pathItem $ref are known only after expansion
   266  	op := pi
   267  	if pi.Ref.String() != "" {
   268  		key := slashpath.Join("/paths", jsonpointer.Escape(path))
   269  		s.references.addPathItemRef(key, pi)
   270  	}
   271  	s.analyzeOperation("GET", path, op.Get)
   272  	s.analyzeOperation("PUT", path, op.Put)
   273  	s.analyzeOperation("POST", path, op.Post)
   274  	s.analyzeOperation("PATCH", path, op.Patch)
   275  	s.analyzeOperation("DELETE", path, op.Delete)
   276  	s.analyzeOperation("HEAD", path, op.Head)
   277  	s.analyzeOperation("OPTIONS", path, op.Options)
   278  	for i, param := range op.Parameters {
   279  		refPref := slashpath.Join("/paths", jsonpointer.Escape(path), "parameters", strconv.Itoa(i))
   280  		if param.Ref.String() != "" {
   281  			s.references.addParamRef(refPref, &param) //#nosec
   282  		}
   283  		if param.Pattern != "" {
   284  			s.patterns.addParameterPattern(refPref, param.Pattern)
   285  		}
   286  		if len(param.Enum) > 0 {
   287  			s.enums.addParameterEnum(refPref, param.Enum)
   288  		}
   289  		if param.Items != nil {
   290  			s.analyzeItems("items", param.Items, refPref, "parameter")
   291  		}
   292  		if param.Schema != nil {
   293  			s.analyzeSchema("schema", param.Schema, refPref)
   294  		}
   295  	}
   296  }
   297  
   298  func (s *Spec) analyzeItems(name string, items *spec.Items, prefix, location string) {
   299  	if items == nil {
   300  		return
   301  	}
   302  	refPref := slashpath.Join(prefix, name)
   303  	s.analyzeItems(name, items.Items, refPref, location)
   304  	if items.Ref.String() != "" {
   305  		s.references.addItemsRef(refPref, items, location)
   306  	}
   307  	if items.Pattern != "" {
   308  		s.patterns.addItemsPattern(refPref, items.Pattern)
   309  	}
   310  	if len(items.Enum) > 0 {
   311  		s.enums.addItemsEnum(refPref, items.Enum)
   312  	}
   313  }
   314  
   315  func (s *Spec) analyzeParameter(prefix string, i int, param spec.Parameter) {
   316  	refPref := slashpath.Join(prefix, "parameters", strconv.Itoa(i))
   317  	if param.Ref.String() != "" {
   318  		s.references.addParamRef(refPref, &param) //#nosec
   319  	}
   320  
   321  	if param.Pattern != "" {
   322  		s.patterns.addParameterPattern(refPref, param.Pattern)
   323  	}
   324  
   325  	if len(param.Enum) > 0 {
   326  		s.enums.addParameterEnum(refPref, param.Enum)
   327  	}
   328  
   329  	s.analyzeItems("items", param.Items, refPref, "parameter")
   330  	if param.In == "body" && param.Schema != nil {
   331  		s.analyzeSchema("schema", param.Schema, refPref)
   332  	}
   333  }
   334  
   335  func (s *Spec) analyzeOperation(method, path string, op *spec.Operation) {
   336  	if op == nil {
   337  		return
   338  	}
   339  
   340  	for _, c := range op.Consumes {
   341  		s.consumes[c] = struct{}{}
   342  	}
   343  
   344  	for _, c := range op.Produces {
   345  		s.produces[c] = struct{}{}
   346  	}
   347  
   348  	for _, ss := range op.Security {
   349  		for k := range ss {
   350  			s.authSchemes[k] = struct{}{}
   351  		}
   352  	}
   353  
   354  	if _, ok := s.operations[method]; !ok {
   355  		s.operations[method] = make(map[string]*spec.Operation)
   356  	}
   357  
   358  	s.operations[method][path] = op
   359  	prefix := slashpath.Join("/paths", jsonpointer.Escape(path), strings.ToLower(method))
   360  	for i, param := range op.Parameters {
   361  		s.analyzeParameter(prefix, i, param)
   362  	}
   363  
   364  	if op.Responses == nil {
   365  		return
   366  	}
   367  
   368  	if op.Responses.Default != nil {
   369  		s.analyzeDefaultResponse(prefix, op.Responses.Default)
   370  	}
   371  
   372  	for k, res := range op.Responses.StatusCodeResponses {
   373  		s.analyzeResponse(prefix, k, res)
   374  	}
   375  }
   376  
   377  func (s *Spec) analyzeDefaultResponse(prefix string, res *spec.Response) {
   378  	refPref := slashpath.Join(prefix, "responses", "default")
   379  	if res.Ref.String() != "" {
   380  		s.references.addResponseRef(refPref, res)
   381  	}
   382  
   383  	for k, v := range res.Headers {
   384  		hRefPref := slashpath.Join(refPref, "headers", k)
   385  		s.analyzeItems("items", v.Items, hRefPref, "header")
   386  		if v.Pattern != "" {
   387  			s.patterns.addHeaderPattern(hRefPref, v.Pattern)
   388  		}
   389  	}
   390  
   391  	if res.Schema != nil {
   392  		s.analyzeSchema("schema", res.Schema, refPref)
   393  	}
   394  }
   395  
   396  func (s *Spec) analyzeResponse(prefix string, k int, res spec.Response) {
   397  	refPref := slashpath.Join(prefix, "responses", strconv.Itoa(k))
   398  	if res.Ref.String() != "" {
   399  		s.references.addResponseRef(refPref, &res) //#nosec
   400  	}
   401  
   402  	for k, v := range res.Headers {
   403  		hRefPref := slashpath.Join(refPref, "headers", k)
   404  		s.analyzeItems("items", v.Items, hRefPref, "header")
   405  		if v.Pattern != "" {
   406  			s.patterns.addHeaderPattern(hRefPref, v.Pattern)
   407  		}
   408  
   409  		if len(v.Enum) > 0 {
   410  			s.enums.addHeaderEnum(hRefPref, v.Enum)
   411  		}
   412  	}
   413  
   414  	if res.Schema != nil {
   415  		s.analyzeSchema("schema", res.Schema, refPref)
   416  	}
   417  }
   418  
   419  func (s *Spec) analyzeSchema(name string, schema *spec.Schema, prefix string) {
   420  	refURI := slashpath.Join(prefix, jsonpointer.Escape(name))
   421  	schRef := SchemaRef{
   422  		Name:     name,
   423  		Schema:   schema,
   424  		Ref:      spec.MustCreateRef("#" + refURI),
   425  		TopLevel: prefix == "/definitions",
   426  	}
   427  
   428  	s.allSchemas["#"+refURI] = schRef
   429  
   430  	if schema.Ref.String() != "" {
   431  		s.references.addSchemaRef(refURI, schRef)
   432  	}
   433  
   434  	if schema.Pattern != "" {
   435  		s.patterns.addSchemaPattern(refURI, schema.Pattern)
   436  	}
   437  
   438  	if len(schema.Enum) > 0 {
   439  		s.enums.addSchemaEnum(refURI, schema.Enum)
   440  	}
   441  
   442  	for k, v := range schema.Definitions {
   443  		v := v
   444  		s.analyzeSchema(k, &v, slashpath.Join(refURI, "definitions"))
   445  	}
   446  
   447  	for k, v := range schema.Properties {
   448  		v := v
   449  		s.analyzeSchema(k, &v, slashpath.Join(refURI, "properties"))
   450  	}
   451  
   452  	for k, v := range schema.PatternProperties {
   453  		v := v
   454  		// NOTE: swagger 2.0 does not support PatternProperties.
   455  		// However it is possible to analyze this in a schema
   456  		s.analyzeSchema(k, &v, slashpath.Join(refURI, "patternProperties"))
   457  	}
   458  
   459  	for i := range schema.AllOf {
   460  		v := &schema.AllOf[i]
   461  		s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "allOf"))
   462  	}
   463  
   464  	if len(schema.AllOf) > 0 {
   465  		s.allOfs["#"+refURI] = schRef
   466  	}
   467  
   468  	for i := range schema.AnyOf {
   469  		v := &schema.AnyOf[i]
   470  		// NOTE: swagger 2.0 does not support anyOf constructs.
   471  		// However it is possible to analyze this in a schema
   472  		s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "anyOf"))
   473  	}
   474  
   475  	for i := range schema.OneOf {
   476  		v := &schema.OneOf[i]
   477  		// NOTE: swagger 2.0 does not support oneOf constructs.
   478  		// However it is possible to analyze this in a schema
   479  		s.analyzeSchema(strconv.Itoa(i), v, slashpath.Join(refURI, "oneOf"))
   480  	}
   481  
   482  	if schema.Not != nil {
   483  		// NOTE: swagger 2.0 does not support "not" constructs.
   484  		// However it is possible to analyze this in a schema
   485  		s.analyzeSchema("not", schema.Not, refURI)
   486  	}
   487  
   488  	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
   489  		s.analyzeSchema("additionalProperties", schema.AdditionalProperties.Schema, refURI)
   490  	}
   491  
   492  	if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
   493  		// NOTE: swagger 2.0 does not support AdditionalItems.
   494  		// However it is possible to analyze this in a schema
   495  		s.analyzeSchema("additionalItems", schema.AdditionalItems.Schema, refURI)
   496  	}
   497  
   498  	if schema.Items != nil {
   499  		if schema.Items.Schema != nil {
   500  			s.analyzeSchema("items", schema.Items.Schema, refURI)
   501  		}
   502  
   503  		for i := range schema.Items.Schemas {
   504  			sch := &schema.Items.Schemas[i]
   505  			s.analyzeSchema(strconv.Itoa(i), sch, slashpath.Join(refURI, "items"))
   506  		}
   507  	}
   508  }
   509  
   510  // SecurityRequirement is a representation of a security requirement for an operation
   511  type SecurityRequirement struct {
   512  	Name   string
   513  	Scopes []string
   514  }
   515  
   516  // SecurityRequirementsFor gets the security requirements for the operation
   517  func (s *Spec) SecurityRequirementsFor(operation *spec.Operation) [][]SecurityRequirement {
   518  	if s.spec.Security == nil && operation.Security == nil {
   519  		return nil
   520  	}
   521  
   522  	schemes := s.spec.Security
   523  	if operation.Security != nil {
   524  		schemes = operation.Security
   525  	}
   526  
   527  	result := [][]SecurityRequirement{}
   528  	for _, scheme := range schemes {
   529  		if len(scheme) == 0 {
   530  			// append a zero object for anonymous
   531  			result = append(result, []SecurityRequirement{{}})
   532  
   533  			continue
   534  		}
   535  
   536  		var reqs []SecurityRequirement
   537  		for k, v := range scheme {
   538  			if v == nil {
   539  				v = []string{}
   540  			}
   541  			reqs = append(reqs, SecurityRequirement{Name: k, Scopes: v})
   542  		}
   543  
   544  		result = append(result, reqs)
   545  	}
   546  
   547  	return result
   548  }
   549  
   550  // SecurityDefinitionsForRequirements gets the matching security definitions for a set of requirements
   551  func (s *Spec) SecurityDefinitionsForRequirements(requirements []SecurityRequirement) map[string]spec.SecurityScheme {
   552  	result := make(map[string]spec.SecurityScheme)
   553  
   554  	for _, v := range requirements {
   555  		if definition, ok := s.spec.SecurityDefinitions[v.Name]; ok {
   556  			if definition != nil {
   557  				result[v.Name] = *definition
   558  			}
   559  		}
   560  	}
   561  
   562  	return result
   563  }
   564  
   565  // SecurityDefinitionsFor gets the matching security definitions for a set of requirements
   566  func (s *Spec) SecurityDefinitionsFor(operation *spec.Operation) map[string]spec.SecurityScheme {
   567  	requirements := s.SecurityRequirementsFor(operation)
   568  	if len(requirements) == 0 {
   569  		return nil
   570  	}
   571  
   572  	result := make(map[string]spec.SecurityScheme)
   573  	for _, reqs := range requirements {
   574  		for _, v := range reqs {
   575  			if v.Name == "" {
   576  				// optional requirement
   577  				continue
   578  			}
   579  
   580  			if _, ok := result[v.Name]; ok {
   581  				// duplicate requirement
   582  				continue
   583  			}
   584  
   585  			if definition, ok := s.spec.SecurityDefinitions[v.Name]; ok {
   586  				if definition != nil {
   587  					result[v.Name] = *definition
   588  				}
   589  			}
   590  		}
   591  	}
   592  
   593  	return result
   594  }
   595  
   596  // ConsumesFor gets the mediatypes for the operation
   597  func (s *Spec) ConsumesFor(operation *spec.Operation) []string {
   598  	if len(operation.Consumes) == 0 {
   599  		cons := make(map[string]struct{}, len(s.spec.Consumes))
   600  		for _, k := range s.spec.Consumes {
   601  			cons[k] = struct{}{}
   602  		}
   603  
   604  		return s.structMapKeys(cons)
   605  	}
   606  
   607  	cons := make(map[string]struct{}, len(operation.Consumes))
   608  	for _, c := range operation.Consumes {
   609  		cons[c] = struct{}{}
   610  	}
   611  
   612  	return s.structMapKeys(cons)
   613  }
   614  
   615  // ProducesFor gets the mediatypes for the operation
   616  func (s *Spec) ProducesFor(operation *spec.Operation) []string {
   617  	if len(operation.Produces) == 0 {
   618  		prod := make(map[string]struct{}, len(s.spec.Produces))
   619  		for _, k := range s.spec.Produces {
   620  			prod[k] = struct{}{}
   621  		}
   622  
   623  		return s.structMapKeys(prod)
   624  	}
   625  
   626  	prod := make(map[string]struct{}, len(operation.Produces))
   627  	for _, c := range operation.Produces {
   628  		prod[c] = struct{}{}
   629  	}
   630  
   631  	return s.structMapKeys(prod)
   632  }
   633  
   634  func mapKeyFromParam(param *spec.Parameter) string {
   635  	return fmt.Sprintf("%s#%s", param.In, fieldNameFromParam(param))
   636  }
   637  
   638  func fieldNameFromParam(param *spec.Parameter) string {
   639  	// TODO: this should be x-go-name
   640  	if nm, ok := param.Extensions.GetString("go-name"); ok {
   641  		return nm
   642  	}
   643  
   644  	return swag.ToGoName(param.Name)
   645  }
   646  
   647  // ErrorOnParamFunc is a callback function to be invoked
   648  // whenever an error is encountered while resolving references
   649  // on parameters.
   650  //
   651  // This function takes as input the spec.Parameter which triggered the
   652  // error and the error itself.
   653  //
   654  // If the callback function returns false, the calling function should bail.
   655  //
   656  // If it returns true, the calling function should continue evaluating parameters.
   657  // A nil ErrorOnParamFunc must be evaluated as equivalent to panic().
   658  type ErrorOnParamFunc func(spec.Parameter, error) bool
   659  
   660  func (s *Spec) paramsAsMap(parameters []spec.Parameter, res map[string]spec.Parameter, callmeOnError ErrorOnParamFunc) {
   661  	for _, param := range parameters {
   662  		pr := param
   663  		if pr.Ref.String() == "" {
   664  			res[mapKeyFromParam(&pr)] = pr
   665  
   666  			continue
   667  		}
   668  
   669  		// resolve $ref
   670  		if callmeOnError == nil {
   671  			callmeOnError = func(_ spec.Parameter, err error) bool {
   672  				panic(err)
   673  			}
   674  		}
   675  
   676  		obj, _, err := pr.Ref.GetPointer().Get(s.spec)
   677  		if err != nil {
   678  			if callmeOnError(param, fmt.Errorf("invalid reference: %q", pr.Ref.String())) {
   679  				continue
   680  			}
   681  
   682  			break
   683  		}
   684  
   685  		objAsParam, ok := obj.(spec.Parameter)
   686  		if !ok {
   687  			if callmeOnError(param, fmt.Errorf("resolved reference is not a parameter: %q", pr.Ref.String())) {
   688  				continue
   689  			}
   690  
   691  			break
   692  		}
   693  
   694  		pr = objAsParam
   695  		res[mapKeyFromParam(&pr)] = pr
   696  	}
   697  }
   698  
   699  // ParametersFor the specified operation id.
   700  //
   701  // Assumes parameters properly resolve references if any and that
   702  // such references actually resolve to a parameter object.
   703  // Otherwise, panics.
   704  func (s *Spec) ParametersFor(operationID string) []spec.Parameter {
   705  	return s.SafeParametersFor(operationID, nil)
   706  }
   707  
   708  // SafeParametersFor the specified operation id.
   709  //
   710  // Does not assume parameters properly resolve references or that
   711  // such references actually resolve to a parameter object.
   712  //
   713  // Upon error, invoke a ErrorOnParamFunc callback with the erroneous
   714  // parameters. If the callback is set to nil, panics upon errors.
   715  func (s *Spec) SafeParametersFor(operationID string, callmeOnError ErrorOnParamFunc) []spec.Parameter {
   716  	gatherParams := func(pi *spec.PathItem, op *spec.Operation) []spec.Parameter {
   717  		bag := make(map[string]spec.Parameter)
   718  		s.paramsAsMap(pi.Parameters, bag, callmeOnError)
   719  		s.paramsAsMap(op.Parameters, bag, callmeOnError)
   720  
   721  		var res []spec.Parameter
   722  		for _, v := range bag {
   723  			res = append(res, v)
   724  		}
   725  
   726  		return res
   727  	}
   728  
   729  	for _, pi := range s.spec.Paths.Paths {
   730  		if pi.Get != nil && pi.Get.ID == operationID {
   731  			return gatherParams(&pi, pi.Get) //#nosec
   732  		}
   733  		if pi.Head != nil && pi.Head.ID == operationID {
   734  			return gatherParams(&pi, pi.Head) //#nosec
   735  		}
   736  		if pi.Options != nil && pi.Options.ID == operationID {
   737  			return gatherParams(&pi, pi.Options) //#nosec
   738  		}
   739  		if pi.Post != nil && pi.Post.ID == operationID {
   740  			return gatherParams(&pi, pi.Post) //#nosec
   741  		}
   742  		if pi.Patch != nil && pi.Patch.ID == operationID {
   743  			return gatherParams(&pi, pi.Patch) //#nosec
   744  		}
   745  		if pi.Put != nil && pi.Put.ID == operationID {
   746  			return gatherParams(&pi, pi.Put) //#nosec
   747  		}
   748  		if pi.Delete != nil && pi.Delete.ID == operationID {
   749  			return gatherParams(&pi, pi.Delete) //#nosec
   750  		}
   751  	}
   752  
   753  	return nil
   754  }
   755  
   756  // ParamsFor the specified method and path. Aggregates them with the defaults etc, so it's all the params that
   757  // apply for the method and path.
   758  //
   759  // Assumes parameters properly resolve references if any and that
   760  // such references actually resolve to a parameter object.
   761  // Otherwise, panics.
   762  func (s *Spec) ParamsFor(method, path string) map[string]spec.Parameter {
   763  	return s.SafeParamsFor(method, path, nil)
   764  }
   765  
   766  // SafeParamsFor the specified method and path. Aggregates them with the defaults etc, so it's all the params that
   767  // apply for the method and path.
   768  //
   769  // Does not assume parameters properly resolve references or that
   770  // such references actually resolve to a parameter object.
   771  //
   772  // Upon error, invoke a ErrorOnParamFunc callback with the erroneous
   773  // parameters. If the callback is set to nil, panics upon errors.
   774  func (s *Spec) SafeParamsFor(method, path string, callmeOnError ErrorOnParamFunc) map[string]spec.Parameter {
   775  	res := make(map[string]spec.Parameter)
   776  	if pi, ok := s.spec.Paths.Paths[path]; ok {
   777  		s.paramsAsMap(pi.Parameters, res, callmeOnError)
   778  		s.paramsAsMap(s.operations[strings.ToUpper(method)][path].Parameters, res, callmeOnError)
   779  	}
   780  
   781  	return res
   782  }
   783  
   784  // OperationForName gets the operation for the given id
   785  func (s *Spec) OperationForName(operationID string) (string, string, *spec.Operation, bool) {
   786  	for method, pathItem := range s.operations {
   787  		for path, op := range pathItem {
   788  			if operationID == op.ID {
   789  				return method, path, op, true
   790  			}
   791  		}
   792  	}
   793  
   794  	return "", "", nil, false
   795  }
   796  
   797  // OperationFor the given method and path
   798  func (s *Spec) OperationFor(method, path string) (*spec.Operation, bool) {
   799  	if mp, ok := s.operations[strings.ToUpper(method)]; ok {
   800  		op, fn := mp[path]
   801  
   802  		return op, fn
   803  	}
   804  
   805  	return nil, false
   806  }
   807  
   808  // Operations gathers all the operations specified in the spec document
   809  func (s *Spec) Operations() map[string]map[string]*spec.Operation {
   810  	return s.operations
   811  }
   812  
   813  func (s *Spec) structMapKeys(mp map[string]struct{}) []string {
   814  	if len(mp) == 0 {
   815  		return nil
   816  	}
   817  
   818  	result := make([]string, 0, len(mp))
   819  	for k := range mp {
   820  		result = append(result, k)
   821  	}
   822  
   823  	return result
   824  }
   825  
   826  // AllPaths returns all the paths in the swagger spec
   827  func (s *Spec) AllPaths() map[string]spec.PathItem {
   828  	if s.spec == nil || s.spec.Paths == nil {
   829  		return nil
   830  	}
   831  
   832  	return s.spec.Paths.Paths
   833  }
   834  
   835  // OperationIDs gets all the operation ids based on method an dpath
   836  func (s *Spec) OperationIDs() []string {
   837  	if len(s.operations) == 0 {
   838  		return nil
   839  	}
   840  
   841  	result := make([]string, 0, len(s.operations))
   842  	for method, v := range s.operations {
   843  		for p, o := range v {
   844  			if o.ID != "" {
   845  				result = append(result, o.ID)
   846  			} else {
   847  				result = append(result, fmt.Sprintf("%s %s", strings.ToUpper(method), p))
   848  			}
   849  		}
   850  	}
   851  
   852  	return result
   853  }
   854  
   855  // OperationMethodPaths gets all the operation ids based on method an dpath
   856  func (s *Spec) OperationMethodPaths() []string {
   857  	if len(s.operations) == 0 {
   858  		return nil
   859  	}
   860  
   861  	result := make([]string, 0, len(s.operations))
   862  	for method, v := range s.operations {
   863  		for p := range v {
   864  			result = append(result, fmt.Sprintf("%s %s", strings.ToUpper(method), p))
   865  		}
   866  	}
   867  
   868  	return result
   869  }
   870  
   871  // RequiredConsumes gets all the distinct consumes that are specified in the specification document
   872  func (s *Spec) RequiredConsumes() []string {
   873  	return s.structMapKeys(s.consumes)
   874  }
   875  
   876  // RequiredProduces gets all the distinct produces that are specified in the specification document
   877  func (s *Spec) RequiredProduces() []string {
   878  	return s.structMapKeys(s.produces)
   879  }
   880  
   881  // RequiredSecuritySchemes gets all the distinct security schemes that are specified in the swagger spec
   882  func (s *Spec) RequiredSecuritySchemes() []string {
   883  	return s.structMapKeys(s.authSchemes)
   884  }
   885  
   886  // SchemaRef is a reference to a schema
   887  type SchemaRef struct {
   888  	Name     string
   889  	Ref      spec.Ref
   890  	Schema   *spec.Schema
   891  	TopLevel bool
   892  }
   893  
   894  // SchemasWithAllOf returns schema references to all schemas that are defined
   895  // with an allOf key
   896  func (s *Spec) SchemasWithAllOf() (result []SchemaRef) {
   897  	for _, v := range s.allOfs {
   898  		result = append(result, v)
   899  	}
   900  
   901  	return
   902  }
   903  
   904  // AllDefinitions returns schema references for all the definitions that were discovered
   905  func (s *Spec) AllDefinitions() (result []SchemaRef) {
   906  	for _, v := range s.allSchemas {
   907  		result = append(result, v)
   908  	}
   909  
   910  	return
   911  }
   912  
   913  // AllDefinitionReferences returns json refs for all the discovered schemas
   914  func (s *Spec) AllDefinitionReferences() (result []string) {
   915  	for _, v := range s.references.schemas {
   916  		result = append(result, v.String())
   917  	}
   918  
   919  	return
   920  }
   921  
   922  // AllParameterReferences returns json refs for all the discovered parameters
   923  func (s *Spec) AllParameterReferences() (result []string) {
   924  	for _, v := range s.references.parameters {
   925  		result = append(result, v.String())
   926  	}
   927  
   928  	return
   929  }
   930  
   931  // AllResponseReferences returns json refs for all the discovered responses
   932  func (s *Spec) AllResponseReferences() (result []string) {
   933  	for _, v := range s.references.responses {
   934  		result = append(result, v.String())
   935  	}
   936  
   937  	return
   938  }
   939  
   940  // AllPathItemReferences returns the references for all the items
   941  func (s *Spec) AllPathItemReferences() (result []string) {
   942  	for _, v := range s.references.pathItems {
   943  		result = append(result, v.String())
   944  	}
   945  
   946  	return
   947  }
   948  
   949  // AllItemsReferences returns the references for all the items in simple schemas (parameters or headers).
   950  //
   951  // NOTE: since Swagger 2.0 forbids $ref in simple params, this should always yield an empty slice for a valid
   952  // Swagger 2.0 spec.
   953  func (s *Spec) AllItemsReferences() (result []string) {
   954  	for _, v := range s.references.items {
   955  		result = append(result, v.String())
   956  	}
   957  
   958  	return
   959  }
   960  
   961  // AllReferences returns all the references found in the document, with possible duplicates
   962  func (s *Spec) AllReferences() (result []string) {
   963  	for _, v := range s.references.allRefs {
   964  		result = append(result, v.String())
   965  	}
   966  
   967  	return
   968  }
   969  
   970  // AllRefs returns all the unique references found in the document
   971  func (s *Spec) AllRefs() (result []spec.Ref) {
   972  	set := make(map[string]struct{})
   973  	for _, v := range s.references.allRefs {
   974  		a := v.String()
   975  		if a == "" {
   976  			continue
   977  		}
   978  
   979  		if _, ok := set[a]; !ok {
   980  			set[a] = struct{}{}
   981  			result = append(result, v)
   982  		}
   983  	}
   984  
   985  	return
   986  }
   987  
   988  func cloneStringMap(source map[string]string) map[string]string {
   989  	res := make(map[string]string, len(source))
   990  	for k, v := range source {
   991  		res[k] = v
   992  	}
   993  
   994  	return res
   995  }
   996  
   997  func cloneEnumMap(source map[string][]interface{}) map[string][]interface{} {
   998  	res := make(map[string][]interface{}, len(source))
   999  	for k, v := range source {
  1000  		res[k] = v
  1001  	}
  1002  
  1003  	return res
  1004  }
  1005  
  1006  // ParameterPatterns returns all the patterns found in parameters
  1007  // the map is cloned to avoid accidental changes
  1008  func (s *Spec) ParameterPatterns() map[string]string {
  1009  	return cloneStringMap(s.patterns.parameters)
  1010  }
  1011  
  1012  // HeaderPatterns returns all the patterns found in response headers
  1013  // the map is cloned to avoid accidental changes
  1014  func (s *Spec) HeaderPatterns() map[string]string {
  1015  	return cloneStringMap(s.patterns.headers)
  1016  }
  1017  
  1018  // ItemsPatterns returns all the patterns found in simple array items
  1019  // the map is cloned to avoid accidental changes
  1020  func (s *Spec) ItemsPatterns() map[string]string {
  1021  	return cloneStringMap(s.patterns.items)
  1022  }
  1023  
  1024  // SchemaPatterns returns all the patterns found in schemas
  1025  // the map is cloned to avoid accidental changes
  1026  func (s *Spec) SchemaPatterns() map[string]string {
  1027  	return cloneStringMap(s.patterns.schemas)
  1028  }
  1029  
  1030  // AllPatterns returns all the patterns found in the spec
  1031  // the map is cloned to avoid accidental changes
  1032  func (s *Spec) AllPatterns() map[string]string {
  1033  	return cloneStringMap(s.patterns.allPatterns)
  1034  }
  1035  
  1036  // ParameterEnums returns all the enums found in parameters
  1037  // the map is cloned to avoid accidental changes
  1038  func (s *Spec) ParameterEnums() map[string][]interface{} {
  1039  	return cloneEnumMap(s.enums.parameters)
  1040  }
  1041  
  1042  // HeaderEnums returns all the enums found in response headers
  1043  // the map is cloned to avoid accidental changes
  1044  func (s *Spec) HeaderEnums() map[string][]interface{} {
  1045  	return cloneEnumMap(s.enums.headers)
  1046  }
  1047  
  1048  // ItemsEnums returns all the enums found in simple array items
  1049  // the map is cloned to avoid accidental changes
  1050  func (s *Spec) ItemsEnums() map[string][]interface{} {
  1051  	return cloneEnumMap(s.enums.items)
  1052  }
  1053  
  1054  // SchemaEnums returns all the enums found in schemas
  1055  // the map is cloned to avoid accidental changes
  1056  func (s *Spec) SchemaEnums() map[string][]interface{} {
  1057  	return cloneEnumMap(s.enums.schemas)
  1058  }
  1059  
  1060  // AllEnums returns all the enums found in the spec
  1061  // the map is cloned to avoid accidental changes
  1062  func (s *Spec) AllEnums() map[string][]interface{} {
  1063  	return cloneEnumMap(s.enums.allEnums)
  1064  }
  1065  

View as plain text