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 validate
16
17import (
18	"encoding/json"
19	"fmt"
20	"sort"
21	"strings"
22
23	"github.com/go-openapi/analysis"
24	"github.com/go-openapi/errors"
25	"github.com/go-openapi/jsonpointer"
26	"github.com/go-openapi/loads"
27	"github.com/go-openapi/spec"
28	"github.com/go-openapi/strfmt"
29)
30
31// Spec validates an OpenAPI 2.0 specification document.
32//
33// Returns an error flattening in a single standard error, all validation messages.
34//
35//  - TODO: $ref should not have siblings
36//  - TODO: make sure documentation reflects all checks and warnings
37//  - TODO: check on discriminators
38//  - TODO: explicit message on unsupported keywords (better than "forbidden property"...)
39//  - TODO: full list of unresolved refs
40//  - TODO: validate numeric constraints (issue#581): this should be handled like defaults and examples
41//  - TODO: option to determine if we validate for go-swagger or in a more general context
42//  - TODO: check on required properties to support anyOf, allOf, oneOf
43//
44// NOTE: SecurityScopes are maps: no need to check uniqueness
45//
46func Spec(doc *loads.Document, formats strfmt.Registry) error {
47	errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc)
48	if errs.HasErrors() {
49		return errors.CompositeValidationError(errs.Errors...)
50	}
51	return nil
52}
53
54// SpecValidator validates a swagger 2.0 spec
55type SpecValidator struct {
56	schema       *spec.Schema // swagger 2.0 schema
57	spec         *loads.Document
58	analyzer     *analysis.Spec
59	expanded     *loads.Document
60	KnownFormats strfmt.Registry
61	Options      Opts // validation options
62}
63
64// NewSpecValidator creates a new swagger spec validator instance
65func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator {
66	return &SpecValidator{
67		schema:       schema,
68		KnownFormats: formats,
69		Options:      defaultOpts,
70	}
71}
72
73// Validate validates the swagger spec
74func (s *SpecValidator) Validate(data interface{}) (*Result, *Result) {
75	var sd *loads.Document
76	errs, warnings := new(Result), new(Result)
77
78	if v, ok := data.(*loads.Document); ok {
79		sd = v
80	}
81	if sd == nil {
82		errs.AddErrors(invalidDocumentMsg())
83		return errs, warnings // no point in continuing
84	}
85	s.spec = sd
86	s.analyzer = analysis.New(sd.Spec())
87
88	// Swagger schema validator
89	schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats, SwaggerSchema(true))
90	var obj interface{}
91
92	// Raw spec unmarshalling errors
93	if err := json.Unmarshal(sd.Raw(), &obj); err != nil {
94		// NOTE: under normal conditions, the *load.Document has been already unmarshalled
95		// So this one is just a paranoid check on the behavior of the spec package
96		panic(InvalidDocumentError)
97	}
98
99	defer func() {
100		// errs holds all errors and warnings,
101		// warnings only warnings
102		errs.MergeAsWarnings(warnings)
103		warnings.AddErrors(errs.Warnings...)
104	}()
105
106	errs.Merge(schv.Validate(obj)) // error -
107	// There may be a point in continuing to try and determine more accurate errors
108	if !s.Options.ContinueOnErrors && errs.HasErrors() {
109		return errs, warnings // no point in continuing
110	}
111
112	errs.Merge(s.validateReferencesValid()) // error -
113	// There may be a point in continuing to try and determine more accurate errors
114	if !s.Options.ContinueOnErrors && errs.HasErrors() {
115		return errs, warnings // no point in continuing
116	}
117
118	errs.Merge(s.validateDuplicateOperationIDs())
119	errs.Merge(s.validateDuplicatePropertyNames()) // error -
120	errs.Merge(s.validateParameters())             // error -
121	errs.Merge(s.validateItems())                  // error -
122
123	// Properties in required definition MUST validate their schema
124	// Properties SHOULD NOT be declared as both required and readOnly (warning)
125	errs.Merge(s.validateRequiredDefinitions()) // error and warning
126
127	// There may be a point in continuing to try and determine more accurate errors
128	if !s.Options.ContinueOnErrors && errs.HasErrors() {
129		return errs, warnings // no point in continuing
130	}
131
132	// Values provided as default MUST validate their schema
133	df := &defaultValidator{SpecValidator: s}
134	errs.Merge(df.Validate())
135
136	// Values provided as examples MUST validate their schema
137	// Value provided as examples in a response without schema generate a warning
138	// Known limitations: examples in responses for mime type not application/json are ignored (warning)
139	ex := &exampleValidator{SpecValidator: s}
140	errs.Merge(ex.Validate())
141
142	errs.Merge(s.validateNonEmptyPathParamNames())
143
144	// errs.Merge(s.validateRefNoSibling()) // warning only
145	errs.Merge(s.validateReferenced()) // warning only
146
147	return errs, warnings
148}
149
150func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
151	res := new(Result)
152	if s.spec.Spec().Paths == nil {
153		// There is no Paths object: error
154		res.AddErrors(noValidPathMsg())
155	} else {
156		if s.spec.Spec().Paths.Paths == nil {
157			// Paths may be empty: warning
158			res.AddWarnings(noValidPathMsg())
159		} else {
160			for k := range s.spec.Spec().Paths.Paths {
161				if strings.Contains(k, "{}") {
162					res.AddErrors(emptyPathParameterMsg(k))
163				}
164			}
165		}
166	}
167	return res
168}
169
170func (s *SpecValidator) validateDuplicateOperationIDs() *Result {
171	// OperationID, if specified, must be unique across the board
172	var analyzer *analysis.Spec
173	if s.expanded != nil {
174		// $ref are valid: we can analyze operations on an expanded spec
175		analyzer = analysis.New(s.expanded.Spec())
176	} else {
177		// fallback on possible incomplete picture because of previous errors
178		analyzer = s.analyzer
179	}
180	res := new(Result)
181	known := make(map[string]int)
182	for _, v := range analyzer.OperationIDs() {
183		if v != "" {
184			known[v]++
185		}
186	}
187	for k, v := range known {
188		if v > 1 {
189			res.AddErrors(nonUniqueOperationIDMsg(k, v))
190		}
191	}
192	return res
193}
194
195type dupProp struct {
196	Name       string
197	Definition string
198}
199
200func (s *SpecValidator) validateDuplicatePropertyNames() *Result {
201	// definition can't declare a property that's already defined by one of its ancestors
202	res := new(Result)
203	for k, sch := range s.spec.Spec().Definitions {
204		if len(sch.AllOf) == 0 {
205			continue
206		}
207
208		knownanc := map[string]struct{}{
209			"#/definitions/" + k: {},
210		}
211
212		ancs, rec := s.validateCircularAncestry(k, sch, knownanc)
213		if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
214			res.Merge(rec)
215		}
216		if len(ancs) > 0 {
217			res.AddErrors(circularAncestryDefinitionMsg(k, ancs))
218			return res
219		}
220
221		knowns := make(map[string]struct{})
222		dups, rep := s.validateSchemaPropertyNames(k, sch, knowns)
223		if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
224			res.Merge(rep)
225		}
226		if len(dups) > 0 {
227			var pns []string
228			for _, v := range dups {
229				pns = append(pns, v.Definition+"."+v.Name)
230			}
231			res.AddErrors(duplicatePropertiesMsg(k, pns))
232		}
233
234	}
235	return res
236}
237
238func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) {
239	if s.spec.SpecFilePath() != "" {
240		return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()})
241	}
242	// NOTE: it looks like with the new spec resolver, this code is now unrecheable
243	return spec.ResolveRef(s.spec.Spec(), ref)
244}
245
246func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) ([]dupProp, *Result) {
247	var dups []dupProp
248
249	schn := nm
250	schc := &sch
251	res := new(Result)
252
253	for schc.Ref.String() != "" {
254		// gather property names
255		reso, err := s.resolveRef(&schc.Ref)
256		if err != nil {
257			errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
258			return dups, res
259		}
260		schc = reso
261		schn = sch.Ref.String()
262	}
263
264	if len(schc.AllOf) > 0 {
265		for _, chld := range schc.AllOf {
266			dup, rep := s.validateSchemaPropertyNames(schn, chld, knowns)
267			if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
268				res.Merge(rep)
269			}
270			dups = append(dups, dup...)
271		}
272		return dups, res
273	}
274
275	for k := range schc.Properties {
276		_, ok := knowns[k]
277		if ok {
278			dups = append(dups, dupProp{Name: k, Definition: schn})
279		} else {
280			knowns[k] = struct{}{}
281		}
282	}
283
284	return dups, res
285}
286
287func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) {
288	res := new(Result)
289
290	if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there
291		return nil, res
292	}
293	var ancs []string
294
295	schn := nm
296	schc := &sch
297
298	for schc.Ref.String() != "" {
299		reso, err := s.resolveRef(&schc.Ref)
300		if err != nil {
301			errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
302			return ancs, res
303		}
304		schc = reso
305		schn = sch.Ref.String()
306	}
307
308	if schn != nm && schn != "" {
309		if _, ok := knowns[schn]; ok {
310			ancs = append(ancs, schn)
311		}
312		knowns[schn] = struct{}{}
313
314		if len(ancs) > 0 {
315			return ancs, res
316		}
317	}
318
319	if len(schc.AllOf) > 0 {
320		for _, chld := range schc.AllOf {
321			if chld.Ref.String() != "" || len(chld.AllOf) > 0 {
322				anc, rec := s.validateCircularAncestry(schn, chld, knowns)
323				if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
324					res.Merge(rec)
325				}
326				ancs = append(ancs, anc...)
327				if len(ancs) > 0 {
328					return ancs, res
329				}
330			}
331		}
332	}
333	return ancs, res
334}
335
336func (s *SpecValidator) validateItems() *Result {
337	// validate parameter, items, schema and response objects for presence of item if type is array
338	res := new(Result)
339
340	for method, pi := range s.analyzer.Operations() {
341		for path, op := range pi {
342			for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
343
344				if param.TypeName() == arrayType && param.ItemsTypeName() == "" {
345					res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
346					continue
347				}
348				if param.In != swaggerBody {
349					if param.Items != nil {
350						items := param.Items
351						for items.TypeName() == arrayType {
352							if items.ItemsTypeName() == "" {
353								res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
354								break
355							}
356							items = items.Items
357						}
358					}
359				} else {
360					// In: body
361					if param.Schema != nil {
362						res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID))
363					}
364				}
365			}
366
367			var responses []spec.Response
368			if op.Responses != nil {
369				if op.Responses.Default != nil {
370					responses = append(responses, *op.Responses.Default)
371				}
372				if op.Responses.StatusCodeResponses != nil {
373					for _, v := range op.Responses.StatusCodeResponses {
374						responses = append(responses, v)
375					}
376				}
377			}
378
379			for _, resp := range responses {
380				// Response headers with array
381				for hn, hv := range resp.Headers {
382					if hv.TypeName() == arrayType && hv.ItemsTypeName() == "" {
383						res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID))
384					}
385				}
386				if resp.Schema != nil {
387					res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID))
388				}
389			}
390		}
391	}
392	return res
393}
394
395// Verifies constraints on array type
396func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result {
397	res := new(Result)
398	if !schema.Type.Contains(arrayType) {
399		return res
400	}
401
402	if schema.Items == nil || schema.Items.Len() == 0 {
403		res.AddErrors(arrayRequiresItemsMsg(prefix, opID))
404		return res
405	}
406
407	if schema.Items.Schema != nil {
408		schema = *schema.Items.Schema
409		if _, err := compileRegexp(schema.Pattern); err != nil {
410			res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern))
411		}
412
413		res.Merge(s.validateSchemaItems(schema, prefix, opID))
414	}
415	return res
416}
417
418func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result {
419	// Each defined operation path parameters must correspond to a named element in the API's path pattern.
420	// (For example, you cannot have a path parameter named id for the following path /pets/{petId} but you must have a path parameter named petId.)
421	res := new(Result)
422	for _, l := range fromPath {
423		var matched bool
424		for _, r := range fromOperation {
425			if l == "{"+r+"}" {
426				matched = true
427				break
428			}
429		}
430		if !matched {
431			res.AddErrors(noParameterInPathMsg(l))
432		}
433	}
434
435	for _, p := range fromOperation {
436		var matched bool
437		for _, r := range fromPath {
438			if "{"+p+"}" == r {
439				matched = true
440				break
441			}
442		}
443		if !matched {
444			res.AddErrors(pathParamNotInPathMsg(path, p))
445		}
446	}
447
448	return res
449}
450
451func (s *SpecValidator) validateReferenced() *Result {
452	var res Result
453	res.MergeAsWarnings(s.validateReferencedParameters())
454	res.MergeAsWarnings(s.validateReferencedResponses())
455	res.MergeAsWarnings(s.validateReferencedDefinitions())
456	return &res
457}
458
459// nolint: dupl
460func (s *SpecValidator) validateReferencedParameters() *Result {
461	// Each referenceable definition should have references.
462	params := s.spec.Spec().Parameters
463	if len(params) == 0 {
464		return nil
465	}
466
467	expected := make(map[string]struct{})
468	for k := range params {
469		expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{}
470	}
471	for _, k := range s.analyzer.AllParameterReferences() {
472		delete(expected, k)
473	}
474
475	if len(expected) == 0 {
476		return nil
477	}
478	result := new(Result)
479	for k := range expected {
480		result.AddWarnings(unusedParamMsg(k))
481	}
482	return result
483}
484
485// nolint: dupl
486func (s *SpecValidator) validateReferencedResponses() *Result {
487	// Each referenceable definition should have references.
488	responses := s.spec.Spec().Responses
489	if len(responses) == 0 {
490		return nil
491	}
492
493	expected := make(map[string]struct{})
494	for k := range responses {
495		expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{}
496	}
497	for _, k := range s.analyzer.AllResponseReferences() {
498		delete(expected, k)
499	}
500
501	if len(expected) == 0 {
502		return nil
503	}
504	result := new(Result)
505	for k := range expected {
506		result.AddWarnings(unusedResponseMsg(k))
507	}
508	return result
509}
510
511// nolint: dupl
512func (s *SpecValidator) validateReferencedDefinitions() *Result {
513	// Each referenceable definition must have references.
514	defs := s.spec.Spec().Definitions
515	if len(defs) == 0 {
516		return nil
517	}
518
519	expected := make(map[string]struct{})
520	for k := range defs {
521		expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{}
522	}
523	for _, k := range s.analyzer.AllDefinitionReferences() {
524		delete(expected, k)
525	}
526
527	if len(expected) == 0 {
528		return nil
529	}
530
531	result := new(Result)
532	for k := range expected {
533		result.AddWarnings(unusedDefinitionMsg(k))
534	}
535	return result
536}
537
538func (s *SpecValidator) validateRequiredDefinitions() *Result {
539	// Each property listed in the required array must be defined in the properties of the model
540	res := new(Result)
541
542DEFINITIONS:
543	for d, schema := range s.spec.Spec().Definitions {
544		if schema.Required != nil { // Safeguard
545			for _, pn := range schema.Required {
546				red := s.validateRequiredProperties(pn, d, &schema) //#nosec
547				res.Merge(red)
548				if !red.IsValid() && !s.Options.ContinueOnErrors {
549					break DEFINITIONS // there is an error, let's stop that bleeding
550				}
551			}
552		}
553	}
554	return res
555}
556
557func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result {
558	// Takes care of recursive property definitions, which may be nested in additionalProperties schemas
559	res := new(Result)
560	propertyMatch := false
561	patternMatch := false
562	additionalPropertiesMatch := false
563	isReadOnly := false
564
565	// Regular properties
566	if _, ok := v.Properties[path]; ok {
567		propertyMatch = true
568		isReadOnly = v.Properties[path].ReadOnly
569	}
570
571	// NOTE: patternProperties are not supported in swagger. Even though, we continue validation here
572	// We check all defined patterns: if one regexp is invalid, croaks an error
573	for pp, pv := range v.PatternProperties {
574		re, err := compileRegexp(pp)
575		if err != nil {
576			res.AddErrors(invalidPatternMsg(pp, in))
577		} else if re.MatchString(path) {
578			patternMatch = true
579			if !propertyMatch {
580				isReadOnly = pv.ReadOnly
581			}
582		}
583	}
584
585	if !(propertyMatch || patternMatch) {
586		if v.AdditionalProperties != nil {
587			if v.AdditionalProperties.Allows && v.AdditionalProperties.Schema == nil {
588				additionalPropertiesMatch = true
589			} else if v.AdditionalProperties.Schema != nil {
590				// additionalProperties as schema are upported in swagger
591				// recursively validates additionalProperties schema
592				// TODO : anyOf, allOf, oneOf like in schemaPropsValidator
593				red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema)
594				if red.IsValid() {
595					additionalPropertiesMatch = true
596					if !propertyMatch && !patternMatch {
597						isReadOnly = v.AdditionalProperties.Schema.ReadOnly
598					}
599				}
600				res.Merge(red)
601			}
602		}
603	}
604
605	if !(propertyMatch || patternMatch || additionalPropertiesMatch) {
606		res.AddErrors(requiredButNotDefinedMsg(path, in))
607	}
608
609	if isReadOnly {
610		res.AddWarnings(readOnlyAndRequiredMsg(in, path))
611	}
612	return res
613}
614
615func (s *SpecValidator) validateParameters() *Result {
616	// - for each method, path is unique, regardless of path parameters
617	//   e.g. GET:/petstore/{id}, GET:/petstore/{pet}, GET:/petstore are
618	//   considered duplicate paths
619	// - each parameter should have a unique `name` and `type` combination
620	// - each operation should have only 1 parameter of type body
621	// - there must be at most 1 parameter in body
622	// - parameters with pattern property must specify valid patterns
623	// - $ref in parameters must resolve
624	// - path param must be required
625	res := new(Result)
626	rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`)
627	for method, pi := range s.analyzer.Operations() {
628		methodPaths := make(map[string]map[string]string)
629		for path, op := range pi {
630			pathToAdd := pathHelp.stripParametersInPath(path)
631
632			// Warn on garbled path afer param stripping
633			if rexGarbledPathSegment.MatchString(pathToAdd) {
634				res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd))
635			}
636
637			// Check uniqueness of stripped paths
638			if _, found := methodPaths[method][pathToAdd]; found {
639
640				// Sort names for stable, testable output
641				if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 {
642					res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd]))
643				} else {
644					res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path))
645				}
646			} else {
647				if _, found := methodPaths[method]; !found {
648					methodPaths[method] = map[string]string{}
649				}
650				methodPaths[method][pathToAdd] = path // Original non stripped path
651
652			}
653
654			var bodyParams []string
655			var paramNames []string
656			var hasForm, hasBody bool
657
658			// Check parameters names uniqueness for operation
659			// TODO: should be done after param expansion
660			res.Merge(s.checkUniqueParams(path, method, op))
661
662			for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
663				// Validate pattern regexp for parameters with a Pattern property
664				if _, err := compileRegexp(pr.Pattern); err != nil {
665					res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern))
666				}
667
668				// There must be at most one parameter in body: list them all
669				if pr.In == swaggerBody {
670					bodyParams = append(bodyParams, fmt.Sprintf("%q", pr.Name))
671					hasBody = true
672				}
673
674				if pr.In == "path" {
675					paramNames = append(paramNames, pr.Name)
676					// Path declared in path must have the required: true property
677					if !pr.Required {
678						res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name))
679					}
680				}
681
682				if pr.In == "formData" {
683					hasForm = true
684				}
685
686				if !(pr.Type == numberType || pr.Type == integerType) &&
687					(pr.Maximum != nil || pr.Minimum != nil || pr.MultipleOf != nil) {
688					// A non-numeric parameter has validation keywords for numeric instances (number and integer)
689					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
690				}
691
692				if !(pr.Type == stringType) &&
693					// A non-string parameter has validation keywords for strings
694					(pr.MaxLength != nil || pr.MinLength != nil || pr.Pattern != "") {
695					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
696				}
697
698				if !(pr.Type == arrayType) &&
699					// A non-array parameter has validation keywords for arrays
700					(pr.MaxItems != nil || pr.MinItems != nil || pr.UniqueItems) {
701					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
702				}
703			}
704
705			// In:formData and In:body are mutually exclusive
706			if hasBody && hasForm {
707				res.AddErrors(bothFormDataAndBodyMsg(op.ID))
708			}
709			// There must be at most one body param
710			// Accurately report situations when more than 1 body param is declared (possibly unnamed)
711			if len(bodyParams) > 1 {
712				sort.Strings(bodyParams)
713				res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams))
714			}
715
716			// Check uniqueness of parameters in path
717			paramsInPath := pathHelp.extractPathParams(path)
718			for i, p := range paramsInPath {
719				for j, q := range paramsInPath {
720					if p == q && i > j {
721						res.AddErrors(pathParamNotUniqueMsg(path, p, q))
722						break
723					}
724				}
725			}
726
727			// Warns about possible malformed params in path
728			rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`)
729			for _, p := range paramsInPath {
730				if rexGarbledParam.MatchString(p) {
731					res.AddWarnings(pathParamGarbledMsg(path, p))
732				}
733			}
734
735			// Match params from path vs params from params section
736			res.Merge(s.validatePathParamPresence(path, paramsInPath, paramNames))
737		}
738	}
739	return res
740}
741
742func (s *SpecValidator) validateReferencesValid() *Result {
743	// each reference must point to a valid object
744	res := new(Result)
745	for _, r := range s.analyzer.AllRefs() {
746		if !r.IsValidURI(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI
747			res.AddErrors(invalidRefMsg(r.String()))
748		}
749	}
750	if !res.HasErrors() {
751		// NOTE: with default settings, loads.Document.Expanded()
752		// stops on first error. Anyhow, the expand option to continue
753		// on errors fails to report errors at all.
754		exp, err := s.spec.Expanded()
755		if err != nil {
756			res.AddErrors(unresolvedReferencesMsg(err))
757		}
758		s.expanded = exp
759	}
760	return res
761}
762
763func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result {
764	// Check for duplicate parameters declaration in param section.
765	// Each parameter should have a unique `name` and `type` combination
766	// NOTE: this could be factorized in analysis (when constructing the params map)
767	// However, there are some issues with such a factorization:
768	// - analysis does not seem to fully expand params
769	// - param keys may be altered by x-go-name
770	res := new(Result)
771	pnames := make(map[string]struct{})
772
773	if op.Parameters != nil { // Safeguard
774		for _, ppr := range op.Parameters {
775			var ok bool
776			pr, red := paramHelp.resolveParam(path, method, op.ID, &ppr, s) //#nosec
777			res.Merge(red)
778
779			if pr != nil && pr.Name != "" { // params with empty name does no participate the check
780				key := fmt.Sprintf("%s#%s", pr.In, pr.Name)
781
782				if _, ok = pnames[key]; ok {
783					res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID))
784				}
785				pnames[key] = struct{}{}
786			}
787		}
788	}
789	return res
790}
791
792// SetContinueOnErrors sets the ContinueOnErrors option for this validator.
793func (s *SpecValidator) SetContinueOnErrors(c bool) {
794	s.Options.ContinueOnErrors = c
795}
796