From 8bb75d911de919162ef1b99ba52cadff4c763965 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:12:39 +0000 Subject: [PATCH] chore(deps): Bump github.com/go-openapi/validate from 0.26.1 to 0.26.3 Bumps [github.com/go-openapi/validate](https://github.com/go-openapi/validate) from 0.26.1 to 0.26.3. - [Release notes](https://github.com/go-openapi/validate/releases) - [Commits](https://github.com/go-openapi/validate/compare/v0.26.1...v0.26.3) --- updated-dependencies: - dependency-name: github.com/go-openapi/validate dependency-version: 0.26.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 14 +- go.sum | 28 +- .../go-openapi/analysis/CONTRIBUTORS.md | 4 +- .../go-openapi/analysis/analyzer.go | 21 + .../github.com/go-openapi/validate/.gitignore | 1 + .../go-openapi/validate/.golangci.yml | 2 + .../go-openapi/validate/CONTRIBUTORS.md | 5 +- .../go-openapi/validate/default_validator.go | 146 ++- .../go-openapi/validate/example_validator.go | 131 ++- .../github.com/go-openapi/validate/formats.go | 22 +- .../github.com/go-openapi/validate/helpers.go | 133 ++- .../go-openapi/validate/object_validator.go | 100 +- .../go-openapi/validate/param_locations.go | 122 ++ vendor/github.com/go-openapi/validate/path.go | 269 +++++ .../github.com/go-openapi/validate/pools.go | 420 ++----- .../go-openapi/validate/pools_debug.go | 1015 ----------------- .../go-openapi/validate/ref_locations.go | 49 + .../go-openapi/validate/ref_redirects.go | 71 ++ .../go-openapi/validate/required_walk.go | 179 +++ .../go-openapi/validate/resolvable.go | 70 ++ .../github.com/go-openapi/validate/result.go | 254 ++++- .../github.com/go-openapi/validate/schema.go | 72 +- .../go-openapi/validate/schema_props.go | 60 +- .../go-openapi/validate/slice_validator.go | 43 +- .../github.com/go-openapi/validate/sorted.go | 39 + vendor/github.com/go-openapi/validate/spec.go | 339 ++++-- .../go-openapi/validate/spec_messages.go | 7 + .../go-openapi/validate/spec_ref_warnings.go | 6 +- vendor/github.com/go-openapi/validate/type.go | 22 +- .../go-openapi/validate/validator.go | 201 ++-- vendor/go.yaml.in/yaml/v3/parserc.go | 178 ++- vendor/go.yaml.in/yaml/v3/yamlh.go | 44 +- vendor/modules.txt | 14 +- 33 files changed, 2011 insertions(+), 2070 deletions(-) create mode 100644 vendor/github.com/go-openapi/validate/param_locations.go create mode 100644 vendor/github.com/go-openapi/validate/path.go delete mode 100644 vendor/github.com/go-openapi/validate/pools_debug.go create mode 100644 vendor/github.com/go-openapi/validate/ref_locations.go create mode 100644 vendor/github.com/go-openapi/validate/ref_redirects.go create mode 100644 vendor/github.com/go-openapi/validate/required_walk.go create mode 100644 vendor/github.com/go-openapi/validate/resolvable.go create mode 100644 vendor/github.com/go-openapi/validate/sorted.go diff --git a/go.mod b/go.mod index eec8a21..3d4ebd5 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/go-openapi/swag/jsonutils v0.28.0 github.com/go-openapi/swag/stringutils v0.28.0 github.com/go-openapi/swag/typeutils v0.28.0 - github.com/go-openapi/validate v0.26.1 + github.com/go-openapi/validate v0.26.3 github.com/obalunenko/getenv v1.15.0 github.com/stretchr/testify v1.11.1 ) @@ -20,17 +20,17 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.25.5 // indirect + github.com/go-openapi/analysis v0.26.0 // indirect github.com/go-openapi/jsonpointer v1.0.0 // indirect github.com/go-openapi/jsonreference v1.0.0 // indirect github.com/go-openapi/loads v0.25.0 // indirect github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect github.com/go-openapi/spec v0.22.9 // indirect - github.com/go-openapi/swag/fileutils v0.27.3 // indirect - github.com/go-openapi/swag/loading v0.27.3 // indirect - github.com/go-openapi/swag/mangling v0.27.3 // indirect + github.com/go-openapi/swag/fileutils v0.28.0 // indirect + github.com/go-openapi/swag/loading v0.28.0 // indirect + github.com/go-openapi/swag/mangling v0.28.0 // indirect github.com/go-openapi/swag/pools v0.28.0 // indirect - github.com/go-openapi/swag/yamlutils v0.27.3 // indirect + github.com/go-openapi/swag/yamlutils v0.28.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/oklog/ulid/v2 v2.1.1 // indirect @@ -39,7 +39,7 @@ require ( go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/text v0.40.0 // indirect diff --git a/go.sum b/go.sum index e77265e..227be7c 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/analysis v0.25.5 h1:xPYEvTb90o1y0epuiOPAoG4QqahjP3cdp5xNlHeKJRI= -github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc= +github.com/go-openapi/analysis v0.26.0 h1:1xECln1iMMmQnTjgcknC1vi1hA4KISt6IHpSwnqcuwI= +github.com/go-openapi/analysis v0.26.0/go.mod h1:40gERFi/2dyXA1FaqRRLxkv1IlC6X+GPDNd1xrYAjZE= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= @@ -27,30 +27,30 @@ github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E8 github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= -github.com/go-openapi/swag/fileutils v0.27.3 h1:3UVoZ2RLaIs1lt+2jcKzL8RM3Yk0rmsDE9FLA/HGxFE= -github.com/go-openapi/swag/fileutils v0.27.3/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU= +github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0= -github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE= -github.com/go-openapi/swag/mangling v0.27.3 h1:gRzzD1PAUoLTtGMgI3KpBmCSOlTuLTFWnviLxLcTnyg= -github.com/go-openapi/swag/mangling v0.27.3/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= +github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM= +github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs= -github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU= +github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/go-openapi/validate v0.26.1 h1:pZSbvtRO8G2R2FpWTYRn3w8LrsNwbtaVhP2dWiBa0Us= -github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo= +github.com/go-openapi/validate v0.26.3 h1:OkfZgLvLDnGP2hrRGD+42WBiPWWkoHomTJ+IVI+KaDc= +github.com/go-openapi/validate v0.26.3/go.mod h1:7DOOa4raU6NRe7A8VQSKbm3VcuUIioREYHFt+er9Sk8= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -82,8 +82,8 @@ go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYr go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= diff --git a/vendor/github.com/go-openapi/analysis/CONTRIBUTORS.md b/vendor/github.com/go-openapi/analysis/CONTRIBUTORS.md index a89274c..89eb5b0 100644 --- a/vendor/github.com/go-openapi/analysis/CONTRIBUTORS.md +++ b/vendor/github.com/go-openapi/analysis/CONTRIBUTORS.md @@ -4,11 +4,11 @@ | Total Contributors | Total Contributions | | --- | --- | -| 22 | 273 | +| 22 | 276 | | Username | All Time Contribution Count | All Commits | | --- | --- | --- | -| @fredbi | 133 | | +| @fredbi | 136 | | | @casualjim | 91 | | | @keramix | 9 | | | @youyuanwu | 8 | | diff --git a/vendor/github.com/go-openapi/analysis/analyzer.go b/vendor/github.com/go-openapi/analysis/analyzer.go index c24811a..9eab8bd 100644 --- a/vendor/github.com/go-openapi/analysis/analyzer.go +++ b/vendor/github.com/go-openapi/analysis/analyzer.go @@ -585,6 +585,20 @@ func (s *Spec) AllRefs() (result []spec.Ref) { return } +// AllRefsByLocation returns all the references found in the document, keyed by +// where each one is declared. +// +// Keys are local JSON references into the analyzed document, with tokens +// escaped as per RFC 6901, e.g. "#/paths/~1pets/get/responses/200/schema". +// +// Unlike [Spec.AllRefs], the result is not deduplicated: the same reference +// declared in several places appears under each of its locations. +// +// The map is cloned to avoid accidental changes. +func (s *Spec) AllRefsByLocation() map[string]spec.Ref { + return cloneRefMap(s.references.allRefs) +} + // ParameterPatterns returns all the patterns found in parameters // the map is cloned to avoid accidental changes. func (s *Spec) ParameterPatterns() map[string]string { @@ -1053,6 +1067,13 @@ func cloneStringMap(source map[string]string) map[string]string { return res } +func cloneRefMap(source map[string]spec.Ref) map[string]spec.Ref { + res := make(map[string]spec.Ref, len(source)) + maps.Copy(res, source) + + return res +} + func cloneEnumMap(source map[string][]any) map[string][]any { res := make(map[string][]any, len(source)) maps.Copy(res, source) diff --git a/vendor/github.com/go-openapi/validate/.gitignore b/vendor/github.com/go-openapi/validate/.gitignore index d8f4186..fbb78de 100644 --- a/vendor/github.com/go-openapi/validate/.gitignore +++ b/vendor/github.com/go-openapi/validate/.gitignore @@ -3,3 +3,4 @@ .idea .env .mcp.json +.worktrees diff --git a/vendor/github.com/go-openapi/validate/.golangci.yml b/vendor/github.com/go-openapi/validate/.golangci.yml index 4d6b36e..b8875d7 100644 --- a/vendor/github.com/go-openapi/validate/.golangci.yml +++ b/vendor/github.com/go-openapi/validate/.golangci.yml @@ -7,6 +7,8 @@ linters: - gochecknoglobals - gochecknoinits - godox + - gomodguard + - gomodguard_v2 - exhaustruct - ireturn - nlreturn diff --git a/vendor/github.com/go-openapi/validate/CONTRIBUTORS.md b/vendor/github.com/go-openapi/validate/CONTRIBUTORS.md index 40d877a..62bd851 100644 --- a/vendor/github.com/go-openapi/validate/CONTRIBUTORS.md +++ b/vendor/github.com/go-openapi/validate/CONTRIBUTORS.md @@ -4,12 +4,12 @@ | Total Contributors | Total Contributions | | --- | --- | -| 31 | 310 | +| 32 | 312 | | Username | All Time Contribution Count | All Commits | | --- | --- | --- | | @casualjim | 169 | | -| @fredbi | 73 | | +| @fredbi | 74 | | | @sttts | 11 | | | @youyuanwu | 9 | | | @keramix | 8 | | @@ -32,6 +32,7 @@ | @liggitt | 1 | | | @ilyakaznacheev | 1 | | | @hypnoglow | 1 | | +| @HNO3Miracle | 1 | | | @gautierdelorme | 1 | | | @flavioribeiro | 1 | | | @pheepi | 1 | | diff --git a/vendor/github.com/go-openapi/validate/default_validator.go b/vendor/github.com/go-openapi/validate/default_validator.go index ebcd807..5902cee 100644 --- a/vendor/github.com/go-openapi/validate/default_validator.go +++ b/vendor/github.com/go-openapi/validate/default_validator.go @@ -4,9 +4,6 @@ package validate import ( - "fmt" - "strings" - "github.com/go-openapi/spec" ) @@ -20,7 +17,7 @@ type defaultValidator struct { // Validate validates the default values declared in the swagger spec. func (d *defaultValidator) Validate() *Result { - errs := pools.poolOfResults.BorrowResult() // will redeem when merged + errs := validatorPools.results.Borrow() // will redeem when merged if d == nil || d.SpecValidator == nil { return errs @@ -44,28 +41,16 @@ func (d *defaultValidator) resetVisited() { } } -func isVisited(path string, visitedSchemas map[string]struct{}) bool { - _, found := visitedSchemas[path] +func isVisited(path pathSegments, visitedSchemas map[string]struct{}) bool { + _, found := visitedSchemas[path.pointer()] if found { return true } - // search for overlapping paths - var ( - parent string - suffix string - ) - const backtrackFromEnd = 2 - for i := len(path) - backtrackFromEnd; i >= 0; i-- { - r := path[i] - if r != '.' { - continue - } - - parent = path[0:i] - suffix = path[i+1:] - - if strings.HasSuffix(parent, suffix) { + // search for overlapping paths: a trailing run of tokens that already + // appears at the end of what leads to it means we are going in circles. + for i := 1; i < len(path); i++ { + if path[:i].hasSuffix(path[i:]) { return true } } @@ -74,12 +59,12 @@ func isVisited(path string, visitedSchemas map[string]struct{}) bool { } // beingVisited asserts a schema is being visited. -func (d *defaultValidator) beingVisited(path string) { - d.visitedSchemas[path] = struct{}{} +func (d *defaultValidator) beingVisited(path pathSegments) { + d.visitedSchemas[path.pointer()] = struct{}{} } // isVisited tells if a path has already been visited. -func (d *defaultValidator) isVisited(path string) bool { +func (d *defaultValidator) isVisited(path pathSegments) bool { return isVisited(path, d.visitedSchemas) } @@ -88,15 +73,18 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { // every default value that is specified must validate against the schema for that property // headers, items, parameters, schema - res := pools.poolOfResults.BorrowResult() // will redeem when merged + res := validatorPools.results.Borrow() // will redeem when merged s := d.SpecValidator - for method, pathItem := range s.expandedAnalyzer().Operations() { - for path, op := range pathItem { + operations := s.expandedAnalyzer().Operations() + for _, method := range sortedKeys(operations) { + pathItem := operations[method] + for _, path := range sortedKeys(pathItem) { + op := pathItem[path] // parameters for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { if param.Default != nil && param.Required { - res.AddWarnings(requiredHasDefaultMsg(param.Name, param.In)) + res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), requiredHasDefaultMsg(param.Name, param.In)) } // reset explored schemas to get depth-first recursive-proof exploration @@ -107,33 +95,34 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { if param.Default != nil && param.Schema == nil { // check param default value is valid red := newParamValidator(¶m, s.KnownFormats, d.schemaOptions).Validate(param.Default) //#nosec + red.relocate(s.parameterPath(path, method, param.In, param.Name).child(jsonDefault)) if red.HasErrorsOrWarnings() { - res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In)) + res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } // Recursively follows Items and Schemas if param.Items != nil { - red := d.validateDefaultValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items) //#nosec + red := d.validateDefaultValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, ¶m, param.Items) //#nosec if red.HasErrorsOrWarnings() { - res.AddErrors(defaultValueItemsDoesNotValidateMsg(param.Name, param.In)) + res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueItemsDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } if param.Schema != nil { // Validate default value against schema - red := d.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema) + red := d.validateDefaultValueSchemaAgainstSchema(s.parameterPath(path, method, param.In, param.Name).structuralChild(jsonSchema), param.In, param.Schema) if red.HasErrorsOrWarnings() { - res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In)) + res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } } @@ -141,68 +130,74 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { if op.Responses != nil { if op.Responses.Default != nil { // Same constraint on default Response - res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID)) + res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, method, 0, op.ID)) } // Same constraint on regular Responses if op.Responses.StatusCodeResponses != nil { // Safeguard - for code, r := range op.Responses.StatusCodeResponses { - res.Merge(d.validateDefaultInResponse(&r, "response", path, code, op.ID)) //#nosec + for _, code := range sortedKeys(op.Responses.StatusCodeResponses) { + r := op.Responses.StatusCodeResponses[code] + res.Merge(d.validateDefaultInResponse(&r, "response", path, method, code, op.ID)) } } } else if op.ID != "" { // Empty op.ID means there is no meaningful operation: no need to report a specific message - res.AddErrors(noValidResponseMsg(op.ID)) + res.addErrorsAt(operationPath(path, method), noValidResponseMsg(op.ID)) } } } if s.spec.Spec().Definitions != nil { // Safeguard // reset explored schemas to get depth-first recursive-proof exploration d.resetVisited() - for nm, sch := range s.spec.Spec().Definitions { - res.Merge(d.validateDefaultValueSchemaAgainstSchema("definitions."+nm, "body", &sch)) //#nosec + definitions := s.spec.Spec().Definitions + for _, nm := range sortedKeys(definitions) { + sch := definitions[nm] + res.Merge(d.validateDefaultValueSchemaAgainstSchema(newPathSegments(swaggerDefinitions, nm), "body", &sch)) } } return res } -func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result { +func (d *defaultValidator) validateDefaultInResponse( + resp *spec.Response, responseType, path, method string, responseCode int, operationID string, +) *Result { s := d.SpecValidator - response, res := responseHelp.expandResponseRef(resp, path, s) + responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode) + response, res := responseHelp.expandResponseRef(resp, path, responsePath(path, method, responseCodeAsStr), s) if !res.IsValid() { return res } - responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode) - if response.Headers != nil { // Safeguard - for nm, h := range response.Headers { + for _, nm := range sortedKeys(response.Headers) { + h := response.Headers[nm] // reset explored schemas to get depth-first recursive-proof exploration d.resetVisited() if h.Default != nil { red := newHeaderValidator(nm, &h, s.KnownFormats, d.schemaOptions).Validate(h.Default) //#nosec + red.relocate(responseHeaderPath(path, method, responseCodeAsStr, nm).child(jsonDefault)) if red.HasErrorsOrWarnings() { - res.AddErrors(defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) + res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } // Headers have inline definition, like params if h.Items != nil { - red := d.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec + red := d.validateDefaultValueItemsAgainstSchema(responseHeaderPath(path, method, responseCodeAsStr, nm), "header", &h, h.Items) //#nosec if red.HasErrorsOrWarnings() { - res.AddErrors(defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) + res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } if _, err := compileRegexp(h.Pattern); err != nil { - res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err)) + res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err)) } // Headers don't have schema @@ -212,62 +207,65 @@ func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, respon // reset explored schemas to get depth-first recursive-proof exploration d.resetVisited() - red := d.validateDefaultValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema) + red := d.validateDefaultValueSchemaAgainstSchema( + responsePath(path, method, responseCodeAsStr).structuralChild(jsonSchema), "response", response.Schema) if red.HasErrorsOrWarnings() { // Additional message to make sure the context of the error is not lost - res.AddErrors(defaultValueInDoesNotValidateMsg(operationID, responseName)) + res.addErrorsAt(responsePath(path, method, responseCodeAsStr), defaultValueInDoesNotValidateMsg(operationID, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } return res } -func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result { +func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path pathSegments, in string, schema *spec.Schema) *Result { if schema == nil || d.isVisited(path) { // Avoids recursing if we are already done with that check return nil } d.beingVisited(path) - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() s := d.SpecValidator if schema.Default != nil { res.Merge( - newSchemaValidator(schema, s.spec.Spec(), path+".default", s.KnownFormats, d.schemaOptions).Validate(schema.Default), + newSchemaValidator(schema, s.spec.Spec(), path.child(jsonDefault), s.KnownFormats, d.schemaOptions).Validate(schema.Default), ) } if schema.Items != nil { if schema.Items.Schema != nil { - res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".items.default", in, schema.Items.Schema)) + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonItems), in, schema.Items.Schema)) } // Multiple schemas in items if schema.Items.Schemas != nil { // Safeguard for i, sch := range schema.Items.Schemas { - res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].default", path, i), in, &sch)) //#nosec + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonItems).item(i), in, &sch)) //#nosec } } } if _, err := compileRegexp(schema.Pattern); err != nil { - res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern)) + res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, schema.Pattern)) } if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { // NOTE: we keep validating values, even though additionalItems is not supported by Swagger 2.0 (and 3.0 as well) - res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalItems", in, schema.AdditionalItems.Schema)) + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAdditionalItems), in, schema.AdditionalItems.Schema)) } - for propName, prop := range schema.Properties { - res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec + for _, propName := range sortedKeys(schema.Properties) { + prop := schema.Properties[propName] + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.structuralChild(jsonProperties).child(propName), in, &prop)) } - for propName, prop := range schema.PatternProperties { - res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec + for _, propName := range sortedKeys(schema.PatternProperties) { + prop := schema.PatternProperties[propName] + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.structuralChild(jsonPatternProperties).child(propName), in, &prop)) } if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { - res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalProperties", in, schema.AdditionalProperties.Schema)) + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAdditionalProperties), in, schema.AdditionalProperties.Schema)) } if schema.AllOf != nil { for i, aoSch := range schema.AllOf { - res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec + res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAllOf).item(i), in, &aoSch)) //#nosec } } return res @@ -275,8 +273,8 @@ func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in stri // NOTE: Temporary duplicated code. Need to refactor with examples -func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in string, root any, items *spec.Items) *Result { - res := pools.poolOfResults.BorrowResult() +func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result { + res := validatorPools.results.Borrow() s := d.SpecValidator if items != nil { if items.Default != nil { @@ -285,10 +283,10 @@ func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in strin ) } if items.Items != nil { - res.Merge(d.validateDefaultValueItemsAgainstSchema(path+"[0].default", in, root, items.Items)) + res.Merge(d.validateDefaultValueItemsAgainstSchema(path.item(0), in, root, items.Items)) } if _, err := compileRegexp(items.Pattern); err != nil { - res.AddErrors(invalidPatternInMsg(path, in, items.Pattern)) + res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, items.Pattern)) } } return res diff --git a/vendor/github.com/go-openapi/validate/example_validator.go b/vendor/github.com/go-openapi/validate/example_validator.go index eb6b5ee..0e193d5 100644 --- a/vendor/github.com/go-openapi/validate/example_validator.go +++ b/vendor/github.com/go-openapi/validate/example_validator.go @@ -4,8 +4,6 @@ package validate import ( - "fmt" - "github.com/go-openapi/spec" ) @@ -24,7 +22,7 @@ type exampleValidator struct { // - individual property // - responses func (ex *exampleValidator) Validate() *Result { - errs := pools.poolOfResults.BorrowResult() + errs := validatorPools.results.Borrow() if ex == nil || ex.SpecValidator == nil { return errs @@ -50,12 +48,12 @@ func (ex *exampleValidator) resetVisited() { } // beingVisited asserts a schema is being visited. -func (ex *exampleValidator) beingVisited(path string) { - ex.visitedSchemas[path] = struct{}{} +func (ex *exampleValidator) beingVisited(path pathSegments) { + ex.visitedSchemas[path.pointer()] = struct{}{} } // isVisited tells if a path has already been visited. -func (ex *exampleValidator) isVisited(path string) bool { +func (ex *exampleValidator) isVisited(path pathSegments) bool { return isVisited(path, ex.visitedSchemas) } @@ -65,11 +63,14 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { // in: schemas, properties, object, items // not in: headers, parameters without schema - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() s := ex.SpecValidator - for method, pathItem := range s.expandedAnalyzer().Operations() { - for path, op := range pathItem { + operations := s.expandedAnalyzer().Operations() + for _, method := range sortedKeys(operations) { + pathItem := operations[method] + for _, path := range sortedKeys(pathItem) { + op := pathItem[path] // parameters for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { @@ -84,33 +85,34 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { if param.Example != nil && param.Schema == nil { // check param default value is valid red := newParamValidator(¶m, s.KnownFormats, ex.schemaOptions).Validate(param.Example) //#nosec + red.relocate(s.parameterPath(path, method, param.In, param.Name).child(swaggerExample)) if red.HasErrorsOrWarnings() { - res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In)) + res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueDoesNotValidateMsg(param.Name, param.In)) res.MergeAsWarnings(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } // Recursively follows Items and Schemas if param.Items != nil { - red := ex.validateExampleValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items) //#nosec + red := ex.validateExampleValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, ¶m, param.Items) //#nosec if red.HasErrorsOrWarnings() { - res.AddWarnings(exampleValueItemsDoesNotValidateMsg(param.Name, param.In)) + res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueItemsDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } if param.Schema != nil { // Validate example value against schema - red := ex.validateExampleValueSchemaAgainstSchema(param.Name, param.In, param.Schema) + red := ex.validateExampleValueSchemaAgainstSchema(s.parameterPath(path, method, param.In, param.Name).structuralChild(jsonSchema), param.In, param.Schema) if red.HasErrorsOrWarnings() { - res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In)) + res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueDoesNotValidateMsg(param.Name, param.In)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } } @@ -118,68 +120,74 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result { if op.Responses != nil { if op.Responses.Default != nil { // Same constraint on default Response - res.Merge(ex.validateExampleInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID)) + res.Merge(ex.validateExampleInResponse(op.Responses.Default, jsonDefault, path, method, 0, op.ID)) } // Same constraint on regular Responses if op.Responses.StatusCodeResponses != nil { // Safeguard - for code, r := range op.Responses.StatusCodeResponses { - res.Merge(ex.validateExampleInResponse(&r, "response", path, code, op.ID)) //#nosec + for _, code := range sortedKeys(op.Responses.StatusCodeResponses) { + r := op.Responses.StatusCodeResponses[code] + res.Merge(ex.validateExampleInResponse(&r, "response", path, method, code, op.ID)) } } } else if op.ID != "" { // Empty op.ID means there is no meaningful operation: no need to report a specific message - res.AddErrors(noValidResponseMsg(op.ID)) + res.addErrorsAt(operationPath(path, method), noValidResponseMsg(op.ID)) } } } if s.spec.Spec().Definitions != nil { // Safeguard // reset explored schemas to get depth-first recursive-proof exploration ex.resetVisited() - for nm, sch := range s.spec.Spec().Definitions { - res.Merge(ex.validateExampleValueSchemaAgainstSchema("definitions."+nm, "body", &sch)) //#nosec + definitions := s.spec.Spec().Definitions + for _, nm := range sortedKeys(definitions) { + sch := definitions[nm] + res.Merge(ex.validateExampleValueSchemaAgainstSchema(newPathSegments(swaggerDefinitions, nm), "body", &sch)) } } return res } -func (ex *exampleValidator) validateExampleInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result { +func (ex *exampleValidator) validateExampleInResponse( + resp *spec.Response, responseType, path, method string, responseCode int, operationID string, +) *Result { s := ex.SpecValidator - response, res := responseHelp.expandResponseRef(resp, path, s) + responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode) + response, res := responseHelp.expandResponseRef(resp, path, responsePath(path, method, responseCodeAsStr), s) if !res.IsValid() { // Safeguard return res } - responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode) - if response.Headers != nil { // Safeguard - for nm, h := range response.Headers { + for _, nm := range sortedKeys(response.Headers) { + h := response.Headers[nm] // reset explored schemas to get depth-first recursive-proof exploration ex.resetVisited() if h.Example != nil { red := newHeaderValidator(nm, &h, s.KnownFormats, ex.schemaOptions).Validate(h.Example) //#nosec + red.relocate(responseHeaderPath(path, method, responseCodeAsStr, nm).child(swaggerExample)) if red.HasErrorsOrWarnings() { - res.AddWarnings(exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) + res.addWarningsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) res.MergeAsWarnings(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } // Headers have inline definition, like params if h.Items != nil { - red := ex.validateExampleValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec + red := ex.validateExampleValueItemsAgainstSchema(responseHeaderPath(path, method, responseCodeAsStr, nm), "header", &h, h.Items) //#nosec if red.HasErrorsOrWarnings() { - res.AddWarnings(exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) + res.addWarningsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) res.MergeAsWarnings(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } if _, err := compileRegexp(h.Pattern); err != nil { - res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err)) + res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err)) } // Headers don't have schema @@ -189,77 +197,84 @@ func (ex *exampleValidator) validateExampleInResponse(resp *spec.Response, respo // reset explored schemas to get depth-first recursive-proof exploration ex.resetVisited() - red := ex.validateExampleValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema) + red := ex.validateExampleValueSchemaAgainstSchema( + responsePath(path, method, responseCodeAsStr).structuralChild(jsonSchema), "response", response.Schema) if red.HasErrorsOrWarnings() { // Additional message to make sure the context of the error is not lost - res.AddWarnings(exampleValueInDoesNotValidateMsg(operationID, responseName)) + res.addWarningsAt(responsePath(path, method, responseCodeAsStr), exampleValueInDoesNotValidateMsg(operationID, responseName)) res.Merge(red) } else if red.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(red) + redeemResult(red) } } if response.Examples != nil { if response.Schema != nil { - if example, ok := response.Examples["application/json"]; ok { + if example, ok := response.Examples[jsonMimeApplicationJSON]; ok { + exampleAt := responsePath(path, method, responseCodeAsStr). + child(swaggerExamples). + structuralChild(jsonMimeApplicationJSON) res.MergeAsWarnings( - newSchemaValidator(response.Schema, s.spec.Spec(), path+".examples", s.KnownFormats, s.schemaOptions).Validate(example), + newSchemaValidator(response.Schema, s.spec.Spec(), + exampleAt, s.KnownFormats, s.schemaOptions).Validate(example), ) } else { // Proposal for enhancement: validate other media types too - res.AddWarnings(examplesMimeNotSupportedMsg(operationID, responseName)) + res.addWarningsAt(responsePath(path, method, responseCodeAsStr).child(swaggerExamples), examplesMimeNotSupportedMsg(operationID, responseName)) } } else { - res.AddWarnings(examplesWithoutSchemaMsg(operationID, responseName)) + res.addWarningsAt(responsePath(path, method, responseCodeAsStr).child(swaggerExamples), examplesWithoutSchemaMsg(operationID, responseName)) } } return res } -func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result { +func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path pathSegments, in string, schema *spec.Schema) *Result { if schema == nil || ex.isVisited(path) { // Avoids recursing if we are already done with that check return nil } ex.beingVisited(path) s := ex.SpecValidator - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if schema.Example != nil { res.MergeAsWarnings( - newSchemaValidator(schema, s.spec.Spec(), path+".example", s.KnownFormats, ex.schemaOptions).Validate(schema.Example), + newSchemaValidator(schema, s.spec.Spec(), path.child(swaggerExample), s.KnownFormats, ex.schemaOptions).Validate(schema.Example), ) } if schema.Items != nil { if schema.Items.Schema != nil { - res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".items.example", in, schema.Items.Schema)) + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonItems), in, schema.Items.Schema)) } // Multiple schemas in items if schema.Items.Schemas != nil { // Safeguard for i, sch := range schema.Items.Schemas { - res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].example", path, i), in, &sch)) //#nosec + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonItems).item(i), in, &sch)) //#nosec } } } if _, err := compileRegexp(schema.Pattern); err != nil { - res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern)) + res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, schema.Pattern)) } if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { // NOTE: we keep validating values, even though additionalItems is unsupported in Swagger 2.0 (and 3.0 as well) - res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".additionalItems", in, schema.AdditionalItems.Schema)) + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonAdditionalItems), in, schema.AdditionalItems.Schema)) } - for propName, prop := range schema.Properties { - res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec + for _, propName := range sortedKeys(schema.Properties) { + prop := schema.Properties[propName] + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.structuralChild(jsonProperties).child(propName), in, &prop)) } - for propName, prop := range schema.PatternProperties { - res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec + for _, propName := range sortedKeys(schema.PatternProperties) { + prop := schema.PatternProperties[propName] + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.structuralChild(jsonPatternProperties).child(propName), in, &prop)) } if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { - res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".additionalProperties", in, schema.AdditionalProperties.Schema)) + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonAdditionalProperties), in, schema.AdditionalProperties.Schema)) } if schema.AllOf != nil { for i, aoSch := range schema.AllOf { - res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec + res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonAllOf).item(i), in, &aoSch)) //#nosec } } return res @@ -268,8 +283,8 @@ func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path, in str // NOTE: Temporary duplicated code. Need to refactor with examples // -func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path, in string, root any, items *spec.Items) *Result { - res := pools.poolOfResults.BorrowResult() +func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result { + res := validatorPools.results.Borrow() s := ex.SpecValidator if items != nil { if items.Example != nil { @@ -278,10 +293,10 @@ func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path, in stri ) } if items.Items != nil { - res.Merge(ex.validateExampleValueItemsAgainstSchema(path+"[0].example", in, root, items.Items)) + res.Merge(ex.validateExampleValueItemsAgainstSchema(path.item(0), in, root, items.Items)) } if _, err := compileRegexp(items.Pattern); err != nil { - res.AddErrors(invalidPatternInMsg(path, in, items.Pattern)) + res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, items.Pattern)) } } diff --git a/vendor/github.com/go-openapi/validate/formats.go b/vendor/github.com/go-openapi/validate/formats.go index eab2615..23359e3 100644 --- a/vendor/github.com/go-openapi/validate/formats.go +++ b/vendor/github.com/go-openapi/validate/formats.go @@ -11,21 +11,21 @@ import ( ) type formatValidator struct { - Path string + Path pathSegments In string Format string KnownFormats strfmt.Registry Options *SchemaValidatorOptions } -func newFormatValidator(path, in, format string, formats strfmt.Registry, opts *SchemaValidatorOptions) *formatValidator { +func newFormatValidator(path pathSegments, in, format string, formats strfmt.Registry, opts *SchemaValidatorOptions) *formatValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var f *formatValidator if opts.recycleValidators { - f = pools.poolOfFormatValidators.BorrowValidator() + f = validatorPools.formatValidators.Borrow() } else { f = new(formatValidator) } @@ -39,10 +39,6 @@ func newFormatValidator(path, in, format string, formats strfmt.Registry, opts * return f } -func (f *formatValidator) SetPath(path string) { - f.Path = path -} - func (f *formatValidator) Applies(source any, kind reflect.Kind) bool { if source == nil || f.KnownFormats == nil { return false @@ -71,7 +67,7 @@ func (f *formatValidator) Validate(val any) *Result { var result *Result if f.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -81,13 +77,17 @@ func (f *formatValidator) Validate(val any) *Result { return result } - if err := FormatOf(f.Path, f.In, f.Format, str, f.KnownFormats); err != nil { - result.AddErrors(err) + if err := FormatOf(f.Path.dotted(), f.In, f.Format, str, f.KnownFormats); err != nil { + result.addErrorsAt(f.Path, err) } return result } +func (f *formatValidator) setPath(path pathSegments) { + f.Path = path +} + func (f *formatValidator) redeem() { - pools.poolOfFormatValidators.RedeemValidator(f) + validatorPools.formatValidators.Redeem(f) } diff --git a/vendor/github.com/go-openapi/validate/helpers.go b/vendor/github.com/go-openapi/validate/helpers.go index 7cc254e..62deb97 100644 --- a/vendor/github.com/go-openapi/validate/helpers.go +++ b/vendor/github.com/go-openapi/validate/helpers.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/go-openapi/errors" + "github.com/go-openapi/jsonpointer" "github.com/go-openapi/spec" ) @@ -33,13 +34,78 @@ const ( ) const ( - jsonProperties = "properties" - jsonItems = "items" - jsonType = "type" - // jsonSchema = "schema". - jsonDefault = "default" + jsonProperties = "properties" + jsonPatternProperties = "patternProperties" + jsonItems = "items" + jsonType = "type" + jsonSchema = "schema" + jsonRequired = "required" + jsonRef = "$ref" + jsonDefault = "default" + + jsonAllOf = "allOf" + jsonAnyOf = "anyOf" + jsonOneOf = "oneOf" + jsonNot = "not" + jsonAdditionalItems = "additionalItems" + jsonAdditionalProperties = "additionalProperties" + + swaggerPaths = "paths" + swaggerDefinitions = "definitions" + swaggerResponses = "responses" + swaggerParameters = "parameters" + swaggerHeaders = "headers" + swaggerOperationID = "operationId" + + jsonMimeApplicationJSON = "application/json" ) +// operationPath locates an operation in the spec document. +func operationPath(path, method string) pathSegments { + return newPathSegments(swaggerPaths, path, methodToken(method)) +} + +// parameterPath locates a parameter of an operation in the spec document. +func (s *SpecValidator) parameterPath(path, method, in, name string) pathSegments { + return s.paramLocations.at(path, method, in, name) +} + +// responsePath locates a response of an operation in the spec document. +func responsePath(path, method, responseCode string) pathSegments { + return operationPath(path, method).children(swaggerResponses, responseCode) +} + +// responseHeaderPath locates a header declared by a response. +func responseHeaderPath(path, method, responseCode, header string) pathSegments { + return responsePath(path, method, responseCode).children(swaggerHeaders, header) +} + +// methodToken normalizes an HTTP method into the key under which the operation +// is found in the document: the analyzer hands them over in upper case, but a +// path item spells them in lower case. +func methodToken(method string) string { + return strings.ToLower(method) +} + +// localRefPath turns a local JSON reference such as "#/definitions/Pet" into +// the location of what it points to. +// +// It yields the document root for anything that does not address a local +// fragment, a remote reference in particular. +func localRefPath(ref string) pathSegments { + rest, isLocal := strings.CutPrefix(ref, "#/") + if !isLocal { + return rootPath() + } + + tokens := strings.Split(rest, "/") + for i, token := range tokens { + tokens[i] = jsonpointer.Unescape(token) + } + + return newPathSegments(tokens...) +} + const ( stringFormatDate = "date" stringFormatDateTime = "date-time" @@ -91,24 +157,33 @@ type errorHelper struct { } func (h *errorHelper) sErr(err errors.Error, recycle bool) *Result { - // Builds a Result from standard errors.Error + return h.sErrAt(nil, err, recycle) +} + +// sErrAt builds a Result from a standard errors.Error reported at a known location. +func (h *errorHelper) sErrAt(at pathSegments, err errors.Error, recycle bool) *Result { var result *Result if recycle { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } - result.Errors = []error{err} + result.addErrorsAt(at, err) return result } func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result { - // Provides more context on error messages - // reported by the jsoinpointer package by altering the passed Result + return h.addPointerErrorAt(res, nil, err, ref, fromPath) +} + +// addPointerErrorAt provides more context on error messages reported by the +// jsonpointer package, by altering the passed Result. +func (h *errorHelper) addPointerErrorAt(res *Result, at pathSegments, err error, ref string, fromPath string) *Result { if err != nil { - res.AddErrors(cannotResolveRefMsg(fromPath, ref, err)) + res.addErrorsAt(at, cannotResolveRefMsg(fromPath, ref, err)) } + return res } @@ -222,15 +297,19 @@ func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, re // remove params with invalid expansion from Slice operation.Parameters = resolvedParams - for _, ppr := range s.expandedAnalyzer().SafeParamsFor(method, path, + // the analyzer keys parameters by name and location: walk those keys in + // order, so that findings about an operation's parameters come out the + // same way on every run + safeParams := s.expandedAnalyzer().SafeParamsFor(method, path, func(_ spec.Parameter, err error) bool { // since params have already been expanded, there are few causes for error - res.AddErrors(someParametersBrokenMsg(path, method, operationID)) + res.addErrorsAt(operationPath(path, method), someParametersBrokenMsg(path, method, operationID)) // original error from analyzer - res.AddErrors(err) + res.addErrorsAt(operationPath(path, method), err) return true - }) { - params = append(params, ppr) + }) + for _, k := range sortedKeys(safeParams) { + params = append(params, safeParams[k]) } } return @@ -249,14 +328,16 @@ func (h *paramHelper) resolveParam(path, method, operationID string, param *spec if err != nil { // Safeguard // NOTE: we may enter here when the whole parameter is an unresolved $ref refPath := strings.Join([]string{"\"" + path + "\"", method}, ".") - errorHelp.addPointerError(res, err, param.Ref.String(), refPath) + errorHelp.addPointerErrorAt(res, s.parameterPath(path, method, param.In, param.Name), err, param.Ref.String(), refPath) return nil, res } - res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef)) + res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, s.parameterPath(path, method, param.In, param.Name), isRef)) return param, res } -func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result { +func (h *paramHelper) checkExpandedParam( + pr *spec.Parameter, path, in, operation string, at pathSegments, isRef bool, +) *Result { // Secure parameter structure after $ref resolution res := new(Result) simpleZero := spec.SimpleSchema{} @@ -267,17 +348,17 @@ func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation // Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning... // but we detect it because of the following error: // schema took over Parameter for an unexplained reason - res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation)) + res.addWarningsAt(at, refShouldNotHaveSiblingsMsg(path, operation)) } - res.AddErrors(invalidParameterDefinitionMsg(path, in, operation)) + res.addErrorsAt(at, invalidParameterDefinitionMsg(path, in, operation)) case pr.In != swaggerBody && pr.Schema != nil: if isRef { - res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation)) + res.addWarningsAt(at, refShouldNotHaveSiblingsMsg(path, operation)) } - res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation)) + res.addErrorsAt(at, invalidParameterDefinitionAsSchemaMsg(path, in, operation)) case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero): // Other unexpected mishaps - res.AddErrors(invalidParameterDefinitionMsg(path, in, operation)) + res.addErrorsAt(at, invalidParameterDefinitionMsg(path, in, operation)) } return res } @@ -288,7 +369,7 @@ type responseHelper struct { func (r *responseHelper) expandResponseRef( response *spec.Response, - path string, s *SpecValidator, + path string, at pathSegments, s *SpecValidator, ) (*spec.Response, *Result) { // Ensure response is expanded var err error @@ -301,7 +382,7 @@ func (r *responseHelper) expandResponseRef( } if err != nil { // Safeguard // NOTE: we may enter here when the whole response is an unresolved $ref. - errorHelp.addPointerError(res, err, response.Ref.String(), path) + errorHelp.addPointerErrorAt(res, at, err, response.Ref.String(), path) return nil, res } diff --git a/vendor/github.com/go-openapi/validate/object_validator.go b/vendor/github.com/go-openapi/validate/object_validator.go index e651b3f..4f1dd15 100644 --- a/vendor/github.com/go-openapi/validate/object_validator.go +++ b/vendor/github.com/go-openapi/validate/object_validator.go @@ -4,7 +4,6 @@ package validate import ( - "fmt" "reflect" "strings" @@ -14,7 +13,7 @@ import ( ) type objectValidator struct { - Path string + Path pathSegments In string MaxProperties *int64 MinProperties *int64 @@ -25,10 +24,9 @@ type objectValidator struct { Root any KnownFormats strfmt.Registry Options *SchemaValidatorOptions - splitPath []string } -func newObjectValidator(path, in string, +func newObjectValidator(path pathSegments, in string, maxProperties, minProperties *int64, required []string, properties spec.SchemaProperties, additionalProperties *spec.SchemaOrBool, patternProperties spec.SchemaProperties, root any, formats strfmt.Registry, opts *SchemaValidatorOptions, @@ -39,7 +37,7 @@ func newObjectValidator(path, in string, var v *objectValidator if opts.recycleValidators { - v = pools.poolOfObjectValidators.BorrowValidator() + v = validatorPools.objectValidators.Borrow() } else { v = new(objectValidator) } @@ -55,7 +53,6 @@ func newObjectValidator(path, in string, v.Root = root v.KnownFormats = formats v.Options = opts - v.splitPath = strings.Split(v.Path, ".") return v } @@ -72,21 +69,21 @@ func (o *objectValidator) Validate(data any) *Result { var ok bool val, ok = data.(map[string]any) if !ok { - return errorHelp.sErr(invalidObjectMsg(o.Path, o.In), o.Options.recycleResult) + return errorHelp.sErrAt(o.Path, invalidObjectMsg(o.Path.dotted(), o.In), o.Options.recycleResult) } } numKeys := int64(len(val)) if o.MinProperties != nil && numKeys < *o.MinProperties { - return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties), o.Options.recycleResult) + return errorHelp.sErrAt(o.Path, errors.TooFewProperties(o.Path.dotted(), o.In, *o.MinProperties), o.Options.recycleResult) } if o.MaxProperties != nil && numKeys > *o.MaxProperties { - return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties), o.Options.recycleResult) + return errorHelp.sErrAt(o.Path, errors.TooManyProperties(o.Path.dotted(), o.In, *o.MaxProperties), o.Options.recycleResult) } var res *Result if o.Options.recycleResult { - res = pools.poolOfResults.BorrowResult() + res = validatorPools.results.Borrow() } else { res = new(Result) } @@ -106,7 +103,8 @@ func (o *objectValidator) Validate(data any) *Result { // Check patternProperties // NOTE: it looks like we have done that twice in many cases - for key, value := range val { + for _, key := range sortedKeys(val) { + value := val[key] _, regularProperty := o.Properties[key] matched, _, patterns := o.validatePatternProperty(key, value, res) // applies to regular properties as well if regularProperty || !matched { @@ -115,7 +113,7 @@ func (o *objectValidator) Validate(data any) *Result { for _, pName := range patterns { if v, ok := o.PatternProperties[pName]; ok { - r := newSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value) + r := newSchemaValidator(&v, o.Root, o.Path.child(key), o.KnownFormats, o.Options).Validate(value) res.mergeForField(data.(map[string]any), key, r) //nolint:forcetypeassert // data is always map[string]any at this point } } @@ -124,11 +122,6 @@ func (o *objectValidator) Validate(data any) *Result { return res } -func (o *objectValidator) SetPath(path string) { - o.Path = path - o.splitPath = strings.Split(path, ".") -} - func (o *objectValidator) Applies(source any, kind reflect.Kind) bool { // NOTE: this should also work for structs // there is a problem in the type validator where it will be unhappy about null values @@ -137,19 +130,29 @@ func (o *objectValidator) Applies(source any, kind reflect.Kind) bool { return isSchema && (kind == reflect.Map || kind == reflect.Struct) } +// The three predicates below tell what kind of content the validated object +// is, so that schema-only checks are not run against plain data. +// +// Array indices are trimmed first: an element of an example is example data +// just as much as the example itself. + func (o *objectValidator) isProperties() bool { - p := o.splitPath - return len(p) > 1 && p[len(p)-1] == jsonProperties && p[len(p)-2] != jsonProperties + p := o.Path.trimIndexes() + + return p.last() == jsonProperties && p.beforeLast() != jsonProperties } func (o *objectValidator) isDefault() bool { - p := o.splitPath - return len(p) > 1 && p[len(p)-1] == jsonDefault && p[len(p)-2] != jsonDefault + p := o.Path.trimIndexes() + + return p.last() == jsonDefault && p.beforeLast() != jsonDefault } func (o *objectValidator) isExample() bool { - p := o.splitPath - return len(p) > 1 && (p[len(p)-1] == swaggerExample || p[len(p)-1] == swaggerExamples) && p[len(p)-2] != swaggerExample + p := o.Path.trimIndexes() + last := p.last() + + return (last == swaggerExample || last == swaggerExamples) && p.beforeLast() != swaggerExample } func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]any) { @@ -174,7 +177,7 @@ func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]an return } - res.AddErrors(errors.Required(jsonItems, o.Path, item)) + res.addErrorsAt(o.Path, errors.Required(jsonItems, o.Path.dotted(), item)) } func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]any) { @@ -194,11 +197,11 @@ func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string] t, typeFound := val[jsonType] if !typeFound { // there is no type - res.AddErrors(errors.Required(jsonType, o.Path, t)) + res.addErrorsAt(o.Path, errors.Required(jsonType, o.Path.dotted(), t)) } if tpe, isString := t.(string); !isString || tpe != arrayType { - res.AddErrors(errors.InvalidType(o.Path, o.In, arrayType, nil)) + res.addErrorsAt(o.Path, errors.InvalidType(o.Path.dotted(), o.In, arrayType, nil)) } } @@ -212,7 +215,7 @@ func (o *objectValidator) precheck(res *Result, val map[string]any) { } func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res *Result) { - for k := range val { + for _, k := range sortedKeys(val) { if k == "$schema" || k == "id" { // special properties "$schema" and "id" are ignored continue @@ -238,7 +241,7 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res continue } - res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k)) + res.addErrorsAt(o.Path.child(k), errors.PropertyNotAllowed(o.Path.dotted(), o.In, k)) // BUG(fredbi): This section should move to a part dedicated to spec validation as // it will conflict with regular schemas where a property "headers" is defined. @@ -261,7 +264,8 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res continue } - for headerKey, headerBody := range headers { + for _, headerKey := range sortedKeys(headers) { + headerBody := headers[headerKey] if headerBody == nil { continue } @@ -282,7 +286,7 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res } msg := strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "") - res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg)) + res.addErrorsAt(o.Path, refNotAllowedInHeaderMsg(o.Path.dotted(), headerKey, msg)) /* case "$ref": if val[k] != nil { @@ -294,7 +298,8 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res } func (o *objectValidator) validateAdditionalProperties(val map[string]any, res *Result) { - for key, value := range val { + for _, key := range sortedKeys(val) { + value := val[key] _, regularProperty := o.Properties[key] if regularProperty { continue @@ -315,7 +320,7 @@ func (o *objectValidator) validateAdditionalProperties(val map[string]any, res * // Cases: properties which are not regular properties and have not been matched by the PatternProperties validator // AdditionalProperties as Schema - r := newSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value) + r := newSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path.child(key), o.KnownFormats, o.Options).Validate(value) res.mergeForField(val, key, r) } // Valid cases: additionalProperties: true or undefined @@ -326,19 +331,14 @@ func (o *objectValidator) validatePropertiesSchema(val map[string]any, res *Resu // Property types: // - regular Property - pSchema := pools.poolOfSchemas.BorrowSchema() // recycle a spec.Schema object which lifespan extends only to the validation of properties + pSchema := validatorPools.schemas.Borrow() // recycle a spec.Schema object which lifespan extends only to the validation of properties defer func() { - pools.poolOfSchemas.RedeemSchema(pSchema) + validatorPools.schemas.Redeem(pSchema) }() - for pName := range o.Properties { + for _, pName := range sortedKeys(o.Properties) { *pSchema = o.Properties[pName] - var rName string - if o.Path == "" { - rName = pName - } else { - rName = o.Path + "." + pName - } + rName := o.Path.child(pName) // Recursively validates each property against its schema v, ok := val[pName] @@ -374,7 +374,9 @@ func (o *objectValidator) validatePropertiesSchema(val map[string]any, res *Resu continue } - res.AddErrors(errors.Required(fmt.Sprintf("%s.%s", o.Path, k), o.In, v)) + // located on the object that lacks the property: the property itself + // has no node to point at, and the object is what has to be amended + res.addErrorsAt(o.Path, errors.Required(o.Path.child(k).dotted(), o.In, v)) } } @@ -388,12 +390,12 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result succeededOnce := false patterns := make([]string, 0, len(o.PatternProperties)) - schema := pools.poolOfSchemas.BorrowSchema() + schema := validatorPools.schemas.Borrow() defer func() { - pools.poolOfSchemas.RedeemSchema(schema) + validatorPools.schemas.Redeem(schema) }() - for k := range o.PatternProperties { + for _, k := range sortedKeys(o.PatternProperties) { re, err := compileRegexp(k) if err != nil { continue @@ -407,7 +409,7 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result *schema = o.PatternProperties[k] patterns = append(patterns, k) matched = true - validator := newSchemaValidator(schema, o.Root, fmt.Sprintf("%s.%s", o.Path, key), o.KnownFormats, o.Options) + validator := newSchemaValidator(schema, o.Root, o.Path.child(key), o.KnownFormats, o.Options) res := validator.Validate(value) result.Merge(res) @@ -416,6 +418,10 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result return matched, succeededOnce, patterns } +func (o *objectValidator) setPath(path pathSegments) { + o.Path = path +} + func (o *objectValidator) redeem() { - pools.poolOfObjectValidators.RedeemValidator(o) + validatorPools.objectValidators.Redeem(o) } diff --git a/vendor/github.com/go-openapi/validate/param_locations.go b/vendor/github.com/go-openapi/validate/param_locations.go new file mode 100644 index 0000000..703d6ca --- /dev/null +++ b/vendor/github.com/go-openapi/validate/param_locations.go @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "strconv" + + "github.com/go-openapi/spec" +) + +// paramLocations tells where an operation declares a parameter. +// +// Parameters are held in an array, so a name is not how a document addresses +// one: only its index is. The index is not something a validator can work out +// from an expanded parameter either, because expansion merges the parameters +// an operation declares with those its path item declares, and resolves the +// ones written as a $ref. So the unexpanded document is indexed once, and +// looked up by what a validator does know: the operation, and the name and +// location of the parameter it is reporting on. +type paramLocations map[paramKey]pathSegments + +// paramKey identifies a parameter the way the swagger specification does: +// a name is unique only within an "in". +type paramKey struct { + path string + method string + in string + name string +} + +// newParamLocations indexes the parameters declared by an unexpanded document. +func newParamLocations(sp *spec.Swagger) paramLocations { + locations := make(paramLocations) + if sp == nil || sp.Paths == nil { + return locations + } + + for path, pathItem := range sp.Paths.Paths { + at := newPathSegments(swaggerPaths, path) + + // parameters declared by the path item are shared by all its + // operations: recorded once, without a method + locations.collect(sp, paramKey{path: path}, at, pathItem.Parameters) + + for method, op := range operationsOf(&pathItem) { //#nosec + if op == nil { + continue + } + + locations.collect(sp, paramKey{path: path, method: method}, at.child(method), op.Parameters) + } + } + + return locations +} + +// at returns where an operation declares a parameter. +// +// A parameter the operation does not declare itself may come from its path +// item. When neither knows it, which happens for a parameter too broken to be +// identified, the pointer stops on the array holding it and the name is kept +// for the message alone: an index no one could work out would be a guess. +func (l paramLocations) at(path, method, in, name string) pathSegments { + if found, isDeclared := l[paramKey{path: path, method: methodToken(method), in: in, name: name}]; isDeclared { + return found + } + + if found, isDeclared := l[paramKey{path: path, in: in, name: name}]; isDeclared { + return found + } + + return operationPath(path, method).child(swaggerParameters).cosmeticChild(name) +} + +func (l paramLocations) collect(sp *spec.Swagger, key paramKey, at pathSegments, params []spec.Parameter) { + for i := range params { + name, in, ok := parameterIdentity(sp, ¶ms[i]) + if !ok { + continue + } + + key.name = name + key.in = in + l[key] = at.child(swaggerParameters).childAs(strconv.Itoa(i), name) + } +} + +// parameterIdentity names a parameter as declared, resolving the one indirection +// a document may put in the way: an entry written as a local $ref. +func parameterIdentity(sp *spec.Swagger, param *spec.Parameter) (name, in string, ok bool) { + if param.Ref.String() == "" { + return param.Name, param.In, param.Name != "" + } + + shared := localRefPath(param.Ref.String()) + const sharedParameterDepth = 2 + if len(shared) != sharedParameterDepth || shared.beforeLast() != swaggerParameters { + return "", "", false + } + + declared, isDeclared := sp.Parameters[shared.last()] + if !isDeclared { + return "", "", false + } + + return declared.Name, declared.In, declared.Name != "" +} + +// operationsOf yields the operations of a path item, keyed as the document +// spells them. +func operationsOf(pathItem *spec.PathItem) map[string]*spec.Operation { + return map[string]*spec.Operation{ + "get": pathItem.Get, + "put": pathItem.Put, + "post": pathItem.Post, + "delete": pathItem.Delete, + "options": pathItem.Options, + "head": pathItem.Head, + "patch": pathItem.Patch, + } +} diff --git a/vendor/github.com/go-openapi/validate/path.go b/vendor/github.com/go-openapi/validate/path.go new file mode 100644 index 0000000..940fc4b --- /dev/null +++ b/vendor/github.com/go-openapi/validate/path.go @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "slices" + "strconv" + "strings" + + "github.com/go-openapi/jsonpointer" +) + +// pathSegments is the location of a validated value inside a document, +// held as an ordered list of unescaped JSON pointer reference tokens. +// +// Validators build a location by appending tokens as they descend into +// properties and array items, then render it only when they report an error. +// Keeping the tokens apart until then is what makes it possible to produce a +// valid [RFC 6901] JSON pointer: a token is escaped when it is rendered, and +// the separator can never be confused with a token that contains one. +// +// The zero value is the location of the document root. +// +// [RFC 6901]: https://datatracker.ietf.org/doc/html/rfc6901 +type pathSegments []pathToken + +// pathToken is one step of a location. +// +// A token addresses a member the way the document does, which is not always +// the way a reader recognizes it: an operation addresses its parameters by +// index, while a message is far more useful naming them. When the two differ, +// display carries the readable form and only the message uses it. +type pathToken struct { + token string + display string + + // structural marks a token a document needs to address the value, but + // that messages have never shown: a "properties" between a schema and one + // of its members, say. It is part of the pointer and absent from the + // dotted form. + structural bool + + // cosmetic is the converse: a token messages name but that the document + // does not address, such as a parameter too broken to be found by name in + // the array holding it. It is part of the dotted form and absent from the + // pointer, which then stops at the deepest node the document does contain. + cosmetic bool +} + +// readable renders a token the way a message should spell it. +func (t pathToken) readable() string { + if t.display != "" { + return t.display + } + + return t.token +} + +// newPathSegments builds a location from a list of unescaped tokens. +func newPathSegments(tokens ...string) pathSegments { + if len(tokens) == 0 { + return nil + } + + segments := make(pathSegments, len(tokens)) + for i, token := range tokens { + segments[i] = pathToken{token: token} + } + + return segments +} + +// rootPath is the location of the document root. +func rootPath() pathSegments { return nil } + +// String implements [fmt.Stringer] with the legacy dotted notation, so that a +// location interpolated into a message reads as it always has. +func (p pathSegments) String() string { return p.dotted() } + +// child returns the location of a named member of the value at p. +// +// The receiver is never modified: sibling children may be derived from the +// same parent without aliasing one another. +func (p pathSegments) child(token string) pathSegments { + return p.appendToken(pathToken{token: token}) +} + +// childAs returns the location of a member the document addresses as token, +// which messages should spell as display instead. +func (p pathSegments) childAs(token, display string) pathSegments { + return p.appendToken(pathToken{token: token, display: display}) +} + +// structuralChild returns the location of a member a document addresses but +// messages do not name. +func (p pathSegments) structuralChild(token string) pathSegments { + return p.appendToken(pathToken{token: token, structural: true}) +} + +// cosmeticChild returns a location that messages spell as a member named token, +// while the pointer stays on p. +func (p pathSegments) cosmeticChild(token string) pathSegments { + return p.appendToken(pathToken{token: token, cosmetic: true}) +} + +func (p pathSegments) appendToken(token pathToken) pathSegments { + child := make(pathSegments, len(p)+1) + copy(child, p) + child[len(p)] = p.inherit(token) + + return child +} + +// inherit passes down what a parent token says about addressability: nothing +// below a token the document does not address is addressable either, so the +// pointer has to stop at the same place. +func (p pathSegments) inherit(token pathToken) pathToken { + if len(p) > 0 && p[len(p)-1].cosmetic { + token.cosmetic = true + } + + return token +} + +// children returns the location of a chain of named members below p. +func (p pathSegments) children(tokens ...string) pathSegments { + child := make(pathSegments, len(p)+len(tokens)) + copy(child, p) + for i, token := range tokens { + child[len(p)+i] = child[:len(p)+i].inherit(pathToken{token: token}) + } + + return child +} + +// item returns the location of the index'th element of the array at p. +func (p pathSegments) item(index int) pathSegments { + return p.child(strconv.Itoa(index)) +} + +// isEmpty tells if p locates the document root. +func (p pathSegments) isEmpty() bool { return len(p) == 0 } + +// last returns the trailing meaningful token, or an empty string at the +// document root. +// +// Structural tokens are skipped: they say how a document addresses the value, +// not what the value is, and the callers here are asking the latter. +func (p pathSegments) last() string { + if token, ok := p.meaningfulAt(0); ok { + return token + } + + return "" +} + +// beforeLast returns the meaningful token before the trailing one, or an empty +// string when p holds fewer than two of them. +func (p pathSegments) beforeLast() string { + if token, ok := p.meaningfulAt(1); ok { + return token + } + + return "" +} + +// meaningfulAt returns the nth token from the end, counting only the tokens a +// message would show. +func (p pathSegments) meaningfulAt(n int) (string, bool) { + seen := 0 + for _, token := range slices.Backward(p) { + if token.structural { + continue + } + + if seen == n { + return token.token, true + } + + seen++ + } + + return "", false +} + +// trimIndexes returns p without its trailing array index tokens. +// +// It answers "what is this value inside of", disregarding how deep into an +// array it sits: the items of an example are still an example. +func (p pathSegments) trimIndexes() pathSegments { + end := len(p) + for end > 0 && isIndexToken(p[end-1].token) { + end-- + } + + return p[:end] +} + +// isIndexToken tells if a token addresses an array element rather than a member. +func isIndexToken(token string) bool { + if token == "" { + return false + } + + for _, r := range token { + if r < '0' || r > '9' { + return false + } + } + + return true +} + +// hasSuffix tells if p ends with the given sequence of tokens. +func (p pathSegments) hasSuffix(suffix pathSegments) bool { + if len(suffix) > len(p) { + return false + } + + offset := len(p) - len(suffix) + for i, token := range suffix { + if p[offset+i].token != token.token { + return false + } + } + + return true +} + +// dotted renders the location in the legacy dot-separated notation, e.g. +// "definitions.Pet.friends.0.name". +// +// Tokens are emitted verbatim: a token containing a dot is indistinguishable +// from a separator. This notation is kept because it is what surfaces as the +// name of a validation error, and API consumers of go-swagger servers see it. +// Use [pathSegments.pointer] whenever the location needs to be unambiguous. +func (p pathSegments) dotted() string { + readable := make([]string, 0, len(p)) + for _, token := range p { + if token.structural { + continue + } + + readable = append(readable, token.readable()) + } + + return strings.Join(readable, ".") +} + +// pointer renders the location as an RFC 6901 JSON pointer, e.g. +// "/definitions/Pet/friends/0/name". The document root renders as "". +func (p pathSegments) pointer() string { + if len(p) == 0 { + return "" + } + + var w strings.Builder + for _, token := range p { + if token.cosmetic { + continue + } + + w.WriteByte('/') + w.WriteString(jsonpointer.Escape(token.token)) + } + + return w.String() +} diff --git a/vendor/github.com/go-openapi/validate/pools.go b/vendor/github.com/go-openapi/validate/pools.go index c8936bd..0bc95d1 100644 --- a/vendor/github.com/go-openapi/validate/pools.go +++ b/vendor/github.com/go-openapi/validate/pools.go @@ -1,369 +1,87 @@ // SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 -//go:build !validatedebug - package validate import ( - "sync" - "github.com/go-openapi/spec" + "github.com/go-openapi/swag/pools" ) -var pools allPools +// validatorPools recycles the objects allocated while validating. +// +// Validation allocates a validator per schema node and a result per check, so +// the same handful of types are built and thrown away constantly. Recycling +// them is what keeps validating a large specification affordable. +// +// Build with the "poolsdebug" tag to have every borrow and redeem tracked: +// misuse then panics where it happens rather than corrupting a pool, and +// [pools.AssertNoLeaks] reports what was borrowed and never given back. +var validatorPools allPools func init() { resetPools() } +// resetPools builds a fresh set of pools. +// +// Recycling an object twice leaves a pool holding it twice, and it would then +// be handed to two borrowers at once. A test that provokes such misuse has to +// start the next one from clean pools. func resetPools() { - // NOTE: for testing purpose, we might want to reset pools after calling Validate twice. - // The pool is corrupted in that case: calling Put twice inserts a duplicate in the pool - // and further calls to Get are mishandled. - - pools = allPools{ - poolOfSchemaValidators: schemaValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &SchemaValidator{} - - return s - }, - }, - }, - poolOfObjectValidators: objectValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &objectValidator{} - - return s - }, - }, - }, - poolOfSliceValidators: sliceValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &schemaSliceValidator{} - - return s - }, - }, - }, - poolOfItemsValidators: itemsValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &itemsValidator{} - - return s - }, - }, - }, - poolOfBasicCommonValidators: basicCommonValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &basicCommonValidator{} - - return s - }, - }, - }, - poolOfHeaderValidators: headerValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &HeaderValidator{} - - return s - }, - }, - }, - poolOfParamValidators: paramValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &ParamValidator{} - - return s - }, - }, - }, - poolOfBasicSliceValidators: basicSliceValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &basicSliceValidator{} - - return s - }, - }, - }, - poolOfNumberValidators: numberValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &numberValidator{} - - return s - }, - }, - }, - poolOfStringValidators: stringValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &stringValidator{} - - return s - }, - }, - }, - poolOfSchemaPropsValidators: schemaPropsValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &schemaPropsValidator{} - - return s - }, - }, - }, - poolOfFormatValidators: formatValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &formatValidator{} - - return s - }, - }, - }, - poolOfTypeValidators: typeValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &typeValidator{} - - return s - }, - }, - }, - poolOfSchemas: schemasPool{ - Pool: &sync.Pool{ - New: func() any { - s := &spec.Schema{} - - return s - }, - }, - }, - poolOfResults: resultsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &Result{} - - return s - }, - }, - }, - } -} - -type ( - allPools struct { - // memory pools for all validator objects. - // - // Each pool can be borrowed from and redeemed to. - poolOfSchemaValidators schemaValidatorsPool - poolOfObjectValidators objectValidatorsPool - poolOfSliceValidators sliceValidatorsPool - poolOfItemsValidators itemsValidatorsPool - poolOfBasicCommonValidators basicCommonValidatorsPool - poolOfHeaderValidators headerValidatorsPool - poolOfParamValidators paramValidatorsPool - poolOfBasicSliceValidators basicSliceValidatorsPool - poolOfNumberValidators numberValidatorsPool - poolOfStringValidators stringValidatorsPool - poolOfSchemaPropsValidators schemaPropsValidatorsPool - poolOfFormatValidators formatValidatorsPool - poolOfTypeValidators typeValidatorsPool - poolOfSchemas schemasPool - poolOfResults resultsPool - } - - schemaValidatorsPool struct { - *sync.Pool - } - - objectValidatorsPool struct { - *sync.Pool - } - - sliceValidatorsPool struct { - *sync.Pool - } - - itemsValidatorsPool struct { - *sync.Pool - } - - basicCommonValidatorsPool struct { - *sync.Pool - } - - headerValidatorsPool struct { - *sync.Pool - } - - paramValidatorsPool struct { - *sync.Pool - } - - basicSliceValidatorsPool struct { - *sync.Pool - } - - numberValidatorsPool struct { - *sync.Pool - } - - stringValidatorsPool struct { - *sync.Pool - } - - schemaPropsValidatorsPool struct { - *sync.Pool - } - - formatValidatorsPool struct { - *sync.Pool - } - - typeValidatorsPool struct { - *sync.Pool - } - - schemasPool struct { - *sync.Pool - } - - resultsPool struct { - *sync.Pool - } -) - -func (p schemaValidatorsPool) BorrowValidator() *SchemaValidator { - return p.Get().(*SchemaValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p schemaValidatorsPool) RedeemValidator(s *SchemaValidator) { - // NOTE: s might be nil. In that case, Put is a noop. - p.Put(s) -} - -func (p objectValidatorsPool) BorrowValidator() *objectValidator { - return p.Get().(*objectValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p objectValidatorsPool) RedeemValidator(s *objectValidator) { - p.Put(s) -} - -func (p sliceValidatorsPool) BorrowValidator() *schemaSliceValidator { - return p.Get().(*schemaSliceValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p sliceValidatorsPool) RedeemValidator(s *schemaSliceValidator) { - p.Put(s) -} - -func (p itemsValidatorsPool) BorrowValidator() *itemsValidator { - return p.Get().(*itemsValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p itemsValidatorsPool) RedeemValidator(s *itemsValidator) { - p.Put(s) -} - -func (p basicCommonValidatorsPool) BorrowValidator() *basicCommonValidator { - return p.Get().(*basicCommonValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p basicCommonValidatorsPool) RedeemValidator(s *basicCommonValidator) { - p.Put(s) -} - -func (p headerValidatorsPool) BorrowValidator() *HeaderValidator { - return p.Get().(*HeaderValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p headerValidatorsPool) RedeemValidator(s *HeaderValidator) { - p.Put(s) -} - -func (p paramValidatorsPool) BorrowValidator() *ParamValidator { - return p.Get().(*ParamValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p paramValidatorsPool) RedeemValidator(s *ParamValidator) { - p.Put(s) -} - -func (p basicSliceValidatorsPool) BorrowValidator() *basicSliceValidator { - return p.Get().(*basicSliceValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p basicSliceValidatorsPool) RedeemValidator(s *basicSliceValidator) { - p.Put(s) -} - -func (p numberValidatorsPool) BorrowValidator() *numberValidator { - return p.Get().(*numberValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p numberValidatorsPool) RedeemValidator(s *numberValidator) { - p.Put(s) -} - -func (p stringValidatorsPool) BorrowValidator() *stringValidator { - return p.Get().(*stringValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p stringValidatorsPool) RedeemValidator(s *stringValidator) { - p.Put(s) -} - -func (p schemaPropsValidatorsPool) BorrowValidator() *schemaPropsValidator { - return p.Get().(*schemaPropsValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p schemaPropsValidatorsPool) RedeemValidator(s *schemaPropsValidator) { - p.Put(s) -} - -func (p formatValidatorsPool) BorrowValidator() *formatValidator { - return p.Get().(*formatValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p formatValidatorsPool) RedeemValidator(s *formatValidator) { - p.Put(s) -} - -func (p typeValidatorsPool) BorrowValidator() *typeValidator { - return p.Get().(*typeValidator) //nolint:forcetypeassert // pool New always returns this type -} - -func (p typeValidatorsPool) RedeemValidator(s *typeValidator) { - p.Put(s) -} - -func (p schemasPool) BorrowSchema() *spec.Schema { - return p.Get().(*spec.Schema) //nolint:forcetypeassert // pool New always returns this type -} - -func (p schemasPool) RedeemSchema(s *spec.Schema) { - p.Put(s) -} - -func (p resultsPool) BorrowResult() *Result { - return p.Get().(*Result).cleared() //nolint:forcetypeassert // pool New always returns *Result -} - -func (p resultsPool) RedeemResult(s *Result) { - if s == emptyResult { + validatorPools = allPools{ + schemaValidators: pools.New[SchemaValidator](), + objectValidators: pools.New[objectValidator](), + sliceValidators: pools.New[schemaSliceValidator](), + itemsValidators: pools.New[itemsValidator](), + basicCommonValidators: pools.New[basicCommonValidator](), + headerValidators: pools.New[HeaderValidator](), + paramValidators: pools.New[ParamValidator](), + basicSliceValidators: pools.New[basicSliceValidator](), + numberValidators: pools.New[numberValidator](), + stringValidators: pools.New[stringValidator](), + schemaPropsValidators: pools.New[schemaPropsValidator](), + formatValidators: pools.New[formatValidator](), + typeValidators: pools.New[typeValidator](), + schemas: pools.New[spec.Schema](), + results: pools.New[Result](), + } +} + +// allPools is the set of pools shared by the validators of this package. +type allPools struct { + schemaValidators *pools.Pool[SchemaValidator] + objectValidators *pools.Pool[objectValidator] + sliceValidators *pools.Pool[schemaSliceValidator] + itemsValidators *pools.Pool[itemsValidator] + basicCommonValidators *pools.Pool[basicCommonValidator] + headerValidators *pools.Pool[HeaderValidator] + paramValidators *pools.Pool[ParamValidator] + basicSliceValidators *pools.Pool[basicSliceValidator] + numberValidators *pools.Pool[numberValidator] + stringValidators *pools.Pool[stringValidator] + schemaPropsValidators *pools.Pool[schemaPropsValidator] + formatValidators *pools.Pool[formatValidator] + typeValidators *pools.Pool[typeValidator] + schemas *pools.Pool[spec.Schema] + results *pools.Pool[Result] +} + +// redeemResult returns a result to the pool. +// +// emptyResult is a shared value that was never borrowed, so it is not the +// pool's to take back: handing it over would be reported as a foreign redeem, +// rightly. +// +// Results are borrowed straight from the pool rather than through a helper, +// so that the instrumented build attributes a leak to the code that borrowed it. +// +// This wrapper costs that attribution on redeem, where a double redeem still +// names the offending call site in the panic it raises. +func redeemResult(r *Result) { + if r == emptyResult { return } - p.Put(s) + + validatorPools.results.Redeem(r) } diff --git a/vendor/github.com/go-openapi/validate/pools_debug.go b/vendor/github.com/go-openapi/validate/pools_debug.go deleted file mode 100644 index d123ed4..0000000 --- a/vendor/github.com/go-openapi/validate/pools_debug.go +++ /dev/null @@ -1,1015 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers -// SPDX-License-Identifier: Apache-2.0 - -//go:build validatedebug - -package validate - -import ( - "fmt" - "runtime" - "sync" - "testing" - - "github.com/go-openapi/spec" -) - -// This version of the pools is to be used for debugging and testing, with build tag "validatedebug". -// -// In this mode, the pools are tracked for allocation and redemption of borrowed objects, so we can -// verify a few behaviors of the validators. The debug pools panic when an invalid usage pattern is detected. - -var pools allPools - -func init() { - resetPools() -} - -func resetPools() { - // NOTE: for testing purpose, we might want to reset pools after calling Validate twice. - // The pool is corrupted in that case: calling Put twice inserts a duplicate in the pool - // and further calls to Get are mishandled. - - pools = allPools{ - poolOfSchemaValidators: schemaValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &SchemaValidator{} - - return s - }, - }, - debugMap: make(map[*SchemaValidator]status), - allocMap: make(map[*SchemaValidator]string), - redeemMap: make(map[*SchemaValidator]string), - }, - poolOfObjectValidators: objectValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &objectValidator{} - - return s - }, - }, - debugMap: make(map[*objectValidator]status), - allocMap: make(map[*objectValidator]string), - redeemMap: make(map[*objectValidator]string), - }, - poolOfSliceValidators: sliceValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &schemaSliceValidator{} - - return s - }, - }, - debugMap: make(map[*schemaSliceValidator]status), - allocMap: make(map[*schemaSliceValidator]string), - redeemMap: make(map[*schemaSliceValidator]string), - }, - poolOfItemsValidators: itemsValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &itemsValidator{} - - return s - }, - }, - debugMap: make(map[*itemsValidator]status), - allocMap: make(map[*itemsValidator]string), - redeemMap: make(map[*itemsValidator]string), - }, - poolOfBasicCommonValidators: basicCommonValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &basicCommonValidator{} - - return s - }, - }, - debugMap: make(map[*basicCommonValidator]status), - allocMap: make(map[*basicCommonValidator]string), - redeemMap: make(map[*basicCommonValidator]string), - }, - poolOfHeaderValidators: headerValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &HeaderValidator{} - - return s - }, - }, - debugMap: make(map[*HeaderValidator]status), - allocMap: make(map[*HeaderValidator]string), - redeemMap: make(map[*HeaderValidator]string), - }, - poolOfParamValidators: paramValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &ParamValidator{} - - return s - }, - }, - debugMap: make(map[*ParamValidator]status), - allocMap: make(map[*ParamValidator]string), - redeemMap: make(map[*ParamValidator]string), - }, - poolOfBasicSliceValidators: basicSliceValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &basicSliceValidator{} - - return s - }, - }, - debugMap: make(map[*basicSliceValidator]status), - allocMap: make(map[*basicSliceValidator]string), - redeemMap: make(map[*basicSliceValidator]string), - }, - poolOfNumberValidators: numberValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &numberValidator{} - - return s - }, - }, - debugMap: make(map[*numberValidator]status), - allocMap: make(map[*numberValidator]string), - redeemMap: make(map[*numberValidator]string), - }, - poolOfStringValidators: stringValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &stringValidator{} - - return s - }, - }, - debugMap: make(map[*stringValidator]status), - allocMap: make(map[*stringValidator]string), - redeemMap: make(map[*stringValidator]string), - }, - poolOfSchemaPropsValidators: schemaPropsValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &schemaPropsValidator{} - - return s - }, - }, - debugMap: make(map[*schemaPropsValidator]status), - allocMap: make(map[*schemaPropsValidator]string), - redeemMap: make(map[*schemaPropsValidator]string), - }, - poolOfFormatValidators: formatValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &formatValidator{} - - return s - }, - }, - debugMap: make(map[*formatValidator]status), - allocMap: make(map[*formatValidator]string), - redeemMap: make(map[*formatValidator]string), - }, - poolOfTypeValidators: typeValidatorsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &typeValidator{} - - return s - }, - }, - debugMap: make(map[*typeValidator]status), - allocMap: make(map[*typeValidator]string), - redeemMap: make(map[*typeValidator]string), - }, - poolOfSchemas: schemasPool{ - Pool: &sync.Pool{ - New: func() any { - s := &spec.Schema{} - - return s - }, - }, - debugMap: make(map[*spec.Schema]status), - allocMap: make(map[*spec.Schema]string), - redeemMap: make(map[*spec.Schema]string), - }, - poolOfResults: resultsPool{ - Pool: &sync.Pool{ - New: func() any { - s := &Result{} - - return s - }, - }, - debugMap: make(map[*Result]status), - allocMap: make(map[*Result]string), - redeemMap: make(map[*Result]string), - }, - } -} - -const ( - statusFresh status = iota + 1 - statusRecycled - statusRedeemed -) - -func (s status) String() string { - switch s { - case statusFresh: - return "fresh" - case statusRecycled: - return "recycled" - case statusRedeemed: - return "redeemed" - default: - panic(fmt.Errorf("invalid status: %d", s)) - } -} - -type ( - // Debug - status uint8 - - allPools struct { - // memory pools for all validator objects. - // - // Each pool can be borrowed from and redeemed to. - poolOfSchemaValidators schemaValidatorsPool - poolOfObjectValidators objectValidatorsPool - poolOfSliceValidators sliceValidatorsPool - poolOfItemsValidators itemsValidatorsPool - poolOfBasicCommonValidators basicCommonValidatorsPool - poolOfHeaderValidators headerValidatorsPool - poolOfParamValidators paramValidatorsPool - poolOfBasicSliceValidators basicSliceValidatorsPool - poolOfNumberValidators numberValidatorsPool - poolOfStringValidators stringValidatorsPool - poolOfSchemaPropsValidators schemaPropsValidatorsPool - poolOfFormatValidators formatValidatorsPool - poolOfTypeValidators typeValidatorsPool - poolOfSchemas schemasPool - poolOfResults resultsPool - } - - schemaValidatorsPool struct { - *sync.Pool - debugMap map[*SchemaValidator]status - allocMap map[*SchemaValidator]string - redeemMap map[*SchemaValidator]string - mx sync.Mutex - } - - objectValidatorsPool struct { - *sync.Pool - debugMap map[*objectValidator]status - allocMap map[*objectValidator]string - redeemMap map[*objectValidator]string - mx sync.Mutex - } - - sliceValidatorsPool struct { - *sync.Pool - debugMap map[*schemaSliceValidator]status - allocMap map[*schemaSliceValidator]string - redeemMap map[*schemaSliceValidator]string - mx sync.Mutex - } - - itemsValidatorsPool struct { - *sync.Pool - debugMap map[*itemsValidator]status - allocMap map[*itemsValidator]string - redeemMap map[*itemsValidator]string - mx sync.Mutex - } - - basicCommonValidatorsPool struct { - *sync.Pool - debugMap map[*basicCommonValidator]status - allocMap map[*basicCommonValidator]string - redeemMap map[*basicCommonValidator]string - mx sync.Mutex - } - - headerValidatorsPool struct { - *sync.Pool - debugMap map[*HeaderValidator]status - allocMap map[*HeaderValidator]string - redeemMap map[*HeaderValidator]string - mx sync.Mutex - } - - paramValidatorsPool struct { - *sync.Pool - debugMap map[*ParamValidator]status - allocMap map[*ParamValidator]string - redeemMap map[*ParamValidator]string - mx sync.Mutex - } - - basicSliceValidatorsPool struct { - *sync.Pool - debugMap map[*basicSliceValidator]status - allocMap map[*basicSliceValidator]string - redeemMap map[*basicSliceValidator]string - mx sync.Mutex - } - - numberValidatorsPool struct { - *sync.Pool - debugMap map[*numberValidator]status - allocMap map[*numberValidator]string - redeemMap map[*numberValidator]string - mx sync.Mutex - } - - stringValidatorsPool struct { - *sync.Pool - debugMap map[*stringValidator]status - allocMap map[*stringValidator]string - redeemMap map[*stringValidator]string - mx sync.Mutex - } - - schemaPropsValidatorsPool struct { - *sync.Pool - debugMap map[*schemaPropsValidator]status - allocMap map[*schemaPropsValidator]string - redeemMap map[*schemaPropsValidator]string - mx sync.Mutex - } - - formatValidatorsPool struct { - *sync.Pool - debugMap map[*formatValidator]status - allocMap map[*formatValidator]string - redeemMap map[*formatValidator]string - mx sync.Mutex - } - - typeValidatorsPool struct { - *sync.Pool - debugMap map[*typeValidator]status - allocMap map[*typeValidator]string - redeemMap map[*typeValidator]string - mx sync.Mutex - } - - schemasPool struct { - *sync.Pool - debugMap map[*spec.Schema]status - allocMap map[*spec.Schema]string - redeemMap map[*spec.Schema]string - mx sync.Mutex - } - - resultsPool struct { - *sync.Pool - debugMap map[*Result]status - allocMap map[*Result]string - redeemMap map[*Result]string - mx sync.Mutex - } -) - -func (p *schemaValidatorsPool) BorrowValidator() *SchemaValidator { - s := p.Get().(*SchemaValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled schema should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *schemaValidatorsPool) RedeemValidator(s *SchemaValidator) { - // NOTE: s might be nil. In that case, Put is a noop. - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed schema should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed schema should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *objectValidatorsPool) BorrowValidator() *objectValidator { - s := p.Get().(*objectValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled object should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *objectValidatorsPool) RedeemValidator(s *objectValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed object should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed object should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *sliceValidatorsPool) BorrowValidator() *schemaSliceValidator { - s := p.Get().(*schemaSliceValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled schemaSliceValidator should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *sliceValidatorsPool) RedeemValidator(s *schemaSliceValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed schemaSliceValidator should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed schemaSliceValidator should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *itemsValidatorsPool) BorrowValidator() *itemsValidator { - s := p.Get().(*itemsValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled itemsValidator should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *itemsValidatorsPool) RedeemValidator(s *itemsValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed itemsValidator should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed itemsValidator should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *basicCommonValidatorsPool) BorrowValidator() *basicCommonValidator { - s := p.Get().(*basicCommonValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled basicCommonValidator should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *basicCommonValidatorsPool) RedeemValidator(s *basicCommonValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed basicCommonValidator should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed basicCommonValidator should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *headerValidatorsPool) BorrowValidator() *HeaderValidator { - s := p.Get().(*HeaderValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled HeaderValidator should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *headerValidatorsPool) RedeemValidator(s *HeaderValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed header should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed header should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *paramValidatorsPool) BorrowValidator() *ParamValidator { - s := p.Get().(*ParamValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled param should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *paramValidatorsPool) RedeemValidator(s *ParamValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed param should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed param should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *basicSliceValidatorsPool) BorrowValidator() *basicSliceValidator { - s := p.Get().(*basicSliceValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled basicSliceValidator should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *basicSliceValidatorsPool) RedeemValidator(s *basicSliceValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed basicSliceValidator should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed basicSliceValidator should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *numberValidatorsPool) BorrowValidator() *numberValidator { - s := p.Get().(*numberValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled number should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *numberValidatorsPool) RedeemValidator(s *numberValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed number should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed number should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *stringValidatorsPool) BorrowValidator() *stringValidator { - s := p.Get().(*stringValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled string should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *stringValidatorsPool) RedeemValidator(s *stringValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed string should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed string should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *schemaPropsValidatorsPool) BorrowValidator() *schemaPropsValidator { - s := p.Get().(*schemaPropsValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled param should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *schemaPropsValidatorsPool) RedeemValidator(s *schemaPropsValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed schemaProps should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed schemaProps should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *formatValidatorsPool) BorrowValidator() *formatValidator { - s := p.Get().(*formatValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled format should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *formatValidatorsPool) RedeemValidator(s *formatValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed format should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed format should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *typeValidatorsPool) BorrowValidator() *typeValidator { - s := p.Get().(*typeValidator) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled type should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *typeValidatorsPool) RedeemValidator(s *typeValidator) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed type should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic(fmt.Errorf("redeemed type should have been allocated from a fresh or recycled pointer. Got status %s, already redeamed at: %s", x, p.redeemMap[s])) - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *schemasPool) BorrowSchema() *spec.Schema { - s := p.Get().(*spec.Schema) - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled spec.Schema should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *schemasPool) RedeemSchema(s *spec.Schema) { - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed spec.Schema should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed spec.Schema should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *resultsPool) BorrowResult() *Result { - s := p.Get().(*Result).cleared() - - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - p.debugMap[s] = statusFresh - } else { - if x != statusRedeemed { - panic("recycled result should have been redeemed") - } - p.debugMap[s] = statusRecycled - } - p.allocMap[s] = caller() - - return s -} - -func (p *resultsPool) RedeemResult(s *Result) { - if s == emptyResult { - if len(s.Errors) > 0 || len(s.Warnings) > 0 { - panic("empty result should not mutate") - } - return - } - p.mx.Lock() - defer p.mx.Unlock() - x, ok := p.debugMap[s] - if !ok { - panic("redeemed Result should have been allocated") - } - if x != statusRecycled && x != statusFresh { - panic("redeemed Result should have been allocated from a fresh or recycled pointer") - } - p.debugMap[s] = statusRedeemed - p.redeemMap[s] = caller() - p.Put(s) -} - -func (p *allPools) allIsRedeemed(t testing.TB) bool { - outcome := true - for k, v := range p.poolOfSchemaValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("schemaValidator should be redeemed. Allocated by: %s", p.poolOfSchemaValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfObjectValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("objectValidator should be redeemed. Allocated by: %s", p.poolOfObjectValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfSliceValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("sliceValidator should be redeemed. Allocated by: %s", p.poolOfSliceValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfItemsValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("itemsValidator should be redeemed. Allocated by: %s", p.poolOfItemsValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfBasicCommonValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("basicCommonValidator should be redeemed. Allocated by: %s", p.poolOfBasicCommonValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfHeaderValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("headerValidator should be redeemed. Allocated by: %s", p.poolOfHeaderValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfParamValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("paramValidator should be redeemed. Allocated by: %s", p.poolOfParamValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfBasicSliceValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("basicSliceValidator should be redeemed. Allocated by: %s", p.poolOfBasicSliceValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfNumberValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("numberValidator should be redeemed. Allocated by: %s", p.poolOfNumberValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfStringValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("stringValidator should be redeemed. Allocated by: %s", p.poolOfStringValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfSchemaPropsValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("schemaPropsValidator should be redeemed. Allocated by: %s", p.poolOfSchemaPropsValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfFormatValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("formatValidator should be redeemed. Allocated by: %s", p.poolOfFormatValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfTypeValidators.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("typeValidator should be redeemed. Allocated by: %s", p.poolOfTypeValidators.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfSchemas.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("schemas should be redeemed. Allocated by: %s", p.poolOfSchemas.allocMap[k]) - outcome = false - } - for k, v := range p.poolOfResults.debugMap { - if v == statusRedeemed { - continue - } - t.Logf("result should be redeemed. Allocated by: %s", p.poolOfResults.allocMap[k]) - outcome = false - } - - return outcome -} - -func caller() string { - pc, _, _, _ := runtime.Caller(3) //nolint:dogsled - from, line := runtime.FuncForPC(pc).FileLine(pc) - - return fmt.Sprintf("%s:%d", from, line) -} diff --git a/vendor/github.com/go-openapi/validate/ref_locations.go b/vendor/github.com/go-openapi/validate/ref_locations.go new file mode 100644 index 0000000..1853340 --- /dev/null +++ b/vendor/github.com/go-openapi/validate/ref_locations.go @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "github.com/go-openapi/analysis" +) + +// refLocations tells where a $ref value is declared in a document. +// +// The analyzer indexes references the other way around, by the location each +// was found at, so the index is inverted here. Only declarations are indexed: +// a "$ref" member sitting in an example or in a default value is data, and the +// analyzer never walks into it. +type refLocations map[string]pathSegments + +// newRefLocations inverts the analyzer's reference index. +// +// A reference declared in several places keeps the smallest declaration, so +// that the answer does not depend on map iteration order. +func newRefLocations(analyzer *analysis.Spec) refLocations { + declarations := make(map[string]string) + for location, ref := range analyzer.AllRefsByLocation() { + value := ref.String() + if value == "" { + continue + } + + if known, isKnown := declarations[value]; isKnown && known <= location { + continue + } + + declarations[value] = location + } + + locations := make(refLocations, len(declarations)) + for value, location := range declarations { + locations[value] = localRefPath(location) + } + + return locations +} + +// at returns where a reference is declared, or the document root when the +// reference is not one the analyzer indexed. +func (l refLocations) at(ref string) pathSegments { + return l[ref] +} diff --git a/vendor/github.com/go-openapi/validate/ref_redirects.go b/vendor/github.com/go-openapi/validate/ref_redirects.go new file mode 100644 index 0000000..95fe96c --- /dev/null +++ b/vendor/github.com/go-openapi/validate/ref_redirects.go @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "strings" + + "github.com/go-openapi/analysis" +) + +// maxRefHops bounds how many $ref a pointer may be followed through, so that a +// document referring to itself cannot spin here. +const maxRefHops = 10 + +// refRedirects maps the location of a $ref to the location it points at, for +// the local references of a document. +// +// Checks walk the expanded document, so a finding below a $ref comes out with a +// pointer that descends into a node the authored document does not contain: a +// bare "$ref" member has nothing under it. Following the reference turns such a +// pointer back into one the document addresses. +type refRedirects map[string]string + +func newRefRedirects(analyzer *analysis.Spec) refRedirects { + redirects := make(refRedirects) + for location, ref := range analyzer.AllRefsByLocation() { + target := ref.String() + if !strings.HasPrefix(target, "#/") { + // only a local reference has a location in this document + continue + } + + redirects[strings.TrimPrefix(location, "#")] = strings.TrimPrefix(target, "#") + } + + return redirects +} + +// through rewrites a pointer that descends below a $ref. +// +// A pointer that stops at the $ref itself is left alone: that node exists, and +// it is where a reader has to go to amend the reference. +func (r refRedirects) through(pointer string) string { + if len(r) == 0 { + return pointer + } + + for range maxRefHops { + prefix, rest, ok := r.crossing(pointer) + if !ok { + return pointer + } + + pointer = prefix + rest + } + + return pointer +} + +// crossing finds the longest prefix of pointer that holds a $ref, and returns +// the location that reference points at together with what is left below it. +func (r refRedirects) crossing(pointer string) (target, rest string, ok bool) { + for at := strings.LastIndex(pointer, "/"); at > 0; at = strings.LastIndex(pointer[:at], "/") { + if target, isRef := r[pointer[:at]]; isRef { + return target, pointer[at:], true + } + } + + return "", "", false +} diff --git a/vendor/github.com/go-openapi/validate/required_walk.go b/vendor/github.com/go-openapi/validate/required_walk.go new file mode 100644 index 0000000..b3fd3cd --- /dev/null +++ b/vendor/github.com/go-openapi/validate/required_walk.go @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "strings" + + "github.com/go-openapi/errors" + "github.com/go-openapi/spec" +) + +// schemaIdentity is how a message refers to the schema a required entry +// belongs to. +// +// A definition is named on its own, the way it always has been. A schema the +// definition holds is named by the way down to it, relative to the definition: +// "A.inner" rather than "A", which would send a reader to the wrong place. +type schemaIdentity struct { + name string + nested bool +} + +// identify names the schema a location leads to. +func identify(at pathSegments) schemaIdentity { + const definitionDepth = 2 // "definitions", then the name of one + + return schemaIdentity{ + name: strings.TrimPrefix(at.dotted(), swaggerDefinitions+"."), + nested: len(at) > definitionDepth, + } +} + +func (i schemaIdentity) requiredButNotDefined(property string) errors.Error { + if i.nested { + return requiredButNotDefinedInSchemaMsg(property, i.name) + } + + return requiredButNotDefinedMsg(property, i.name) +} + +// maxCompositionHops bounds how far the search for a declared property follows +// allOf members and the local $ref they may be written as. +const maxCompositionHops = 20 + +// walkRequired checks the required entries of a schema, then those of every +// schema it holds inline. +// +// A definition is not the only place a document says an object must hold a +// property: so does the schema of a property, of an array item, of an +// additionalProperties. Each of those is a self-contained object definition, +// and a required entry naming something it never declares is the same slip +// wherever it sits. +// +// A schema written as a $ref is left alone: it is checked where it is defined, +// and following it here would report the same slip twice and, for a recursive +// definition, would not terminate. +// +// It reports whether the walk should carry on, which is how the caller stops on +// the first fault unless it was asked for everything. +func (s *SpecValidator) walkRequired(at pathSegments, v *spec.Schema, res *Result) bool { + if v == nil || v.Ref.String() != "" { + return true + } + + for i, pn := range v.Required { + // the offending entry of the required array, not the schema holding + // it: that is what a reader has to go and amend + red := s.validateRequiredProperties(pn, identify(at), at, at.child(jsonRequired).item(i), v) + // NOTE: capture validity before merging: Merge may redeem `red` to the + // pool (wantsRedeemOnMerge), after which reading it races with a + // concurrent BorrowResult().cleared() in another goroutine. + isValid := red.IsValid() + res.Merge(red) + if !isValid && !s.Options.ContinueOnErrors { + return false + } + } + + return s.walkInlineSchemas(at, v, res) +} + +// walkInlineSchemas descends into every schema a schema holds, without checking +// the required entries of a composition member. +// +// Inside allOf, anyOf, oneOf or not, a member is a fragment of a constraint +// rather than a complete definition: its required entries speak of the instance +// the whole composition describes, and are legitimately met by a sibling member +// or by no declaration at all. Those are honoured when data is validated, and +// saying anything about them here would be wrong. Their own members are still +// walked, because a property schema nested in one of them is a definition like +// any other. +func (s *SpecValidator) walkInlineSchemas(at pathSegments, v *spec.Schema, res *Result) bool { + for _, name := range sortedKeys(v.Properties) { + held := v.Properties[name] + if !s.walkRequired(at.structuralChild(jsonProperties).child(name), &held, res) { + return false + } + } + + for _, pattern := range sortedKeys(v.PatternProperties) { + held := v.PatternProperties[pattern] + if !s.walkRequired(at.structuralChild(jsonPatternProperties).child(pattern), &held, res) { + return false + } + } + + if v.Items != nil { + if v.Items.Schema != nil && !s.walkRequired(at.child(jsonItems), v.Items.Schema, res) { + return false + } + for i := range v.Items.Schemas { + if !s.walkRequired(at.child(jsonItems).item(i), &v.Items.Schemas[i], res) { + return false + } + } + } + + if v.AdditionalProperties != nil && v.AdditionalProperties.Schema != nil && + !s.walkRequired(at.child(jsonAdditionalProperties), v.AdditionalProperties.Schema, res) { + return false + } + + // a composition member is walked for the schemas it holds, never for its + // own required entries + for _, composition := range []struct { + keyword string + members []spec.Schema + }{ + {jsonAllOf, v.AllOf}, + {jsonAnyOf, v.AnyOf}, + {jsonOneOf, v.OneOf}, + } { + for i := range composition.members { + if !s.walkInlineSchemas(at.child(composition.keyword).item(i), &composition.members[i], res) { + return false + } + } + } + + if v.Not != nil { + return s.walkInlineSchemas(at.child(jsonNot), v.Not, res) + } + + return true +} + +// declaresProperty reports whether a schema, or any schema composed into it by +// allOf, declares the named property, and whether that declaration is readOnly. +// +// An allOf member may be written as a $ref, which is followed here: a property +// contributed by a base definition is declared just as plainly as one written +// in place. +func (s *SpecValidator) declaresProperty(v *spec.Schema, name string, hops int) (readOnly, declared bool) { + if v == nil || hops <= 0 { + return false, false + } + + if held, ok := v.Properties[name]; ok { + return held.ReadOnly, true + } + + for i := range v.AllOf { + member := &v.AllOf[i] + if member.Ref.String() != "" { + resolved, err := s.resolveRef(&member.Ref) + if err != nil { + continue + } + member = resolved + } + + if readOnly, ok := s.declaresProperty(member, name, hops-1); ok { + return readOnly, true + } + } + + return false, false +} diff --git a/vendor/github.com/go-openapi/validate/resolvable.go b/vendor/github.com/go-openapi/validate/resolvable.go new file mode 100644 index 0000000..bbd35d3 --- /dev/null +++ b/vendor/github.com/go-openapi/validate/resolvable.go @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "strconv" + "strings" + + "github.com/go-openapi/jsonpointer" +) + +// resolvable trims a pointer down to the deepest node the document holds. +// +// Checks walk an expanded, model-level view of a specification, which holds +// members the document itself never wrote: a parameter merged in from a path +// item, a member of a schema reached through a $ref. A pointer built along the +// way may therefore end on something a reader cannot go to. +// +// Trimming is the last word on a location, applied once every check has had its +// say: it only ever shortens, so a pointer that already addressed a node comes +// back untouched, and one that did not still says as much as it truthfully can. +// This is what makes [Located.Pointer] always resolve. +func (s *SpecValidator) resolvable(pointer string) string { + if pointer == "" || s.document == nil { + return pointer + } + + node := s.document + for at := 0; at < len(pointer); { + end := strings.IndexByte(pointer[at+1:], '/') + token := pointer[at+1:] + if end >= 0 { + token = pointer[at+1 : at+1+end] + } + + member, isHeld := memberOf(node, jsonpointer.Unescape(token)) + if !isHeld { + return pointer[:at] + } + node = member + + if end < 0 { + break + } + at += end + 1 + } + + return pointer +} + +// memberOf returns the member a reference token addresses in a decoded JSON +// node, and whether the node holds one at all. +func memberOf(node any, token string) (any, bool) { + switch held := node.(type) { + case map[string]any: + member, isHeld := held[token] + + return member, isHeld + case []any: + index, err := strconv.Atoi(token) + if err != nil || index < 0 || index >= len(held) { + return nil, false + } + + return held[index], true + default: + return nil, false + } +} diff --git a/vendor/github.com/go-openapi/validate/result.go b/vendor/github.com/go-openapi/validate/result.go index ede9455..5684774 100644 --- a/vendor/github.com/go-openapi/validate/result.go +++ b/vendor/github.com/go-openapi/validate/result.go @@ -14,6 +14,25 @@ import ( var emptyResult = &Result{MatchCount: 1} +// Located pairs a validation error with the location of the value that caused it. +type Located struct { + // Err is the reported error or warning. + Err error + + // Pointer locates the offending value as an RFC 6901 JSON pointer, + // relative to the validated document. + // + // It is empty when the document as a whole is the answer: either because + // the finding is about the document rather than a value in it, such as a + // duplicate operation id, or because the value in question is the root. + // An empty pointer is a valid one, addressing the whole document. + // + // A finding about something a document does not contain, a missing + // required property say, is located on the value that should contain it: + // what is absent has no node to point at. + Pointer string +} + // Result represents a validation result set, composed of // errors and warnings. // @@ -25,12 +44,17 @@ var emptyResult = &Result{MatchCount: 1} // schema validation. Results from the validation branch // with most matches get eventually selected. // -// Proposal for enhancement: keep path of key originating the error. +// Use [Result.LocatedErrors] to know where each error happened. type Result struct { Errors []error Warnings []error MatchCount int + // errorLocations[i] locates Errors[i], and likewise for warnings. Kept + // aligned by the add methods; see [Result.LocatedErrors]. + errorLocations []string + warningLocations []string + // the object data data any @@ -110,7 +134,7 @@ func (r *Result) Merge(others ...*Result) *Result { r.mergeWithoutRootSchemata(other) r.rootObjectSchemata.Append(other.rootObjectSchemata) if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } } return r @@ -173,11 +197,11 @@ func (r *Result) MergeAsErrors(others ...*Result) *Result { for _, other := range others { if other != nil { r.resetCaches() - r.AddErrors(other.Errors...) - r.AddErrors(other.Warnings...) + r.carryErrors(other.Errors, other.errorLocations) + r.carryErrors(other.Warnings, other.warningLocations) r.MatchCount += other.MatchCount if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } } } @@ -191,11 +215,11 @@ func (r *Result) MergeAsWarnings(others ...*Result) *Result { for _, other := range others { if other != nil { r.resetCaches() - r.AddWarnings(other.Errors...) - r.AddWarnings(other.Warnings...) + r.carryWarnings(other.Errors, other.errorLocations) + r.carryWarnings(other.Warnings, other.warningLocations) r.MatchCount += other.MatchCount if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } } } @@ -207,39 +231,79 @@ func (r *Result) MergeAsWarnings(others ...*Result) *Result { // Since the same check may be passed several times while exploring the // spec structure (via $ref, ...) reported messages are kept // unique. +// +// Errors added this way carry no location. Validators use [Result.addErrorsAt] +// so that [Result.LocatedErrors] can tell where the failure happened. func (r *Result) AddErrors(errors ...error) { - for _, e := range errors { - found := false - if e != nil { - for _, isReported := range r.Errors { - if e.Error() == isReported.Error() { - found = true - break - } - } - if !found { - r.Errors = append(r.Errors, e) - } - } - } + r.addLocatedErrors("", errors...) } // AddWarnings adds warnings to this validation result (if not already reported). func (r *Result) AddWarnings(warnings ...error) { - for _, e := range warnings { - found := false - if e != nil { - for _, isReported := range r.Warnings { - if e.Error() == isReported.Error() { - found = true - break - } - } - if !found { - r.Warnings = append(r.Warnings, e) - } + r.addLocatedWarnings("", warnings...) +} + +// isReportedError tells if the same message is already part of a collection. +func isReportedError(reported []error, e error) bool { + msg := e.Error() + for _, isReported := range reported { + if msg == isReported.Error() { + return true } } + + return false +} + +// locationAt reads a location out of a slice that may be shorter than the +// errors it describes. +func locationAt(locations []string, i int) string { + if i < len(locations) { + return locations[i] + } + + return "" +} + +// appendLocation records the location of the error that has just been appended, +// keeping the location slice aligned with the error slice it describes. +// +// Errors may reach a Result without going through the methods here (a caller +// assigning Errors directly, say), so the slice is padded rather than assumed +// to be in step. +func appendLocation(locations []string, upTo int, pointer string) []string { + for len(locations) < upTo-1 { + locations = append(locations, "") + } + + return append(locations, pointer) +} + +// LocatedErrors returns the reported errors, each paired with the JSON pointer +// of the value that caused it. +// +// The pointer is empty whenever the location is unknown, so callers should +// treat it as a hint and keep using the error message as the primary report. +func (r *Result) LocatedErrors() []Located { + return locate(r.Errors, r.errorLocations) +} + +// LocatedWarnings returns the reported warnings, each paired with the JSON +// pointer of the value that caused it. +func (r *Result) LocatedWarnings() []Located { + return locate(r.Warnings, r.warningLocations) +} + +func locate(errs []error, locations []string) []Located { + located := make([]Located, len(errs)) + for i, err := range errs { + located[i] = Located{Err: err} + if i < len(locations) { + located[i].Pointer = locations[i] + } + } + + return located } // IsValid returns true when this result is valid. @@ -298,6 +362,106 @@ func (r *Result) AsError() error { return errors.CompositeValidationError(r.Errors...) } +// Reset clears this result so it may be reused, keeping allocated capacity. +// +// It implements the hook the pool calls when a result is borrowed and when it +// is redeemed. Calling it on a result still in use loses its findings. +func (r *Result) Reset() { + _ = r.cleared() +} + +// addErrorsAt adds errors located at the given path. +func (r *Result) addErrorsAt(at pathSegments, errors ...error) { + r.addLocatedErrors(at.pointer(), errors...) +} + +// addWarningsAt adds warnings located at the given path. +func (r *Result) addWarningsAt(at pathSegments, warnings ...error) { + r.addLocatedWarnings(at.pointer(), warnings...) +} + +func (r *Result) addLocatedErrors(pointer string, errors ...error) { + for _, e := range errors { + if e == nil { + continue + } + + if isReportedError(r.Errors, e) { + continue + } + + r.Errors = append(r.Errors, e) + r.errorLocations = appendLocation(r.errorLocations, len(r.Errors), pointer) + } +} + +func (r *Result) addLocatedWarnings(pointer string, warnings ...error) { + for _, e := range warnings { + if e == nil { + continue + } + + if isReportedError(r.Warnings, e) { + continue + } + + r.Warnings = append(r.Warnings, e) + r.warningLocations = appendLocation(r.warningLocations, len(r.Warnings), pointer) + } +} + +// relocate rewrites every location this result recorded. +// +// The parameter and header validators are the ones a generated client uses at +// runtime, so they locate a finding by the name of the parameter or header it +// concerns: a name is all the caller has. When spec validation borrows them to +// check a default or an example, that name addresses nothing in the document, +// and the value's own node is the best location available for everything the +// borrowed validator found. +func (r *Result) relocate(at pathSegments) { + if r == nil { + return + } + + pointer := at.pointer() + r.errorLocations = fillLocations(r.errorLocations[:0], len(r.Errors), pointer) + r.warningLocations = fillLocations(r.warningLocations[:0], len(r.Warnings), pointer) +} + +// fillLocations records the same location for a whole run of findings. +func fillLocations(locations []string, count int, pointer string) []string { + for range count { + locations = append(locations, pointer) + } + + return locations +} + +// redirect rewrites every location this result recorded with the given mapping. +func (r *Result) redirect(through func(string) string) { + for i, pointer := range r.errorLocations { + r.errorLocations[i] = through(pointer) + } + for i, pointer := range r.warningLocations { + r.warningLocations[i] = through(pointer) + } +} + +// carryErrors adds errors from another result as errors, one by one, so that +// each keeps the location that result recorded for it. +func (r *Result) carryErrors(errs []error, locations []string) { + for i, e := range errs { + r.addLocatedErrors(locationAt(locations, i), e) + } +} + +// carryWarnings adds errors from another result as warnings, keeping locations. +func (r *Result) carryWarnings(errs []error, locations []string) { + for i, e := range errs { + r.addLocatedWarnings(locationAt(locations, i), e) + } +} + func (r *Result) resetCaches() { r.cachedFieldSchemata = nil r.cachedItemSchemata = nil @@ -324,7 +488,7 @@ func (r *Result) mergeForField(obj map[string]any, field string, other *Result) }) } if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } return r @@ -352,7 +516,7 @@ func (r *Result) mergeForSlice(slice reflect.Value, i int, other *Result) *Resul } if other.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(other) + redeemResult(other) } return r @@ -391,8 +555,8 @@ func (r *Result) addSliceSchemata(slice reflect.Value, i int, schema *spec.Schem // mergeWithoutRootSchemata merges other into r, ignoring the rootObject schemata. func (r *Result) mergeWithoutRootSchemata(other *Result) { r.resetCaches() - r.AddErrors(other.Errors...) - r.AddWarnings(other.Warnings...) + r.carryErrors(other.Errors, other.errorLocations) + r.carryWarnings(other.Warnings, other.warningLocations) r.MatchCount += other.MatchCount if other.fieldSchemata != nil { @@ -438,32 +602,40 @@ func (r *Result) keepRelevantErrors() *Result { // codes would require to change a lot here. So, for the moment, let's go with // placeholders. strippedErrors := []error{} - for _, e := range r.Errors { + strippedErrorLocations := []string{} + for i, e := range r.Errors { if isImportant(e) { strippedErrors = append(strippedErrors, stripImportantTag(e)) + strippedErrorLocations = append(strippedErrorLocations, locationAt(r.errorLocations, i)) } } strippedWarnings := []error{} - for _, e := range r.Warnings { + strippedWarningLocations := []string{} + for i, e := range r.Warnings { if isImportant(e) { strippedWarnings = append(strippedWarnings, stripImportantTag(e)) + strippedWarningLocations = append(strippedWarningLocations, locationAt(r.warningLocations, i)) } } var strippedResult *Result if r.wantsRedeemOnMerge { - strippedResult = pools.poolOfResults.BorrowResult() + strippedResult = validatorPools.results.Borrow() } else { strippedResult = new(Result) } strippedResult.Errors = strippedErrors + strippedResult.errorLocations = strippedErrorLocations strippedResult.Warnings = strippedWarnings + strippedResult.warningLocations = strippedWarningLocations return strippedResult } func (r *Result) cleared() *Result { // clear the Result to be reusable. Keep allocated capacity. r.Errors = r.Errors[:0] + r.errorLocations = r.errorLocations[:0] r.Warnings = r.Warnings[:0] + r.warningLocations = r.warningLocations[:0] r.MatchCount = 0 r.data = nil r.rootObjectSchemata.one = nil diff --git a/vendor/github.com/go-openapi/validate/schema.go b/vendor/github.com/go-openapi/validate/schema.go index 706b7f5..e7af892 100644 --- a/vendor/github.com/go-openapi/validate/schema.go +++ b/vendor/github.com/go-openapi/validate/schema.go @@ -15,7 +15,17 @@ import ( // SchemaValidator validates data against a JSON schema. type SchemaValidator struct { - Path string + // Path is the location of the validated value, in the legacy dot-separated + // notation. It is what surfaces as the name of a validation error. + // + // Deprecated: a dotted path is ambiguous whenever a property name contains + // a dot. Prefer the JSON pointer rendering of the same location. + Path string + + // path is the same location, kept as JSON pointer reference tokens so that + // children may be derived from it unambiguously. + path pathSegments + in string Schema *spec.Schema validators [8]valueValidator @@ -32,7 +42,7 @@ func AgainstSchema(schema *spec.Schema, data any, formats strfmt.Registry, optio append(options, WithRecycleValidators(true), withRecycleResults(true))..., ).Validate(data) defer func() { - pools.poolOfResults.RedeemResult(res) + redeemResult(res) }() if res.HasErrors() { @@ -51,10 +61,23 @@ func NewSchemaValidator(schema *spec.Schema, rootSchema any, root string, format o(opts) } - return newSchemaValidator(schema, rootSchema, root, formats, opts) + return newSchemaValidator(schema, rootSchema, rootPathFromString(root), formats, opts) +} + +// rootPathFromString interprets the root path of the exported constructors. +// +// The caller hands over an opaque string, so there is no telling which of its +// dots are separators and which belong to a name: it is taken as a single +// reference token. +func rootPathFromString(root string) pathSegments { + if root == "" { + return rootPath() + } + + return newPathSegments(root) } -func newSchemaValidator(schema *spec.Schema, rootSchema any, root string, formats strfmt.Registry, opts *SchemaValidatorOptions) *SchemaValidator { +func newSchemaValidator(schema *spec.Schema, rootSchema any, root pathSegments, formats strfmt.Registry, opts *SchemaValidatorOptions) *SchemaValidator { if schema == nil { return nil } @@ -77,12 +100,13 @@ func newSchemaValidator(schema *spec.Schema, rootSchema any, root string, format var s *SchemaValidator if opts.recycleValidators { - s = pools.poolOfSchemaValidators.BorrowValidator() + s = validatorPools.schemaValidators.Borrow() } else { s = new(SchemaValidator) } - s.Path = root + s.path = root + s.Path = root.dotted() s.in = "body" s.Schema = schema s.Root = rootSchema @@ -104,8 +128,11 @@ func newSchemaValidator(schema *spec.Schema, rootSchema any, root string, format } // SetPath sets the path for this schema validator. +// +// Note that the sub-validators are built when the validator is created, so +// this only affects errors reported by this validator, not by its children. func (s *SchemaValidator) SetPath(path string) { - s.Path = path + s.setPath(rootPathFromString(path)) } // Applies returns true when this schema validator applies. @@ -131,7 +158,7 @@ func (s *SchemaValidator) Validate(data any) *Result { var result *Result if s.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() result.data = data } else { result = &Result{data: data} @@ -169,7 +196,7 @@ func (s *SchemaValidator) Validate(data any) *Result { // to map[string]interface{}. var dd any if err := jsonutils.FromDynamicJSON(data, &dd); err != nil { - result.AddErrors(err) + result.addErrorsAt(s.path, err) result.Inc() return result @@ -185,7 +212,7 @@ func (s *SchemaValidator) Validate(data any) *Result { if s.Schema.Type.Contains(integerType) { // avoid lossy conversion in, erri := num.Int64() if erri != nil { - result.AddErrors(invalidTypeConversionMsg(s.Path, erri)) + result.addErrorsAt(s.path, invalidTypeConversionMsg(s.Path, erri)) result.Inc() return result @@ -194,7 +221,7 @@ func (s *SchemaValidator) Validate(data any) *Result { } else { nf, errf := num.Float64() if errf != nil { - result.AddErrors(invalidTypeConversionMsg(s.Path, errf)) + result.addErrorsAt(s.path, invalidTypeConversionMsg(s.Path, errf)) result.Inc() return result @@ -235,7 +262,7 @@ func (s *SchemaValidator) Validate(data any) *Result { func (s *SchemaValidator) typeValidator() valueValidator { return newTypeValidator( - s.Path, + s.path, s.in, s.Schema.Type, s.Schema.Nullable, @@ -246,7 +273,7 @@ func (s *SchemaValidator) typeValidator() valueValidator { func (s *SchemaValidator) commonValidator() valueValidator { return newBasicCommonValidator( - s.Path, + s.path, s.in, s.Schema.Default, s.Schema.Enum, @@ -256,7 +283,7 @@ func (s *SchemaValidator) commonValidator() valueValidator { func (s *SchemaValidator) sliceValidator() valueValidator { return newSliceValidator( - s.Path, + s.path, s.in, s.Schema.MaxItems, s.Schema.MinItems, @@ -271,7 +298,7 @@ func (s *SchemaValidator) sliceValidator() valueValidator { func (s *SchemaValidator) numberValidator() valueValidator { return newNumberValidator( - s.Path, + s.path, s.in, s.Schema.Default, s.Schema.MultipleOf, @@ -287,7 +314,7 @@ func (s *SchemaValidator) numberValidator() valueValidator { func (s *SchemaValidator) stringValidator() valueValidator { return newStringValidator( - s.Path, + s.path, s.in, nil, false, @@ -301,7 +328,7 @@ func (s *SchemaValidator) stringValidator() valueValidator { func (s *SchemaValidator) formatValidator() valueValidator { return newFormatValidator( - s.Path, + s.path, s.in, s.Schema.Format, s.KnownFormats, @@ -312,14 +339,14 @@ func (s *SchemaValidator) formatValidator() valueValidator { func (s *SchemaValidator) schemaPropsValidator() valueValidator { sch := s.Schema return newSchemaPropsValidator( - s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats, + s.path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats, s.Options, ) } func (s *SchemaValidator) objectValidator() valueValidator { return newObjectValidator( - s.Path, + s.path, s.in, s.Schema.MaxProperties, s.Schema.MinProperties, @@ -333,8 +360,13 @@ func (s *SchemaValidator) objectValidator() valueValidator { ) } +func (s *SchemaValidator) setPath(path pathSegments) { + s.path = path + s.Path = path.dotted() +} + func (s *SchemaValidator) redeem() { - pools.poolOfSchemaValidators.RedeemValidator(s) + validatorPools.schemaValidators.Redeem(s) } func (s *SchemaValidator) redeemChildren() { diff --git a/vendor/github.com/go-openapi/validate/schema_props.go b/vendor/github.com/go-openapi/validate/schema_props.go index 2c4354d..d7ecb14 100644 --- a/vendor/github.com/go-openapi/validate/schema_props.go +++ b/vendor/github.com/go-openapi/validate/schema_props.go @@ -12,7 +12,7 @@ import ( ) type schemaPropsValidator struct { - Path string + Path pathSegments In string AllOf []spec.Schema OneOf []spec.Schema @@ -28,12 +28,8 @@ type schemaPropsValidator struct { Options *SchemaValidatorOptions } -func (s *schemaPropsValidator) SetPath(path string) { - s.Path = path -} - func newSchemaPropsValidator( - path string, in string, allOf, oneOf, anyOf []spec.Schema, not *spec.Schema, deps spec.Dependencies, root any, formats strfmt.Registry, + path pathSegments, in string, allOf, oneOf, anyOf []spec.Schema, not *spec.Schema, deps spec.Dependencies, root any, formats strfmt.Registry, opts *SchemaValidatorOptions, ) *schemaPropsValidator { if opts == nil { @@ -60,7 +56,7 @@ func newSchemaPropsValidator( var s *schemaPropsValidator if opts.recycleValidators { - s = pools.poolOfSchemaPropsValidators.BorrowValidator() + s = validatorPools.schemaPropsValidators.Borrow() } else { s = new(schemaPropsValidator) } @@ -91,7 +87,7 @@ func (s *schemaPropsValidator) Applies(source any, _ reflect.Kind) bool { func (s *schemaPropsValidator) Validate(data any) *Result { var mainResult *Result if s.Options.recycleResult { - mainResult = pools.poolOfResults.BorrowResult() + mainResult = validatorPools.results.Borrow() } else { mainResult = new(Result) } @@ -111,17 +107,17 @@ func (s *schemaPropsValidator) Validate(data any) *Result { } if len(s.anyOfValidators) > 0 { - keepResultAnyOf = pools.poolOfResults.BorrowResult() + keepResultAnyOf = validatorPools.results.Borrow() s.validateAnyOf(data, mainResult, keepResultAnyOf) } if len(s.oneOfValidators) > 0 { - keepResultOneOf = pools.poolOfResults.BorrowResult() + keepResultOneOf = validatorPools.results.Borrow() s.validateOneOf(data, mainResult, keepResultOneOf) } if len(s.allOfValidators) > 0 { - keepResultAllOf = pools.poolOfResults.BorrowResult() + keepResultAllOf = validatorPools.results.Borrow() s.validateAllOf(data, mainResult, keepResultAllOf) } @@ -154,7 +150,7 @@ func (s *schemaPropsValidator) validateAnyOf(data any, mainResult, keepResultAny if result.IsValid() { if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } _ = keepResultAnyOf.cleared() @@ -166,7 +162,7 @@ func (s *schemaPropsValidator) validateAnyOf(data any, mainResult, keepResultAny // MatchCount is used to select errors from the schema with most positive checks if bestFailures == nil || result.MatchCount > bestFailures.MatchCount { if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } bestFailures = result @@ -174,11 +170,11 @@ func (s *schemaPropsValidator) validateAnyOf(data any, mainResult, keepResultAny } if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } } - mainResult.AddErrors(mustValidateAtLeastOneSchemaMsg(s.Path)) + mainResult.addErrorsAt(s.Path, mustValidateAtLeastOneSchemaMsg(s.Path.dotted())) mainResult.Merge(bestFailures) } @@ -205,7 +201,7 @@ func (s *schemaPropsValidator) validateOneOf(data any, mainResult, keepResultOne if firstSuccess == nil { firstSuccess = result } else if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } continue @@ -214,29 +210,29 @@ func (s *schemaPropsValidator) validateOneOf(data any, mainResult, keepResultOne // MatchCount is used to select errors from the schema with most positive checks if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) { if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } bestFailures = result } else if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } } switch validated { case 0: - mainResult.AddErrors(mustValidateOnlyOneSchemaMsg(s.Path, "Found none valid")) + mainResult.addErrorsAt(s.Path, mustValidateOnlyOneSchemaMsg(s.Path.dotted(), "Found none valid")) mainResult.Merge(bestFailures) // firstSucess necessarily nil case 1: mainResult.Merge(firstSuccess) if bestFailures != nil && bestFailures.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(bestFailures) + redeemResult(bestFailures) } default: - mainResult.AddErrors(mustValidateOnlyOneSchemaMsg(s.Path, fmt.Sprintf("Found %d valid alternatives", validated))) + mainResult.addErrorsAt(s.Path, mustValidateOnlyOneSchemaMsg(s.Path.dotted(), fmt.Sprintf("Found %d valid alternatives", validated))) mainResult.Merge(bestFailures) if firstSuccess != nil && firstSuccess.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(firstSuccess) + redeemResult(firstSuccess) } } } @@ -260,10 +256,10 @@ func (s *schemaPropsValidator) validateAllOf(data any, mainResult, keepResultAll switch validated { case 0: - mainResult.AddErrors(mustValidateAllSchemasMsg(s.Path, ". None validated")) + mainResult.addErrorsAt(s.Path, mustValidateAllSchemasMsg(s.Path.dotted(), ". None validated")) case len(s.allOfValidators): default: - mainResult.AddErrors(mustValidateAllSchemasMsg(s.Path, "")) + mainResult.addErrorsAt(s.Path, mustValidateAllSchemasMsg(s.Path.dotted(), "")) } } @@ -274,16 +270,16 @@ func (s *schemaPropsValidator) validateNot(data any, mainResult *Result) { } // We keep inner IMPORTANT! errors no matter what MatchCount tells us if result.IsValid() { - mainResult.AddErrors(mustNotValidatechemaMsg(s.Path)) + mainResult.addErrorsAt(s.Path, mustNotValidatechemaMsg(s.Path.dotted())) } if result.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(result) // this result is ditched + redeemResult(result) // this result is ditched } } func (s *schemaPropsValidator) validateDependencies(data any, mainResult *Result) { val := data.(map[string]any) //nolint:forcetypeassert // caller guarantees map[string]any - for key := range val { + for _, key := range sortedKeys(val) { dep, ok := s.Dependencies[key] if !ok { continue @@ -291,7 +287,7 @@ func (s *schemaPropsValidator) validateDependencies(data any, mainResult *Result if dep.Schema != nil { mainResult.Merge( - newSchemaValidator(dep.Schema, s.Root, s.Path+"."+key, s.KnownFormats, s.Options).Validate(data), + newSchemaValidator(dep.Schema, s.Root, s.Path.child(key), s.KnownFormats, s.Options).Validate(data), ) continue } @@ -299,15 +295,19 @@ func (s *schemaPropsValidator) validateDependencies(data any, mainResult *Result if len(dep.Property) > 0 { for _, depKey := range dep.Property { if _, ok := val[depKey]; !ok { - mainResult.AddErrors(hasADependencyMsg(s.Path, depKey)) + mainResult.addErrorsAt(s.Path, hasADependencyMsg(s.Path.dotted(), depKey)) } } } } } +func (s *schemaPropsValidator) setPath(path pathSegments) { + s.Path = path +} + func (s *schemaPropsValidator) redeem() { - pools.poolOfSchemaPropsValidators.RedeemValidator(s) + validatorPools.schemaPropsValidators.Redeem(s) } func (s *schemaPropsValidator) redeemChildren() { diff --git a/vendor/github.com/go-openapi/validate/slice_validator.go b/vendor/github.com/go-openapi/validate/slice_validator.go index 8f49d13..0a0ee74 100644 --- a/vendor/github.com/go-openapi/validate/slice_validator.go +++ b/vendor/github.com/go-openapi/validate/slice_validator.go @@ -4,7 +4,6 @@ package validate import ( - "fmt" "reflect" "github.com/go-openapi/spec" @@ -12,7 +11,7 @@ import ( ) type schemaSliceValidator struct { - Path string + Path pathSegments In string MaxItems *int64 MinItems *int64 @@ -24,7 +23,7 @@ type schemaSliceValidator struct { Options *SchemaValidatorOptions } -func newSliceValidator(path, in string, +func newSliceValidator(path pathSegments, in string, maxItems, minItems *int64, uniqueItems bool, additionalItems *spec.SchemaOrBool, items *spec.SchemaOrArray, root any, formats strfmt.Registry, opts *SchemaValidatorOptions, @@ -35,7 +34,7 @@ func newSliceValidator(path, in string, var v *schemaSliceValidator if opts.recycleValidators { - v = pools.poolOfSliceValidators.BorrowValidator() + v = validatorPools.sliceValidators.Borrow() } else { v = new(schemaSliceValidator) } @@ -54,10 +53,6 @@ func newSliceValidator(path, in string, return v } -func (s *schemaSliceValidator) SetPath(path string) { - s.Path = path -} - func (s *schemaSliceValidator) Applies(source any, kind reflect.Kind) bool { _, ok := source.(*spec.Schema) r := ok && kind == reflect.Slice @@ -73,7 +68,7 @@ func (s *schemaSliceValidator) Validate(data any) *Result { var result *Result if s.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -85,8 +80,10 @@ func (s *schemaSliceValidator) Validate(data any) *Result { if s.Items != nil && s.Items.Schema != nil { for i := range size { - validator := newSchemaValidator(s.Items.Schema, s.Root, s.Path, s.KnownFormats, s.Options) - validator.SetPath(fmt.Sprintf("%s.%d", s.Path, i)) + // the index has to reach the constructor: the sub-validators that + // report the error are built there, and setting the path afterwards + // would leave them located on the array rather than on the item. + validator := newSchemaValidator(s.Items.Schema, s.Root, s.Path.item(i), s.KnownFormats, s.Options) value := val.Index(i) result.mergeForSlice(val, i, validator.Validate(value.Interface())) } @@ -100,41 +97,45 @@ func (s *schemaSliceValidator) Validate(data any) *Result { break } - validator := newSchemaValidator(&s.Items.Schemas[i], s.Root, fmt.Sprintf("%s.%d", s.Path, i), s.KnownFormats, s.Options) + validator := newSchemaValidator(&s.Items.Schemas[i], s.Root, s.Path.item(i), s.KnownFormats, s.Options) result.mergeForSlice(val, i, validator.Validate(val.Index(i).Interface())) } } if s.AdditionalItems != nil && itemsSize < size { if s.Items != nil && len(s.Items.Schemas) > 0 && !s.AdditionalItems.Allows { - result.AddErrors(arrayDoesNotAllowAdditionalItemsMsg()) + result.addErrorsAt(s.Path, arrayDoesNotAllowAdditionalItemsMsg()) } if s.AdditionalItems.Schema != nil { for i := itemsSize; i < size-itemsSize+1; i++ { - validator := newSchemaValidator(s.AdditionalItems.Schema, s.Root, fmt.Sprintf("%s.%d", s.Path, i), s.KnownFormats, s.Options) + validator := newSchemaValidator(s.AdditionalItems.Schema, s.Root, s.Path.item(i), s.KnownFormats, s.Options) result.mergeForSlice(val, i, validator.Validate(val.Index(i).Interface())) } } } if s.MinItems != nil { - if err := MinItems(s.Path, s.In, int64(size), *s.MinItems); err != nil { - result.AddErrors(err) + if err := MinItems(s.Path.dotted(), s.In, int64(size), *s.MinItems); err != nil { + result.addErrorsAt(s.Path, err) } } if s.MaxItems != nil { - if err := MaxItems(s.Path, s.In, int64(size), *s.MaxItems); err != nil { - result.AddErrors(err) + if err := MaxItems(s.Path.dotted(), s.In, int64(size), *s.MaxItems); err != nil { + result.addErrorsAt(s.Path, err) } } if s.UniqueItems { - if err := UniqueItems(s.Path, s.In, val.Interface()); err != nil { - result.AddErrors(err) + if err := UniqueItems(s.Path.dotted(), s.In, val.Interface()); err != nil { + result.addErrorsAt(s.Path, err) } } result.Inc() return result } +func (s *schemaSliceValidator) setPath(path pathSegments) { + s.Path = path +} + func (s *schemaSliceValidator) redeem() { - pools.poolOfSliceValidators.RedeemValidator(s) + validatorPools.sliceValidators.Redeem(s) } diff --git a/vendor/github.com/go-openapi/validate/sorted.go b/vendor/github.com/go-openapi/validate/sorted.go new file mode 100644 index 0000000..829a042 --- /dev/null +++ b/vendor/github.com/go-openapi/validate/sorted.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 + +package validate + +import ( + "cmp" + "maps" + "slices" + "strings" + + "github.com/go-openapi/spec" +) + +// sortedKeys returns the keys of a map in ascending order. +// +// Findings are reported in the order the checks walk the document, and several +// of them walk a map: definitions, paths, response codes, headers, properties. +// Go randomises map iteration, so the same bytes validated twice would list +// their findings in a different order — and where a check stops on the first +// fault it meets, would name a different offender altogether. +// +// Walking keys in sorted order makes both defined: findings come out in +// definition-name (or path, or status-code) order, on every run. +func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K { + return slices.Sorted(maps.Keys(m)) +} + +// sortedRefs orders references by the location they point at. +// +// The analyzer gathers references in a map, so the slice it hands back comes +// out in a different order on every run. See [sortedKeys]. +func sortedRefs(refs []spec.Ref) []spec.Ref { + slices.SortFunc(refs, func(a, b spec.Ref) int { + return strings.Compare(a.String(), b.String()) + }) + + return refs +} diff --git a/vendor/github.com/go-openapi/validate/spec.go b/vendor/github.com/go-openapi/validate/spec.go index d6a61ea..e5d16b6 100644 --- a/vendor/github.com/go-openapi/validate/spec.go +++ b/vendor/github.com/go-openapi/validate/spec.go @@ -10,6 +10,7 @@ import ( "fmt" "slices" "sort" + "strconv" "strings" "github.com/go-openapi/analysis" @@ -48,13 +49,17 @@ func Spec(doc *loads.Document, formats strfmt.Registry, options ...Option) error // SpecValidator validates a swagger 2.0 spec. type SpecValidator struct { - schema *spec.Schema // swagger 2.0 schema - spec *loads.Document - analyzer *analysis.Spec - expanded *loads.Document - KnownFormats strfmt.Registry - Options Opts // validation options - schemaOptions *SchemaValidatorOptions + schema *spec.Schema // swagger 2.0 schema + spec *loads.Document + analyzer *analysis.Spec + expanded *loads.Document + refLocations refLocations + refRedirects refRedirects + paramLocations paramLocations + document any // the document as decoded, to tell what it holds + KnownFormats strfmt.Registry + Options Opts // validation options + schemaOptions *SchemaValidatorOptions } // NewSpecValidator creates a new swagger spec validator instance. @@ -98,6 +103,15 @@ func (s *SpecValidator) Validate(data any) (*Result, *Result) { } s.spec = sd s.analyzer = analysis.New(sd.Spec()) + // where each $ref sits, as authored: refs are reported against the + // unexpanded document, before expansion flattens them away + s.refLocations = newRefLocations(s.analyzer) + // where each operation declares its parameters: the document addresses + // them by index, and expansion loses that + s.paramLocations = newParamLocations(sd.Spec()) + // where each $ref leads: checks walk the expanded document, and a finding + // below a $ref has to be brought back to a node the document contains + s.refRedirects = newRefRedirects(s.analyzer) // Raw spec unmarshalling errors var obj any @@ -106,16 +120,23 @@ func (s *SpecValidator) Validate(data any) (*Result, *Result) { // So this one is just a paranoid check on the behavior of the spec package panic(InvalidDocumentError) } + s.document = obj defer func() { + // bring findings reached through a $ref back onto the document, then + // hold every location to what the document actually addresses + errs.redirect(s.refRedirects.through) + errs.redirect(s.resolvable) // errs holds all errors and warnings, // warnings only warnings errs.MergeAsWarnings(warnings) - warnings.AddErrors(errs.Warnings...) + // reported as errors of the warnings-only result, but keeping the + // location each was recorded with + warnings.carryErrors(errs.Warnings, errs.warningLocations) }() // Swagger schema validator - schv := newSchemaValidator(s.schema, nil, "", s.KnownFormats, s.schemaOptions) + schv := newSchemaValidator(s.schema, nil, rootPath(), s.KnownFormats, s.schemaOptions) errs.Merge(schv.Validate(obj)) // error - // There may be a point in continuing to try and determine more accurate errors if !s.Options.ContinueOnErrors && errs.HasErrors() { @@ -167,24 +188,25 @@ func (s *SpecValidator) SetContinueOnErrors(c bool) { } func (s *SpecValidator) validateNonEmptyPathParamNames() *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if s.spec.Spec().Paths == nil { - // There is no Paths object: error - res.AddErrors(noValidPathMsg()) + // There is no Paths object: the document itself is what lacks it, so + // there is no node below it to point at + res.addErrorsAt(rootPath(), noValidPathMsg()) return res } if s.spec.Spec().Paths.Paths == nil { // Paths may be empty: warning - res.AddWarnings(noValidPathMsg()) + res.addWarningsAt(newPathSegments(swaggerPaths), noValidPathMsg()) return res } - for k := range s.spec.Spec().Paths.Paths { + for _, k := range sortedKeys(s.spec.Spec().Paths.Paths) { if strings.Contains(k, "{}") { - res.AddErrors(emptyPathParameterMsg(k)) + res.addErrorsAt(newPathSegments(swaggerPaths, k), emptyPathParameterMsg(k)) } } @@ -201,21 +223,55 @@ func (s *SpecValidator) validateDuplicateOperationIDs() *Result { // fallback on possible incomplete picture because of previous errors analyzer = s.analyzer } - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() + + // the message says how many times an identifier is used, so the count is + // what it needs; a reader needs somewhere to go, so the first operation to + // declare the identifier is remembered along with it known := make(map[string]int) - for _, v := range analyzer.OperationIDs() { - if v != "" { - known[v]++ + declaredAt := make(map[string]pathSegments) + operations := analyzer.Operations() + for _, method := range sortedKeys(operations) { + byPath := operations[method] + for _, path := range sortedKeys(byPath) { + op := byPath[path] + id := operationIdentity(method, path, op) + known[id]++ + if _, isKnown := declaredAt[id]; !isKnown { + declaredAt[id] = operationIDPath(path, method, op) + } } } - for k, v := range known { - if v > 1 { - res.AddErrors(nonUniqueOperationIDMsg(k, v)) + + for _, k := range sortedKeys(known) { + if v := known[k]; v > 1 { + res.addErrorsAt(declaredAt[k], nonUniqueOperationIDMsg(k, v)) } } return res } +// operationIdentity names an operation the way the analyzer does: by its +// operationId, or by method and path when it declares none. +func operationIdentity(method, path string, op *spec.Operation) string { + if op == nil || op.ID == "" { + return strings.ToUpper(method) + " " + path + } + + return op.ID +} + +// operationIDPath locates the operationId of an operation, or the operation +// itself when it declares none. +func operationIDPath(path, method string, op *spec.Operation) pathSegments { + at := operationPath(path, method) + if op == nil || op.ID == "" { + return at + } + + return at.child(swaggerOperationID) +} + type dupProp struct { Name string Definition string @@ -223,8 +279,10 @@ type dupProp struct { func (s *SpecValidator) validateDuplicatePropertyNames() *Result { // definition can't declare a property that's already defined by one of its ancestors - res := pools.poolOfResults.BorrowResult() - for k, sch := range s.spec.Spec().Definitions { + res := validatorPools.results.Borrow() + definitions := s.spec.Spec().Definitions + for _, k := range sortedKeys(definitions) { + sch := definitions[k] if len(sch.AllOf) == 0 { continue } @@ -238,8 +296,15 @@ func (s *SpecValidator) validateDuplicatePropertyNames() *Result { res.Merge(rec) } if len(ancs) > 0 { - res.AddErrors(circularAncestryDefinitionMsg(k, ancs)) - return res + res.addErrorsAt(newPathSegments(swaggerDefinitions, k), circularAncestryDefinitionMsg(k, ancs)) + if !s.Options.ContinueOnErrors { + return res + } + + // the ancestry loops back on itself: searching it for duplicate + // property names would not terminate, so this definition stops here + // and the next one is examined. + continue } knowns := make(map[string]struct{}) @@ -252,7 +317,7 @@ func (s *SpecValidator) validateDuplicatePropertyNames() *Result { for _, v := range dups { pns = append(pns, v.Definition+"."+v.Name) } - res.AddErrors(duplicatePropertiesMsg(k, pns)) + res.addErrorsAt(newPathSegments(swaggerDefinitions, k), duplicatePropertiesMsg(k, pns)) } } @@ -272,7 +337,7 @@ func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, schn := nm schc := &sch - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for schc.Ref.String() != "" { // gather property names @@ -296,7 +361,7 @@ func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, return dups, res } - for k := range schc.Properties { + for _, k := range sortedKeys(schc.Properties) { _, ok := knowns[k] if ok { dups = append(dups, dupProp{Name: k, Definition: schn}) @@ -309,7 +374,7 @@ func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, } func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there return nil, res @@ -360,14 +425,17 @@ func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, kno //nolint:gocognit // refactor in a forthcoming PR func (s *SpecValidator) validateItems() *Result { // validate parameter, items, schema and response objects for presence of item if type is array - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() - for method, pi := range s.analyzer.Operations() { - for path, op := range pi { + operations := s.analyzer.Operations() + for _, method := range sortedKeys(operations) { + pi := operations[method] + for _, path := range sortedKeys(pi) { + op := pi[path] for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { if param.TypeName() == arrayType && param.ItemsTypeName() == "" { - res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID)) + res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), arrayInParamRequiresItemsMsg(param.Name, op.ID)) continue } if param.In != swaggerBody { @@ -375,7 +443,7 @@ func (s *SpecValidator) validateItems() *Result { items := param.Items for items.TypeName() == arrayType { if items.ItemsTypeName() == "" { - res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID)) + res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), arrayInParamRequiresItemsMsg(param.Name, op.ID)) break } items = items.Items @@ -384,32 +452,41 @@ func (s *SpecValidator) validateItems() *Result { } else { // In: body if param.Schema != nil { - res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID)) + res.Merge(s.validateSchemaItems(*param.Schema, s.parameterPath(path, method, param.In, param.Name).child(jsonSchema), + fmt.Sprintf("body param %q", param.Name), op.ID)) } } } - var responses []spec.Response + type codedResponse struct { + code string + resp spec.Response + } + var responses []codedResponse if op.Responses != nil { if op.Responses.Default != nil { - responses = append(responses, *op.Responses.Default) + responses = append(responses, codedResponse{code: jsonDefault, resp: *op.Responses.Default}) } if op.Responses.StatusCodeResponses != nil { - for _, v := range op.Responses.StatusCodeResponses { - responses = append(responses, v) + for _, code := range sortedKeys(op.Responses.StatusCodeResponses) { + responses = append(responses, codedResponse{ + code: strconv.Itoa(code), + resp: op.Responses.StatusCodeResponses[code], + }) } } } for _, resp := range responses { + at := responsePath(path, method, resp.code) // Response headers with array - for hn, hv := range resp.Headers { - if hv.TypeName() == arrayType && hv.ItemsTypeName() == "" { - res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID)) + for _, hn := range sortedKeys(resp.resp.Headers) { + if hv := resp.resp.Headers[hn]; hv.TypeName() == arrayType && hv.ItemsTypeName() == "" { + res.addErrorsAt(at.children(swaggerHeaders, hn), arrayInHeaderRequiresItemsMsg(hn, op.ID)) } } - if resp.Schema != nil { - res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID)) + if resp.resp.Schema != nil { + res.Merge(s.validateSchemaItems(*resp.resp.Schema, at.child(jsonSchema), "response body", op.ID)) } } } @@ -418,24 +495,24 @@ func (s *SpecValidator) validateItems() *Result { } // Verifies constraints on array type. -func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result { - res := pools.poolOfResults.BorrowResult() +func (s *SpecValidator) validateSchemaItems(schema spec.Schema, at pathSegments, prefix, opID string) *Result { + res := validatorPools.results.Borrow() if !schema.Type.Contains(arrayType) { return res } if schema.Items == nil || schema.Items.Len() == 0 { - res.AddErrors(arrayRequiresItemsMsg(prefix, opID)) + res.addErrorsAt(at, arrayRequiresItemsMsg(prefix, opID)) return res } if schema.Items.Schema != nil { schema = *schema.Items.Schema if _, err := compileRegexp(schema.Pattern); err != nil { - res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern)) + res.addErrorsAt(at, invalidItemsPatternMsg(prefix, opID, schema.Pattern)) } - res.Merge(s.validateSchemaItems(schema, prefix, opID)) + res.Merge(s.validateSchemaItems(schema, at.child(jsonItems), prefix, opID)) } return res } @@ -443,7 +520,7 @@ func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID str func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result { // Each defined operation path parameters must correspond to a named element in the API's path pattern. // (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.) - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() for _, l := range fromPath { var matched bool for _, r := range fromOperation { @@ -453,7 +530,7 @@ func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOpe } } if !matched { - res.AddErrors(noParameterInPathMsg(l)) + res.addErrorsAt(newPathSegments(swaggerPaths, path), noParameterInPathMsg(l)) } } @@ -463,7 +540,7 @@ func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOpe matched = true } if !matched { - res.AddErrors(pathParamNotInPathMsg(path, p)) + res.addErrorsAt(newPathSegments(swaggerPaths, path), pathParamNotInPathMsg(path, p)) } } @@ -496,9 +573,9 @@ func (s *SpecValidator) validateReferencedParameters() *Result { if len(expected) == 0 { return nil } - result := pools.poolOfResults.BorrowResult() - for k := range expected { - result.AddWarnings(unusedParamMsg(k)) + result := validatorPools.results.Borrow() + for _, k := range sortedKeys(expected) { + result.addWarningsAt(localRefPath(k), unusedParamMsg(k)) } return result } @@ -521,10 +598,12 @@ func (s *SpecValidator) validateReferencedResponses() *Result { if len(expected) == 0 { return nil } - result := pools.poolOfResults.BorrowResult() - for k := range expected { - result.AddWarnings(unusedResponseMsg(k)) + + result := validatorPools.results.Borrow() + for _, k := range sortedKeys(expected) { + result.addWarningsAt(localRefPath(k), unusedResponseMsg(k)) } + return result } @@ -548,59 +627,63 @@ func (s *SpecValidator) validateReferencedDefinitions() *Result { } result := new(Result) - for k := range expected { - result.AddWarnings(unusedDefinitionMsg(k)) + for _, k := range sortedKeys(expected) { + result.addWarningsAt(localRefPath(k), unusedDefinitionMsg(k)) } return result } func (s *SpecValidator) validateRequiredDefinitions() *Result { // Each property listed in the required array must be defined in the properties of the model - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() + + definitions := s.spec.Spec().Definitions DEFINITIONS: - for d, schema := range s.spec.Spec().Definitions { - if schema.Required != nil { // Safeguard - for _, pn := range schema.Required { - red := s.validateRequiredProperties(pn, d, &schema) //#nosec - // NOTE: capture validity before merging: Merge may redeem `red` to the - // pool (wantsRedeemOnMerge), after which reading it races with a concurrent - // BorrowResult().cleared() in another goroutine sharing the global pool. - isValid := red.IsValid() - res.Merge(red) - if !isValid && !s.Options.ContinueOnErrors { - break DEFINITIONS // there is an error, let's stop that bleeding - } - } + for _, d := range sortedKeys(definitions) { + schema := definitions[d] + red := validatorPools.results.Borrow() + keepGoing := s.walkRequired(newPathSegments(swaggerDefinitions, d), &schema, red) //#nosec + res.Merge(red) + if !keepGoing { + break DEFINITIONS // there is an error, let's stop that bleeding } } return res } -func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result { +// validateRequiredProperties checks one entry of a required array. +// +// schemaAt locates the schema being searched for the property, which moves as +// the search descends into additionalProperties. requiredAt locates the entry +// of the required array that started it, and stays put. +func (s *SpecValidator) validateRequiredProperties( + path string, of schemaIdentity, schemaAt, requiredAt pathSegments, v *spec.Schema, +) *Result { + in := of.name // Takes care of recursive property definitions, which may be nested in additionalProperties schemas - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() propertyMatch := false patternMatch := false additionalPropertiesMatch := false isReadOnly := false - // Regular properties - if _, ok := v.Properties[path]; ok { + // Regular properties, including those a base definition contributes + if readOnly, declared := s.declaresProperty(v, path, maxCompositionHops); declared { propertyMatch = true - isReadOnly = v.Properties[path].ReadOnly + isReadOnly = readOnly } // NOTE: patternProperties are not supported in swagger. Even though, we continue validation here // We check all defined patterns: if one regexp is invalid, croaks an error - for pp, pv := range v.PatternProperties { + for _, pp := range sortedKeys(v.PatternProperties) { re, err := compileRegexp(pp) if err != nil { - res.AddErrors(invalidPatternMsg(pp, in)) + res.addErrorsAt(schemaAt, invalidPatternMsg(pp, in)) } else if re.MatchString(path) { patternMatch = true if !propertyMatch { - isReadOnly = pv.ReadOnly + isReadOnly = v.PatternProperties[pp].ReadOnly } } } @@ -613,7 +696,7 @@ func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Sche // additionalProperties as schema are upported in swagger // recursively validates additionalProperties schema // Proposal for enhancement: anyOf, allOf, oneOf like in schemaPropsValidator - red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema) + red := s.validateRequiredProperties(path, of, schemaAt.child(jsonAdditionalProperties), requiredAt, v.AdditionalProperties.Schema) if red.IsValid() { additionalPropertiesMatch = true if !propertyMatch && !patternMatch { @@ -626,11 +709,11 @@ func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Sche } if !propertyMatch && !patternMatch && !additionalPropertiesMatch { - res.AddErrors(requiredButNotDefinedMsg(path, in)) + res.addErrorsAt(requiredAt, of.requiredButNotDefined(path)) } if isReadOnly { - res.AddWarnings(readOnlyAndRequiredMsg(in, path)) + res.addWarningsAt(requiredAt, readOnlyAndRequiredMsg(in, path)) } return res } @@ -646,26 +729,29 @@ func (s *SpecValidator) validateParameters() *Result { // - parameters with pattern property must specify valid patterns // - $ref in parameters must resolve // - path param must be required - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`) - for method, pi := range s.expandedAnalyzer().Operations() { + operations := s.expandedAnalyzer().Operations() + for _, method := range sortedKeys(operations) { + pi := operations[method] methodPaths := make(map[string]map[string]string) - for path, op := range pi { + for _, path := range sortedKeys(pi) { + op := pi[path] if s.Options.StrictPathParamUniqueness { pathToAdd := pathHelp.stripParametersInPath(path) // Warn on garbled path afer param stripping if rexGarbledPathSegment.MatchString(pathToAdd) { - res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd)) + res.addWarningsAt(newPathSegments(swaggerPaths, path), pathStrippedParamGarbledMsg(pathToAdd)) } // Check uniqueness of stripped paths if _, found := methodPaths[method][pathToAdd]; found { // Sort names for stable, testable output if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 { - res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd])) + res.addErrorsAt(newPathSegments(swaggerPaths, path), pathOverlapMsg(path, methodPaths[method][pathToAdd])) } else { - res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path)) + res.addErrorsAt(newPathSegments(swaggerPaths, path), pathOverlapMsg(methodPaths[method][pathToAdd], path)) } } else { if _, found := methodPaths[method]; !found { @@ -697,10 +783,10 @@ func (s *SpecValidator) validateParameters() *Result { for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { // An expanded parameter must validate the Parameter schema (an unexpanded $ref always passes high-level schema validation) - schv := newSchemaValidator(¶mSchema, s.schema, fmt.Sprintf("%s.%s.parameters.%s", path, method, pr.Name), s.KnownFormats, s.schemaOptions) + schv := newSchemaValidator(¶mSchema, s.schema, s.parameterPath(path, method, pr.In, pr.Name), s.KnownFormats, s.schemaOptions) var obj any if err := jsonutils.FromDynamicJSON(pr, &obj); err != nil { - res.AddErrors(err) + res.addErrorsAt(s.parameterPath(path, method, pr.In, pr.Name), err) return res } @@ -709,7 +795,7 @@ func (s *SpecValidator) validateParameters() *Result { // Validate pattern regexp for parameters with a Pattern property if _, err := compileRegexp(pr.Pattern); err != nil { - res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern)) + res.addErrorsAt(s.parameterPath(path, method, pr.In, pr.Name), invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern)) } // There must be at most one parameter in body: list them all @@ -722,7 +808,7 @@ func (s *SpecValidator) validateParameters() *Result { paramNames = append(paramNames, pr.Name) // Path declared in path must have the required: true property if !pr.Required { - res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name)) + res.addErrorsAt(s.parameterPath(path, method, pr.In, pr.Name), pathParamRequiredMsg(op.ID, pr.Name)) } } @@ -733,31 +819,31 @@ func (s *SpecValidator) validateParameters() *Result { if pr.Type != numberType && pr.Type != integerType && (pr.Maximum != nil || pr.Minimum != nil || pr.MultipleOf != nil) { // A non-numeric parameter has validation keywords for numeric instances (number and integer) - res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type)) + res.addWarningsAt(s.parameterPath(path, method, pr.In, pr.Name), parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type)) } if pr.Type != stringType && // A non-string parameter has validation keywords for strings (pr.MaxLength != nil || pr.MinLength != nil || pr.Pattern != "") { - res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type)) + res.addWarningsAt(s.parameterPath(path, method, pr.In, pr.Name), parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type)) } if pr.Type != arrayType && // A non-array parameter has validation keywords for arrays (pr.MaxItems != nil || pr.MinItems != nil || pr.UniqueItems) { - res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type)) + res.addWarningsAt(s.parameterPath(path, method, pr.In, pr.Name), parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type)) } } // In:formData and In:body are mutually exclusive if hasBody && hasForm { - res.AddErrors(bothFormDataAndBodyMsg(op.ID)) + res.addErrorsAt(operationPath(path, method), bothFormDataAndBodyMsg(op.ID)) } // There must be at most one body param // Accurately report situations when more than 1 body param is declared (possibly unnamed) if len(bodyParams) > 1 { sort.Strings(bodyParams) - res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams)) + res.addErrorsAt(operationPath(path, method), multipleBodyParamMsg(op.ID, bodyParams)) } // Check uniqueness of parameters in path @@ -765,7 +851,7 @@ func (s *SpecValidator) validateParameters() *Result { for i, p := range paramsInPath { for j, q := range paramsInPath { if p == q && i > j { - res.AddErrors(pathParamNotUniqueMsg(path, p, q)) + res.addErrorsAt(newPathSegments(swaggerPaths, path), pathParamNotUniqueMsg(path, p, q)) break } } @@ -775,7 +861,7 @@ func (s *SpecValidator) validateParameters() *Result { rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`) for _, p := range paramsInPath { if rexGarbledParam.MatchString(p) { - res.AddWarnings(pathParamGarbledMsg(path, p)) + res.addWarningsAt(newPathSegments(swaggerPaths, path), pathParamGarbledMsg(path, p)) } } @@ -788,10 +874,10 @@ func (s *SpecValidator) validateParameters() *Result { func (s *SpecValidator) validateReferencesValid() *Result { // each reference must point to a valid object - res := pools.poolOfResults.BorrowResult() - for _, r := range s.analyzer.AllRefs() { + res := validatorPools.results.Borrow() + for _, r := range sortedRefs(s.analyzer.AllRefs()) { if !r.IsValidURI(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI - res.AddErrors(invalidRefMsg(r.String())) + res.addErrorsAt(s.refLocations.at(r.String()), invalidRefMsg(r.String())) } } if !res.HasErrors() { @@ -803,13 +889,48 @@ func (s *SpecValidator) validateReferencesValid() *Result { // is set, this is a no-op: loads falls back to the document's own loader. exp, err := s.spec.Expanded(s.schemaOptions.expandOptions("")) if err != nil { - res.AddErrors(unresolvedReferencesMsg(err)) + res.addErrorsAt(s.firstUnresolvableRef(), unresolvedReferencesMsg(err)) } s.expanded = exp } return res } +// firstUnresolvableRef locates the declaration of the first local $ref, in +// document order, that points at a node the document does not hold. +// +// Expansion reports the whole document in a single message, naming only the +// reference it happened to trip on, so the finding has no location of its own. +// A document usually has one broken reference; when it has several, this is the +// first one a reader would meet. +func (s *SpecValidator) firstUnresolvableRef() pathSegments { + first := rootPath() + found := false + + for _, r := range s.analyzer.AllRefs() { + value := r.String() + if !strings.HasPrefix(value, "#/") { + // a remote reference cannot be checked against the document alone + continue + } + + pointer, err := jsonpointer.New(strings.TrimPrefix(value, "#")) + if err != nil { + continue + } + if _, _, err := pointer.Get(s.document); err == nil { + continue + } + + at := s.refLocations.at(value) + if !found || at.pointer() < first.pointer() { + first, found = at, true + } + } + + return first +} + func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result { // Check for duplicate parameters declaration in param section. // Each parameter should have a unique `name` and `type` combination @@ -817,7 +938,7 @@ func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operatio // However, there are some issues with such a factorization: // - analysis does not seem to fully expand params // - param keys may be altered by x-go-name - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() pnames := make(map[string]struct{}) if op.Parameters != nil { // Safeguard @@ -830,7 +951,7 @@ func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operatio key := fmt.Sprintf("%s#%s", pr.In, pr.Name) if _, ok = pnames[key]; ok { - res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID)) + res.addErrorsAt(s.parameterPath(path, method, pr.In, pr.Name), duplicateParamNameMsg(pr.In, pr.Name, op.ID)) } pnames[key] = struct{}{} } diff --git a/vendor/github.com/go-openapi/validate/spec_messages.go b/vendor/github.com/go-openapi/validate/spec_messages.go index eeb8a86..0a0739a 100644 --- a/vendor/github.com/go-openapi/validate/spec_messages.go +++ b/vendor/github.com/go-openapi/validate/spec_messages.go @@ -132,6 +132,9 @@ const ( // RequiredButNotDefinedError ... RequiredButNotDefinedError = "%q is present in required but not defined as property in definition %q" + // RequiredButNotDefinedInSchemaError is the same slip, in a schema a definition holds rather than + // in the definition itself. + RequiredButNotDefinedInSchemaError = "%q is present in required but not defined as property in schema %q" // SomeParametersBrokenError indicates that some parameters could not be resolved, which might result in partial checks to be carried on. SomeParametersBrokenError = "some parameters definitions are broken in %q.%s. Cannot carry on full checks on parameters for operation %s" @@ -260,6 +263,10 @@ func requiredButNotDefinedMsg(path, definition string) errors.Error { return errors.New(errors.CompositeErrorCode, RequiredButNotDefinedError, path, definition) } +func requiredButNotDefinedInSchemaMsg(path, schema string) errors.Error { + return errors.New(errors.CompositeErrorCode, RequiredButNotDefinedInSchemaError, path, schema) +} + func pathParamGarbledMsg(path, param string) errors.Error { return errors.New(errors.CompositeErrorCode, PathParamGarbledWarning, path, param) } diff --git a/vendor/github.com/go-openapi/validate/spec_ref_warnings.go b/vendor/github.com/go-openapi/validate/spec_ref_warnings.go index 49c7231..d56cb17 100644 --- a/vendor/github.com/go-openapi/validate/spec_ref_warnings.go +++ b/vendor/github.com/go-openapi/validate/spec_ref_warnings.go @@ -34,12 +34,12 @@ const minDistinctHostsToWarn = 2 // // All findings are warnings: they do not affect validity (see Result.IsValid). func (s *SpecValidator) validateDubiousRefs() *Result { - res := pools.poolOfResults.BorrowResult() + res := validatorPools.results.Borrow() baseDir, hasBase := s.localBaseDir() remoteHosts := make(map[string]struct{}) - for _, r := range s.analyzer.AllRefs() { + for _, r := range sortedRefs(s.analyzer.AllRefs()) { u := r.GetURL() if u == nil { // Safeguard: a valid spec always yields parseable refs continue @@ -48,7 +48,7 @@ func (s *SpecValidator) validateDubiousRefs() *Result { // Rule 1: absolute local reference escaping the base path. if refPath, isLocalAbs := absoluteLocalRefPath(r, u); isLocalAbs { if !hasBase || !isBeneathBase(refPath, baseDir) { - res.AddWarnings(dubiousAbsoluteRefMsg(r.String())) + res.addWarningsAt(s.refLocations.at(r.String()), dubiousAbsoluteRefMsg(r.String())) } continue } diff --git a/vendor/github.com/go-openapi/validate/type.go b/vendor/github.com/go-openapi/validate/type.go index d29574c..3016783 100644 --- a/vendor/github.com/go-openapi/validate/type.go +++ b/vendor/github.com/go-openapi/validate/type.go @@ -15,7 +15,7 @@ import ( ) type typeValidator struct { - Path string + Path pathSegments In string Type spec.StringOrArray Nullable bool @@ -23,14 +23,14 @@ type typeValidator struct { Options *SchemaValidatorOptions } -func newTypeValidator(path, in string, typ spec.StringOrArray, nullable bool, format string, opts *SchemaValidatorOptions) *typeValidator { +func newTypeValidator(path pathSegments, in string, typ spec.StringOrArray, nullable bool, format string, opts *SchemaValidatorOptions) *typeValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var t *typeValidator if opts.recycleValidators { - t = pools.poolOfTypeValidators.BorrowValidator() + t = validatorPools.typeValidators.Borrow() } else { t = new(typeValidator) } @@ -45,10 +45,6 @@ func newTypeValidator(path, in string, typ spec.StringOrArray, nullable bool, fo return t } -func (t *typeValidator) SetPath(path string) { - t.Path = path -} - func (t *typeValidator) Applies(source any, _ reflect.Kind) bool { // typeValidator applies to Schema, Parameter and Header objects switch source.(type) { @@ -72,7 +68,7 @@ func (t *typeValidator) Validate(data any) *Result { if data == nil { // nil or zero value for the passed structure require Type: null if len(t.Type) > 0 && !t.Type.Contains(nullType) && !t.Nullable { // NOTE: if a property is not required it also passes this - return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), nullType), t.Options.recycleResult) + return errorHelp.sErrAt(t.Path, errors.InvalidType(t.Path.dotted(), t.In, strings.Join(t.Type, ","), nullType), t.Options.recycleResult) } return emptyResult @@ -98,7 +94,7 @@ func (t *typeValidator) Validate(data any) *Result { !isFloatInt && !isIntFloat && !isLowerInt && !isLowerFloat if formatMismatch { // NOTE: test case - return errorHelp.sErr(errors.InvalidType(t.Path, t.In, t.Format, format), t.Options.recycleResult) + return errorHelp.sErrAt(t.Path, errors.InvalidType(t.Path.dotted(), t.In, t.Format, format), t.Options.recycleResult) } if !t.Type.Contains(numberType) && !t.Type.Contains(integerType) && t.Format != "" && (kind == reflect.String || kind == reflect.Slice) { @@ -106,7 +102,7 @@ func (t *typeValidator) Validate(data any) *Result { } if !t.Type.Contains(schType) && !isFloatInt && !isIntFloat { - return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), schType), t.Options.recycleResult) + return errorHelp.sErrAt(t.Path, errors.InvalidType(t.Path.dotted(), t.In, strings.Join(t.Type, ","), schType), t.Options.recycleResult) } return emptyResult @@ -201,6 +197,10 @@ func (t *typeValidator) schemaInfoForType(data any) (string, string) { return "", "" } +func (t *typeValidator) setPath(path pathSegments) { + t.Path = path +} + func (t *typeValidator) redeem() { - pools.poolOfTypeValidators.RedeemValidator(t) + validatorPools.typeValidators.Redeem(t) } diff --git a/vendor/github.com/go-openapi/validate/validator.go b/vendor/github.com/go-openapi/validate/validator.go index e7aebc5..3989b21 100644 --- a/vendor/github.com/go-openapi/validate/validator.go +++ b/vendor/github.com/go-openapi/validate/validator.go @@ -4,7 +4,6 @@ package validate import ( - "fmt" "reflect" "github.com/go-openapi/errors" @@ -18,7 +17,7 @@ type EntityValidator interface { } type valueValidator interface { - SetPath(path string) + setPath(path pathSegments) Applies(source any, kind reflect.Kind) bool Validate(data any) *Result } @@ -26,21 +25,21 @@ type valueValidator interface { type itemsValidator struct { items *spec.Items root any - path string + path pathSegments in string validators [6]valueValidator KnownFormats strfmt.Registry Options *SchemaValidatorOptions } -func newItemsValidator(path, in string, items *spec.Items, root any, formats strfmt.Registry, opts *SchemaValidatorOptions) *itemsValidator { +func newItemsValidator(path pathSegments, in string, items *spec.Items, root any, formats strfmt.Registry, opts *SchemaValidatorOptions) *itemsValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var iv *itemsValidator if opts.recycleValidators { - iv = pools.poolOfItemsValidators.BorrowValidator() + iv = validatorPools.itemsValidators.Borrow() } else { iv = new(itemsValidator) } @@ -74,12 +73,12 @@ func (i *itemsValidator) Validate(index int, data any) *Result { kind := tpe.Kind() var result *Result if i.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } - path := fmt.Sprintf("%s.%d", i.path, index) + path := i.path.item(index) for idx, validator := range i.validators { if !validator.Applies(i.root, kind) { @@ -97,7 +96,7 @@ func (i *itemsValidator) Validate(index int, data any) *Result { continue } - validator.SetPath(path) + validator.setPath(path) err := validator.Validate(data) if i.Options.recycleValidators { i.validators[idx] = nil // prevents further (unsafe) usage @@ -130,7 +129,7 @@ func (i *itemsValidator) typeValidator() valueValidator { func (i *itemsValidator) commonValidator() valueValidator { return newBasicCommonValidator( - "", + nil, // located by the item index, set on each Validate call i.in, i.items.Default, i.items.Enum, @@ -140,7 +139,7 @@ func (i *itemsValidator) commonValidator() valueValidator { func (i *itemsValidator) sliceValidator() valueValidator { return newBasicSliceValidator( - "", + nil, // located by the item index, set on each Validate call i.in, i.items.Default, i.items.MaxItems, @@ -155,7 +154,7 @@ func (i *itemsValidator) sliceValidator() valueValidator { func (i *itemsValidator) numberValidator() valueValidator { return newNumberValidator( - "", + nil, // located by the item index, set on each Validate call i.in, i.items.Default, i.items.MultipleOf, @@ -171,7 +170,7 @@ func (i *itemsValidator) numberValidator() valueValidator { func (i *itemsValidator) stringValidator() valueValidator { return newStringValidator( - "", + nil, // located by the item index, set on each Validate call i.in, i.items.Default, false, // Required @@ -185,7 +184,7 @@ func (i *itemsValidator) stringValidator() valueValidator { func (i *itemsValidator) formatValidator() valueValidator { return newFormatValidator( - "", + nil, // located by the item index, set on each Validate call i.in, i.items.Format, i.KnownFormats, @@ -194,7 +193,7 @@ func (i *itemsValidator) formatValidator() valueValidator { } func (i *itemsValidator) redeem() { - pools.poolOfItemsValidators.RedeemValidator(i) + validatorPools.itemsValidators.Redeem(i) } func (i *itemsValidator) redeemChildren() { @@ -213,21 +212,21 @@ func (i *itemsValidator) redeemChildren() { } type basicCommonValidator struct { - Path string + Path pathSegments In string Default any Enum []any Options *SchemaValidatorOptions } -func newBasicCommonValidator(path, in string, def any, enum []any, opts *SchemaValidatorOptions) *basicCommonValidator { +func newBasicCommonValidator(path pathSegments, in string, def any, enum []any, opts *SchemaValidatorOptions) *basicCommonValidator { if opts == nil { opts = new(SchemaValidatorOptions) } var b *basicCommonValidator if opts.recycleValidators { - b = pools.poolOfBasicCommonValidators.BorrowValidator() + b = validatorPools.basicCommonValidators.Borrow() } else { b = new(basicCommonValidator) } @@ -241,10 +240,6 @@ func newBasicCommonValidator(path, in string, def any, enum []any, opts *SchemaV return b } -func (b *basicCommonValidator) SetPath(path string) { - b.Path = path -} - func (b *basicCommonValidator) Applies(source any, _ reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Header: @@ -279,11 +274,15 @@ func (b *basicCommonValidator) Validate(data any) (res *Result) { } } - return errorHelp.sErr(errors.EnumFail(b.Path, b.In, data, b.Enum), b.Options.recycleResult) + return errorHelp.sErrAt(b.Path, errors.EnumFail(b.Path.dotted(), b.In, data, b.Enum), b.Options.recycleResult) +} + +func (b *basicCommonValidator) setPath(path pathSegments) { + b.Path = path } func (b *basicCommonValidator) redeem() { - pools.poolOfBasicCommonValidators.RedeemValidator(b) + validatorPools.basicCommonValidators.Redeem(b) } // A HeaderValidator has very limited subset of validations to apply. @@ -312,7 +311,7 @@ func newHeaderValidator(name string, header *spec.Header, formats strfmt.Registr var p *HeaderValidator if opts.recycleValidators { - p = pools.poolOfHeaderValidators.BorrowValidator() + p = validatorPools.headerValidators.Borrow() } else { p = new(HeaderValidator) } @@ -323,7 +322,7 @@ func newHeaderValidator(name string, header *spec.Header, formats strfmt.Registr p.Options = opts p.validators = [6]valueValidator{ newTypeValidator( - name, + newPathSegments(name), "header", spec.StringOrArray([]string{header.Type}), header.Nullable, @@ -355,7 +354,7 @@ func (p *HeaderValidator) Validate(data any) *Result { var result *Result if p.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -397,7 +396,7 @@ func (p *HeaderValidator) Validate(data any) *Result { func (p *HeaderValidator) commonValidator() valueValidator { return newBasicCommonValidator( - p.name, + newPathSegments(p.name), "response", p.header.Default, p.header.Enum, @@ -407,7 +406,7 @@ func (p *HeaderValidator) commonValidator() valueValidator { func (p *HeaderValidator) sliceValidator() valueValidator { return newBasicSliceValidator( - p.name, + newPathSegments(p.name), "response", p.header.Default, p.header.MaxItems, @@ -422,7 +421,7 @@ func (p *HeaderValidator) sliceValidator() valueValidator { func (p *HeaderValidator) numberValidator() valueValidator { return newNumberValidator( - p.name, + newPathSegments(p.name), "response", p.header.Default, p.header.MultipleOf, @@ -438,7 +437,7 @@ func (p *HeaderValidator) numberValidator() valueValidator { func (p *HeaderValidator) stringValidator() valueValidator { return newStringValidator( - p.name, + newPathSegments(p.name), "response", p.header.Default, true, @@ -452,7 +451,7 @@ func (p *HeaderValidator) stringValidator() valueValidator { func (p *HeaderValidator) formatValidator() valueValidator { return newFormatValidator( - p.name, + newPathSegments(p.name), "response", p.header.Format, p.KnownFormats, @@ -461,7 +460,7 @@ func (p *HeaderValidator) formatValidator() valueValidator { } func (p *HeaderValidator) redeem() { - pools.poolOfHeaderValidators.RedeemValidator(p) + validatorPools.headerValidators.Redeem(p) } func (p *HeaderValidator) redeemChildren() { @@ -504,7 +503,7 @@ func newParamValidator(param *spec.Parameter, formats strfmt.Registry, opts *Sch var p *ParamValidator if opts.recycleValidators { - p = pools.poolOfParamValidators.BorrowValidator() + p = validatorPools.paramValidators.Borrow() } else { p = new(ParamValidator) } @@ -514,7 +513,7 @@ func newParamValidator(param *spec.Parameter, formats strfmt.Registry, opts *Sch p.Options = opts p.validators = [6]valueValidator{ newTypeValidator( - param.Name, + newPathSegments(param.Name), param.In, spec.StringOrArray([]string{param.Type}), param.Nullable, @@ -539,7 +538,7 @@ func (p *ParamValidator) Validate(data any) *Result { var result *Result if p.Options.recycleResult { - result = pools.poolOfResults.BorrowResult() + result = validatorPools.results.Borrow() } else { result = new(Result) } @@ -589,7 +588,7 @@ func (p *ParamValidator) Validate(data any) *Result { func (p *ParamValidator) commonValidator() valueValidator { return newBasicCommonValidator( - p.param.Name, + newPathSegments(p.param.Name), p.param.In, p.param.Default, p.param.Enum, @@ -599,7 +598,7 @@ func (p *ParamValidator) commonValidator() valueValidator { func (p *ParamValidator) sliceValidator() valueValidator { return newBasicSliceValidator( - p.param.Name, + newPathSegments(p.param.Name), p.param.In, p.param.Default, p.param.MaxItems, @@ -614,7 +613,7 @@ func (p *ParamValidator) sliceValidator() valueValidator { func (p *ParamValidator) numberValidator() valueValidator { return newNumberValidator( - p.param.Name, + newPathSegments(p.param.Name), p.param.In, p.param.Default, p.param.MultipleOf, @@ -630,7 +629,7 @@ func (p *ParamValidator) numberValidator() valueValidator { func (p *ParamValidator) stringValidator() valueValidator { return newStringValidator( - p.param.Name, + newPathSegments(p.param.Name), p.param.In, p.param.Default, p.param.Required, @@ -644,7 +643,7 @@ func (p *ParamValidator) stringValidator() valueValidator { func (p *ParamValidator) formatValidator() valueValidator { return newFormatValidator( - p.param.Name, + newPathSegments(p.param.Name), p.param.In, p.param.Format, p.KnownFormats, @@ -653,7 +652,7 @@ func (p *ParamValidator) formatValidator() valueValidator { } func (p *ParamValidator) redeem() { - pools.poolOfParamValidators.RedeemValidator(p) + validatorPools.paramValidators.Redeem(p) } func (p *ParamValidator) redeemChildren() { @@ -672,7 +671,7 @@ func (p *ParamValidator) redeemChildren() { } type basicSliceValidator struct { - Path string + Path pathSegments In string Default any MaxItems *int64 @@ -685,7 +684,7 @@ type basicSliceValidator struct { } func newBasicSliceValidator( - path, in string, + path pathSegments, in string, def any, maxItems, minItems *int64, uniqueItems bool, items *spec.Items, source any, formats strfmt.Registry, opts *SchemaValidatorOptions, @@ -696,7 +695,7 @@ func newBasicSliceValidator( var s *basicSliceValidator if opts.recycleValidators { - s = pools.poolOfBasicSliceValidators.BorrowValidator() + s = validatorPools.basicSliceValidators.Borrow() } else { s = new(basicSliceValidator) } @@ -715,10 +714,6 @@ func newBasicSliceValidator( return s } -func (s *basicSliceValidator) SetPath(path string) { - s.Path = path -} - func (s *basicSliceValidator) Applies(source any, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Items, *spec.Header: @@ -738,20 +733,20 @@ func (s *basicSliceValidator) Validate(data any) *Result { size := int64(val.Len()) if s.MinItems != nil { - if err := MinItems(s.Path, s.In, size, *s.MinItems); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := MinItems(s.Path.dotted(), s.In, size, *s.MinItems); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } if s.MaxItems != nil { - if err := MaxItems(s.Path, s.In, size, *s.MaxItems); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := MaxItems(s.Path.dotted(), s.In, size, *s.MaxItems); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } if s.UniqueItems { - if err := UniqueItems(s.Path, s.In, data); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := UniqueItems(s.Path.dotted(), s.In, data); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } @@ -767,7 +762,7 @@ func (s *basicSliceValidator) Validate(data any) *Result { return err } if err.wantsRedeemOnMerge { - pools.poolOfResults.RedeemResult(err) + redeemResult(err) } } } @@ -775,12 +770,16 @@ func (s *basicSliceValidator) Validate(data any) *Result { return nil } +func (s *basicSliceValidator) setPath(path pathSegments) { + s.Path = path +} + func (s *basicSliceValidator) redeem() { - pools.poolOfBasicSliceValidators.RedeemValidator(s) + validatorPools.basicSliceValidators.Redeem(s) } type numberValidator struct { - Path string + Path pathSegments In string Default any MultipleOf *float64 @@ -795,7 +794,7 @@ type numberValidator struct { } func newNumberValidator( - path, in string, def any, + path pathSegments, in string, def any, multipleOf, maximum *float64, exclusiveMaximum bool, minimum *float64, exclusiveMinimum bool, typ, format string, opts *SchemaValidatorOptions, @@ -806,7 +805,7 @@ func newNumberValidator( var n *numberValidator if opts.recycleValidators { - n = pools.poolOfNumberValidators.BorrowValidator() + n = validatorPools.numberValidators.Borrow() } else { n = new(numberValidator) } @@ -826,10 +825,6 @@ func newNumberValidator( return n } -func (n *numberValidator) SetPath(path string) { - n.Path = path -} - func (n *numberValidator) Applies(source any, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: @@ -871,7 +866,7 @@ func (n *numberValidator) Validate(val any) *Result { var res, resMultiple, resMinimum, resMaximum *Result if n.Options.recycleResult { - res = pools.poolOfResults.BorrowResult() + res = validatorPools.results.Borrow() } else { res = new(Result) } @@ -881,58 +876,58 @@ func (n *numberValidator) Validate(val any) *Result { data := valueHelp.asFloat64(val) // Is the provided value within the range of the specified numeric type and format? - res.AddErrors(IsValueValidAgainstRange(val, n.Type, n.Format, "Checked", n.Path)) + res.addErrorsAt(n.Path, IsValueValidAgainstRange(val, n.Type, n.Format, "Checked", n.Path.dotted())) if n.MultipleOf != nil { - resMultiple = pools.poolOfResults.BorrowResult() + resMultiple = validatorPools.results.Borrow() // Is the constraint specifier within the range of the specific numeric type and format? - resMultiple.AddErrors(IsValueValidAgainstRange(*n.MultipleOf, n.Type, n.Format, "MultipleOf", n.Path)) + resMultiple.addErrorsAt(n.Path, IsValueValidAgainstRange(*n.MultipleOf, n.Type, n.Format, "MultipleOf", n.Path.dotted())) if resMultiple.IsValid() { // Constraint validated with compatible types - if err := MultipleOfNativeType(n.Path, n.In, val, *n.MultipleOf); err != nil { - resMultiple.Merge(errorHelp.sErr(err, n.Options.recycleResult)) + if err := MultipleOfNativeType(n.Path.dotted(), n.In, val, *n.MultipleOf); err != nil { + resMultiple.Merge(errorHelp.sErrAt(n.Path, err, n.Options.recycleResult)) } } else { // Constraint nevertheless validated, converted as general number - if err := MultipleOf(n.Path, n.In, data, *n.MultipleOf); err != nil { - resMultiple.Merge(errorHelp.sErr(err, n.Options.recycleResult)) + if err := MultipleOf(n.Path.dotted(), n.In, data, *n.MultipleOf); err != nil { + resMultiple.Merge(errorHelp.sErrAt(n.Path, err, n.Options.recycleResult)) } } } if n.Maximum != nil { - resMaximum = pools.poolOfResults.BorrowResult() + resMaximum = validatorPools.results.Borrow() // Is the constraint specifier within the range of the specific numeric type and format? - resMaximum.AddErrors(IsValueValidAgainstRange(*n.Maximum, n.Type, n.Format, "Maximum boundary", n.Path)) + resMaximum.addErrorsAt(n.Path, IsValueValidAgainstRange(*n.Maximum, n.Type, n.Format, "Maximum boundary", n.Path.dotted())) if resMaximum.IsValid() { // Constraint validated with compatible types - if err := MaximumNativeType(n.Path, n.In, val, *n.Maximum, n.ExclusiveMaximum); err != nil { - resMaximum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) + if err := MaximumNativeType(n.Path.dotted(), n.In, val, *n.Maximum, n.ExclusiveMaximum); err != nil { + resMaximum.Merge(errorHelp.sErrAt(n.Path, err, n.Options.recycleResult)) } } else { // Constraint nevertheless validated, converted as general number - if err := Maximum(n.Path, n.In, data, *n.Maximum, n.ExclusiveMaximum); err != nil { - resMaximum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) + if err := Maximum(n.Path.dotted(), n.In, data, *n.Maximum, n.ExclusiveMaximum); err != nil { + resMaximum.Merge(errorHelp.sErrAt(n.Path, err, n.Options.recycleResult)) } } } if n.Minimum != nil { - resMinimum = pools.poolOfResults.BorrowResult() + resMinimum = validatorPools.results.Borrow() // Is the constraint specifier within the range of the specific numeric type and format? - resMinimum.AddErrors(IsValueValidAgainstRange(*n.Minimum, n.Type, n.Format, "Minimum boundary", n.Path)) + resMinimum.addErrorsAt(n.Path, IsValueValidAgainstRange(*n.Minimum, n.Type, n.Format, "Minimum boundary", n.Path.dotted())) if resMinimum.IsValid() { // Constraint validated with compatible types - if err := MinimumNativeType(n.Path, n.In, val, *n.Minimum, n.ExclusiveMinimum); err != nil { - resMinimum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) + if err := MinimumNativeType(n.Path.dotted(), n.In, val, *n.Minimum, n.ExclusiveMinimum); err != nil { + resMinimum.Merge(errorHelp.sErrAt(n.Path, err, n.Options.recycleResult)) } } else { // Constraint nevertheless validated, converted as general number - if err := Minimum(n.Path, n.In, data, *n.Minimum, n.ExclusiveMinimum); err != nil { - resMinimum.Merge(errorHelp.sErr(err, n.Options.recycleResult)) + if err := Minimum(n.Path.dotted(), n.In, data, *n.Minimum, n.ExclusiveMinimum); err != nil { + resMinimum.Merge(errorHelp.sErrAt(n.Path, err, n.Options.recycleResult)) } } } @@ -942,12 +937,16 @@ func (n *numberValidator) Validate(val any) *Result { return res } +func (n *numberValidator) setPath(path pathSegments) { + n.Path = path +} + func (n *numberValidator) redeem() { - pools.poolOfNumberValidators.RedeemValidator(n) + validatorPools.numberValidators.Redeem(n) } type stringValidator struct { - Path string + Path pathSegments In string Default any Required bool @@ -959,7 +958,7 @@ type stringValidator struct { } func newStringValidator( - path, in string, + path pathSegments, in string, def any, required, allowEmpty bool, maxLength, minLength *int64, pattern string, opts *SchemaValidatorOptions, ) *stringValidator { @@ -969,7 +968,7 @@ func newStringValidator( var s *stringValidator if opts.recycleValidators { - s = pools.poolOfStringValidators.BorrowValidator() + s = validatorPools.stringValidators.Borrow() } else { s = new(stringValidator) } @@ -987,10 +986,6 @@ func newStringValidator( return s } -func (s *stringValidator) SetPath(path string) { - s.Path = path -} - func (s *stringValidator) Applies(source any, kind reflect.Kind) bool { switch source.(type) { case *spec.Parameter, *spec.Schema, *spec.Items, *spec.Header: @@ -1009,35 +1004,39 @@ func (s *stringValidator) Validate(val any) *Result { data, ok := val.(string) if !ok { - return errorHelp.sErr(errors.InvalidType(s.Path, s.In, stringType, val), s.Options.recycleResult) + return errorHelp.sErrAt(s.Path, errors.InvalidType(s.Path.dotted(), s.In, stringType, val), s.Options.recycleResult) } if s.Required && !s.AllowEmptyValue && (s.Default == nil || s.Default == "") { - if err := RequiredString(s.Path, s.In, data); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := RequiredString(s.Path.dotted(), s.In, data); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } if s.MaxLength != nil { - if err := MaxLength(s.Path, s.In, data, *s.MaxLength); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := MaxLength(s.Path.dotted(), s.In, data, *s.MaxLength); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } if s.MinLength != nil { - if err := MinLength(s.Path, s.In, data, *s.MinLength); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := MinLength(s.Path.dotted(), s.In, data, *s.MinLength); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } if s.Pattern != "" { - if err := Pattern(s.Path, s.In, data, s.Pattern); err != nil { - return errorHelp.sErr(err, s.Options.recycleResult) + if err := Pattern(s.Path.dotted(), s.In, data, s.Pattern); err != nil { + return errorHelp.sErrAt(s.Path, err, s.Options.recycleResult) } } return nil } +func (s *stringValidator) setPath(path pathSegments) { + s.Path = path +} + func (s *stringValidator) redeem() { - pools.poolOfStringValidators.RedeemValidator(s) + validatorPools.stringValidators.Redeem(s) } diff --git a/vendor/go.yaml.in/yaml/v3/parserc.go b/vendor/go.yaml.in/yaml/v3/parserc.go index 25fe823..f35829d 100644 --- a/vendor/go.yaml.in/yaml/v3/parserc.go +++ b/vendor/go.yaml.in/yaml/v3/parserc.go @@ -226,9 +226,9 @@ func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool } // Parse the production: -// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END // -// ************ +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -249,13 +249,11 @@ func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) } // Parse the productions: -// implicit_document ::= block_node DOCUMENT-END* -// -// * // -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -// -// ************************* +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { token := peek_token(parser) @@ -359,9 +357,9 @@ func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t } // Parse the productions: -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* // -// *********** +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -382,11 +380,10 @@ func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event } // Parse the productions: -// implicit_document ::= block_node DOCUMENT-END* -// -// ************* // -// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -432,42 +429,32 @@ func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) } // Parse the productions: -// block_node_or_indentless_sequence ::= -// -// ALIAS -// ***** -// | properties (block_content | indentless_block_sequence)? -// ********** * -// | block_content | indentless_block_sequence -// * -// -// block_node ::= ALIAS -// -// ***** -// | properties block_content? -// ********** * -// | block_content -// * -// -// flow_node ::= ALIAS -// -// ***** -// | properties flow_content? -// ********** * -// | flow_content -// * -// -// properties ::= TAG ANCHOR? | ANCHOR TAG? -// -// ************************* -// -// block_content ::= block_collection | flow_collection | SCALAR -// -// ****** // -// flow_content ::= flow_collection | SCALAR -// -// ****** +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() @@ -697,9 +684,9 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i } // Parse the productions: -// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END // -// ******************** *********** * ********* +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -755,9 +742,9 @@ func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_e } // Parse the productions: -// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ // -// *********** * +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -821,15 +808,15 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { } // Parse the productions: -// block_mapping ::= BLOCK-MAPPING_START // -// ******************* -// ((KEY block_node_or_indentless_sequence?)? -// *** * -// (VALUE block_node_or_indentless_sequence?)?)* +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* // -// BLOCK-END -// ********* +// BLOCK-END +// ********* func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -896,13 +883,14 @@ func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_even } // Parse the productions: -// block_mapping ::= BLOCK-MAPPING_START // -// ((KEY block_node_or_indentless_sequence?)? +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? // -// (VALUE block_node_or_indentless_sequence?)?)* -// ***** * -// BLOCK-END +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -929,19 +917,17 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev } // Parse the productions: -// flow_sequence ::= FLOW-SEQUENCE-START -// -// ******************* -// (flow_sequence_entry FLOW-ENTRY)* -// * ********** -// flow_sequence_entry? -// * -// FLOW-SEQUENCE-END -// ***************** // -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// -// * +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -1005,9 +991,9 @@ func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_ev } // Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // -// *** * +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -1026,9 +1012,9 @@ func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, ev } // Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // -// ***** * +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -1050,9 +1036,9 @@ func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, } // Parse the productions: -// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // -// * +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { token := peek_token(parser) if token == nil { @@ -1068,18 +1054,17 @@ func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, ev } // Parse the productions: -// flow_mapping ::= FLOW-MAPPING-START -// -// ****************** -// (flow_mapping_entry FLOW-ENTRY)* -// * ********** -// flow_mapping_entry? -// ****************** -// FLOW-MAPPING-END -// **************** // -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// - *** * +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) @@ -1144,8 +1129,9 @@ func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event } // Parse the productions: -// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -// - ***** * +// +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { token := peek_token(parser) if token == nil { diff --git a/vendor/go.yaml.in/yaml/v3/yamlh.go b/vendor/go.yaml.in/yaml/v3/yamlh.go index f59aa40..07c4423 100644 --- a/vendor/go.yaml.in/yaml/v3/yamlh.go +++ b/vendor/go.yaml.in/yaml/v3/yamlh.go @@ -433,21 +433,19 @@ type yaml_document_t struct { // The prototype of a read handler. // -// The read handler is called when the parser needs to read more bytes from the -// source. The handler should write not more than size bytes to the buffer. -// The number of written bytes should be set to the size_read variable. +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. // -// [in,out] data A pointer to an application data specified by +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. // -// yaml_parser_set_input(). -// -// [out] buffer The buffer to write the data from the source. -// [in] size The size of the buffer. -// [out] size_read The actual number of bytes read from the source. -// -// On success, the handler should return 1. If the handler failed, -// the returned value should be 0. On EOF, the handler should set the -// size_read to 0 and return 1. +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) // This structure holds information about a potential simple key. @@ -655,19 +653,17 @@ type yaml_comment_t struct { // The prototype of a write handler. // -// The write handler is called when the emitter needs to flush the accumulated -// characters to the output. The handler should write @a size bytes of the -// @a buffer to the output. -// -// @param[in,out] data A pointer to an application data specified by -// -// yaml_emitter_set_output(). +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. // -// @param[in] buffer The buffer with bytes to be written. -// @param[in] size The size of the buffer. +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. // -// @returns On success, the handler should return @c 1. If the handler failed, -// the returned value should be @c 0. +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error type yaml_emitter_state_t int diff --git a/vendor/modules.txt b/vendor/modules.txt index 091ddb6..43fb74f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -11,7 +11,7 @@ github.com/go-logr/logr/funcr # github.com/go-logr/stdr v1.2.2 ## explicit; go 1.16 github.com/go-logr/stdr -# github.com/go-openapi/analysis v0.25.5 +# github.com/go-openapi/analysis v0.26.0 ## explicit; go 1.25.0 github.com/go-openapi/analysis github.com/go-openapi/analysis/internal/debug @@ -62,7 +62,7 @@ github.com/go-openapi/strfmt/internal/countries # github.com/go-openapi/swag/conv v0.28.0 ## explicit; go 1.25.0 github.com/go-openapi/swag/conv -# github.com/go-openapi/swag/fileutils v0.27.3 +# github.com/go-openapi/swag/fileutils v0.28.0 ## explicit; go 1.25.0 github.com/go-openapi/swag/fileutils # github.com/go-openapi/swag/jsonutils v0.28.0 @@ -71,10 +71,10 @@ github.com/go-openapi/swag/jsonutils github.com/go-openapi/swag/jsonutils/adapters github.com/go-openapi/swag/jsonutils/adapters/ifaces github.com/go-openapi/swag/jsonutils/adapters/stdlib/json -# github.com/go-openapi/swag/loading v0.27.3 +# github.com/go-openapi/swag/loading v0.28.0 ## explicit; go 1.25.0 github.com/go-openapi/swag/loading -# github.com/go-openapi/swag/mangling v0.27.3 +# github.com/go-openapi/swag/mangling v0.28.0 ## explicit; go 1.25.0 github.com/go-openapi/swag/mangling # github.com/go-openapi/swag/pools v0.28.0 @@ -86,10 +86,10 @@ github.com/go-openapi/swag/stringutils # github.com/go-openapi/swag/typeutils v0.28.0 ## explicit; go 1.25.0 github.com/go-openapi/swag/typeutils -# github.com/go-openapi/swag/yamlutils v0.27.3 +# github.com/go-openapi/swag/yamlutils v0.28.0 ## explicit; go 1.25.0 github.com/go-openapi/swag/yamlutils -# github.com/go-openapi/validate v0.26.1 +# github.com/go-openapi/validate v0.26.3 ## explicit; go 1.25.0 github.com/go-openapi/validate # github.com/go-viper/mapstructure/v2 v2.5.0 @@ -143,7 +143,7 @@ go.opentelemetry.io/otel/trace go.opentelemetry.io/otel/trace/embedded go.opentelemetry.io/otel/trace/internal/telemetry go.opentelemetry.io/otel/trace/noop -# go.yaml.in/yaml/v3 v3.0.4 +# go.yaml.in/yaml/v3 v3.0.5 ## explicit; go 1.16 go.yaml.in/yaml/v3 # golang.org/x/net v0.57.0