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