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{}) (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} 778