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