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