From c301dd7a0befcec7eb14714f61bd14feb9360df3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:12:56 +0000 Subject: [PATCH] chore(deps): update dependency: bump github.com/getkin/kin-openapi Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.140.0 to 0.149.0. - [Release notes](https://github.com/getkin/kin-openapi/releases) - [Commits](https://github.com/getkin/kin-openapi/compare/v0.140.0...v0.149.0) --- updated-dependencies: - dependency-name: github.com/getkin/kin-openapi dependency-version: 0.149.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 +- go.sum | 16 +- .../getkin/kin-openapi/openapi3/callback.go | 6 - .../getkin/kin-openapi/openapi3/content.go | 6 - .../kin-openapi/openapi3/discriminator.go | 2 +- .../getkin/kin-openapi/openapi3/doc.go | 11 +- .../getkin/kin-openapi/openapi3/encoding.go | 6 - .../getkin/kin-openapi/openapi3/example.go | 6 - .../getkin/kin-openapi/openapi3/header.go | 6 - .../kin-openapi/openapi3/internalize_refs.go | 7 + .../getkin/kin-openapi/openapi3/link.go | 6 - .../getkin/kin-openapi/openapi3/loader.go | 274 +++++++++++---- .../getkin/kin-openapi/openapi3/maplike.go | 2 +- .../getkin/kin-openapi/openapi3/marsh.go | 11 +- .../getkin/kin-openapi/openapi3/media_type.go | 31 +- .../getkin/kin-openapi/openapi3/openapi3.go | 11 +- .../getkin/kin-openapi/openapi3/operation.go | 20 +- .../getkin/kin-openapi/openapi3/origin.go | 172 +++++++++- .../getkin/kin-openapi/openapi3/parameter.go | 6 - .../getkin/kin-openapi/openapi3/path_item.go | 162 ++++++++- .../getkin/kin-openapi/openapi3/ref.go | 19 +- .../getkin/kin-openapi/openapi3/refs.go | 181 +++++++++- .../getkin/kin-openapi/openapi3/refs.tmpl | 31 +- .../kin-openapi/openapi3/request_body.go | 6 - .../getkin/kin-openapi/openapi3/response.go | 12 +- .../getkin/kin-openapi/openapi3/schema.go | 229 +++++++++++-- .../kin-openapi/openapi3/schema_formats.go | 5 + .../openapi3/schema_jsonschema_validator.go | 90 ++++- .../kin-openapi/openapi3/schema_pattern.go | 2 + .../openapi3/schema_validation_settings.go | 4 + .../openapi3/security_requirements.go | 6 - .../kin-openapi/openapi3/security_scheme.go | 8 +- .../getkin/kin-openapi/openapi3/server.go | 7 - .../getkin/kin-openapi/openapi3/stringmap.go | 64 ---- .../getkin/kin-openapi/openapi3/tag.go | 15 +- .../kin-openapi/openapi3/validation_code.go | 278 +++++++++++++++ .../kin-openapi/openapi3/validation_error.go | 186 +++++++++- .../openapi3/validation_error_context.go | 52 ++- .../openapi3/validation_options.go | 10 + .../kin-openapi/openapi3/walk_parameters.go | 126 +++++++ .../kin-openapi/openapi3/walk_schemas.go | 317 ++++++++++++++++++ vendor/github.com/oasdiff/yaml/.golangci.toml | 16 - vendor/github.com/oasdiff/yaml/.golangci.yml | 26 ++ vendor/github.com/oasdiff/yaml/README.md | 16 +- vendor/github.com/oasdiff/yaml/yaml.go | 2 +- vendor/github.com/oasdiff/yaml3/decode.go | 49 +++ vendor/github.com/oasdiff/yaml3/origin.go | 16 +- vendor/github.com/oasdiff/yaml3/scannerc.go | 9 +- vendor/github.com/oasdiff/yaml3/yaml.go | 8 + .../jsonschema/v6/.golangci.yml | 1 - .../santhosh-tekuri/jsonschema/v6/.swp | Bin 20480 -> 0 bytes .../santhosh-tekuri/jsonschema/v6/format.go | 2 +- .../santhosh-tekuri/jsonschema/v6/go.work.sum | 2 +- .../jsonschema/v6/kind/kind.go | 2 +- .../jsonschema/v6/objcompiler.go | 6 +- .../santhosh-tekuri/jsonschema/v6/schema.go | 31 +- .../santhosh-tekuri/jsonschema/v6/util.go | 3 + .../jsonschema/v6/validator.go | 6 +- .../santhosh-tekuri/jsonschema/v6/vocab.go | 4 + vendor/modules.txt | 12 +- 60 files changed, 2283 insertions(+), 345 deletions(-) delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/validation_code.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/walk_parameters.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/walk_schemas.go delete mode 100644 vendor/github.com/oasdiff/yaml/.golangci.toml create mode 100644 vendor/github.com/oasdiff/yaml/.golangci.yml delete mode 100644 vendor/github.com/santhosh-tekuri/jsonschema/v6/.swp diff --git a/go.mod b/go.mod index ac48311..5b499b7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/DistroByte/molecule go 1.26.0 require ( - github.com/getkin/kin-openapi v0.140.0 + github.com/getkin/kin-openapi v0.149.0 github.com/go-chi/chi/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/hashicorp/nomad/api v0.0.0-20260220212019-daca79db0bd6 @@ -17,7 +17,7 @@ require ( github.com/go-openapi/swag/jsonname v0.25.5 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -35,8 +35,8 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/oasdiff/yaml v0.1.0 // indirect - github.com/oasdiff/yaml3 v0.0.13 // indirect + github.com/oasdiff/yaml v0.1.1 // indirect + github.com/oasdiff/yaml3 v0.0.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect diff --git a/go.sum b/go.sum index 60508eb..001008e 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/getkin/kin-openapi v0.140.0 h1:JFn675aXRFjyiZKa/BFWploGldQlI0gobp4J5k0EZ2g= -github.com/getkin/kin-openapi v0.140.0/go.mod h1:lISrB64F0CPcuDJ3LdtPTMJBY8VENjR9wJBdrcT6J3g= +github.com/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isdtMDfA= +github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY= github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= @@ -56,10 +56,10 @@ github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjc github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/oasdiff/yaml v0.1.0 h1:0bqZjfKc/8S9urj4JuwepX41WX9EoA6ifhU3SV06cXg= -github.com/oasdiff/yaml v0.1.0/go.mod h1:kOlRmMdL2X3vucLCEQO5u61SU22RysnfXvcttrZA1O0= -github.com/oasdiff/yaml3 v0.0.13 h1:06svmvOHOVBqF81+sY2EUScvUI/iS/vl2VIeUUxZQwg= -github.com/oasdiff/yaml3 v0.0.13/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY= +github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU= +github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw= +github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -67,8 +67,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/shoenig/test v1.12.2 h1:ZVT8NeIUwGWpZcKaepPmFMoNQ3sVpxvqUh/MAqwFiJI= github.com/shoenig/test v1.12.2/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go index 407f730..6350d79 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -47,9 +47,3 @@ func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption return validateExtensions(ctx, callback.Extensions, callback.Origin) } - -// UnmarshalJSON sets Callbacks to a copy of data. -func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) { - *callbacks, err = unmarshalStringMapP[CallbackRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/content.go b/vendor/github.com/getkin/kin-openapi/openapi3/content.go index 8bf7921..ab79147 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/content.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/content.go @@ -115,9 +115,3 @@ func (content Content) Validate(ctx context.Context, opts ...ValidationOption) e } return nil } - -// UnmarshalJSON sets Content to a copy of data. -func (content *Content) UnmarshalJSON(data []byte) (err error) { - *content, err = unmarshalStringMapP[MediaType](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go index d797701..64219d8 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -13,7 +13,7 @@ type Discriminator struct { Origin *Origin `json:"-" yaml:"-"` PropertyName string `json:"propertyName" yaml:"propertyName"` // required - Mapping StringMap[MappingRef] `json:"mapping,omitempty" yaml:"mapping,omitempty"` + Mapping map[string]MappingRef `json:"mapping,omitempty" yaml:"mapping,omitempty"` } // MappingRef is a ref to a Schema objects. Unlike SchemaRefs it is serialised diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go index 4179030..409dd92 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go @@ -1,8 +1,9 @@ // Package openapi3 parses and writes OpenAPI 3 specification documents. // -// Supports both OpenAPI 3.0 and OpenAPI 3.1: +// Supports OpenAPI 3.0, OpenAPI 3.1, and OpenAPI 3.2: // - OpenAPI 3.0.x: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md // - OpenAPI 3.1.x: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md +// - OpenAPI 3.2.x: https://spec.openapis.org/oas/v3.2.0.html // // OpenAPI 3.1 Features: // - Type arrays with null support (e.g., ["string", "null"]) @@ -11,6 +12,11 @@ // - JSON Schema dialect specification // - SPDX license identifiers // +// OpenAPI 3.2 Features: +// - Media Type Object itemSchema for streaming sequential media types +// - Path Item Object query field for the HTTP QUERY method +// - Path Item Object additionalOperations for custom HTTP methods +// // The implementation maintains 100% backward compatibility with OpenAPI 3.0. // // For OpenAPI 3.1 validation, use the JSON Schema 2020-12 validator option: @@ -22,4 +28,7 @@ // if doc.IsOpenAPI31OrLater() { // // Handle OpenAPI 3.1 specific features // } +// if doc.IsOpenAPI32OrLater() { +// // Handle OpenAPI 3.2 specific features +// } package openapi3 diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go index fd9316a..1094488 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -26,12 +26,6 @@ func NewEncoding() *Encoding { // Encodings is a map of encoding objects keyed by field name. type Encodings map[string]*Encoding -// UnmarshalJSON sets Encodings to a copy of data. -func (encodings *Encodings) UnmarshalJSON(data []byte) (err error) { - *encodings, err = unmarshalStringMapP[Encoding](data) - return -} - func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding { return encoding.WithHeaderRef(name, &HeaderRef{ Value: header, diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/example.go b/vendor/github.com/getkin/kin-openapi/openapi3/example.go index eeb4c6d..eeda137 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/example.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/example.go @@ -82,9 +82,3 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) return validateExtensions(ctx, example.Extensions, example.Origin) } - -// UnmarshalJSON sets Examples to a copy of data. -func (examples *Examples) UnmarshalJSON(data []byte) (err error) { - *examples, err = unmarshalStringMapP[ExampleRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/vendor/github.com/getkin/kin-openapi/openapi3/header.go index 466c8ea..36b74dd 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/header.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/header.go @@ -92,9 +92,3 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er } return nil } - -// UnmarshalJSON sets Headers to a copy of data. -func (headers *Headers) UnmarshalJSON(data []byte) (err error) { - *headers, err = unmarshalStringMapP[HeaderRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go index fe8b379..3dd99ac 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go @@ -351,6 +351,13 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsEx for _, k := range componentNames(s.Discriminator.Mapping) { mapRef := s.Discriminator.Mapping[k] s2 := (*SchemaRef)(&mapRef) + // The mapping value may be a plain schema name rather than a + // reference, and a reference the loader could not follow was left + // as written. Neither has a resolved path, so neither can be named + // by the resolver: leave them exactly as they are. + if s2.RefPath() == nil { + continue + } isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal) doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal) s.Discriminator.Mapping[k] = MappingRef(*s2) diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/link.go b/vendor/github.com/getkin/kin-openapi/openapi3/link.go index 60f78a0..eafeb42 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/link.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -91,9 +91,3 @@ func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error return validateExtensions(ctx, link.Extensions, link.Origin) } - -// UnmarshalJSON sets Links to a copy of data. -func (links *Links) UnmarshalJSON(data []byte) (err error) { - *links, err = unmarshalStringMapP[LinkRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go index 1974294..7706006 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -64,6 +64,13 @@ type Loader struct { visitedDocuments map[string]*T + // originTrees retains each loaded document's origin tree, keyed by the + // document itself so insert and lookup cannot disagree, populated when + // IncludeOrigin is set. resolveComponent uses it to re-attach origins to + // components that lose them in the generic-map path, without re-reading + // or re-parsing the file. + originTrees map[*T]*originTree + visitedRefs map[string]struct{} visitedPath []string backtrack map[string][]func(value any) @@ -133,13 +140,43 @@ func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, el if err != nil { return nil, err } - if err := unmarshal(data, element, loader.IncludeOrigin, resolvedPath); err != nil { + if _, err := unmarshal(data, element, loader.IncludeOrigin, resolvedPath); err != nil { return nil, err } return resolvedPath, nil } +// rememberOriginTree retains doc's origin tree for attachOriginToResolved. +// tree is nil when IncludeOrigin is off or the data took the json path. +// +// The tree is kept only for a document a $ref can reach into untyped, which is +// what attachOriginToResolved exists to re-origin. In practice that means a +// file of shared fragments, whose top level is the fragment name itself rather +// than the fields of an OpenAPI Object: +// +// User: # a $ref to "./schemas.yaml#/User" lands here, untyped +// type: object +// +// Anything OpenAPI defines a field for resolves through typed structures and +// keeps its origins on the way, so its tree could never be read. That includes +// a referenced document that is itself an OpenAPI Object: a $ref to +// "#/components/schemas/User" needs no tree. (A top-level x- extension is +// undefined by the same rule, so a document carrying one keeps its tree too, +// whether or not anything ever points at it.) +// +// Worth the condition: on a 22 MB spec the retained tree was a third of +// everything the loader held. +func (loader *Loader) rememberOriginTree(doc *T, tree *originTree) { + if tree == nil || len(doc.Extensions) == 0 { + return + } + if loader.originTrees == nil { + loader.originTrees = make(map[*T]*originTree) + } + loader.originTrees[doc] = tree +} + func (loader *Loader) readURL(location *url.URL) ([]byte, error) { if f := loader.ReadFromURIFunc; f != nil { return f(loader, location) @@ -169,9 +206,11 @@ func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error) { func (loader *Loader) LoadFromData(data []byte) (*T, error) { loader.resetVisitedPathItemRefs() doc := &T{} - if err := unmarshal(data, doc, loader.IncludeOrigin, nil); err != nil { + tree, err := unmarshal(data, doc, loader.IncludeOrigin, nil) + if err != nil { return nil, err } + loader.rememberOriginTree(doc, tree) if err := loader.ResolveRefsIn(doc, nil); err != nil { return nil, err } @@ -198,9 +237,11 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR doc := &T{} loader.visitedDocuments[uri] = doc - if err := unmarshal(data, doc, loader.IncludeOrigin, location); err != nil { + tree, err := unmarshal(data, doc, loader.IncludeOrigin, location) + if err != nil { return nil, err } + loader.rememberOriginTree(doc, tree) doc.url = copyURI(location) @@ -429,10 +470,12 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv // Special case due to multijson case *SchemaRef: if pathPart == "additionalProperties" { - if ap := c.Value.AdditionalProperties.Has; ap != nil { - cursor = *ap - } else { - cursor = c.Value.AdditionalProperties.Schema + if s := c.Value; s != nil { + if ap := s.AdditionalProperties.Has; ap != nil { + cursor = *ap + } else { + cursor = s.AdditionalProperties.Schema + } } attempted = true } @@ -468,7 +511,7 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv if err2 != nil { return nil, nil, err } - if err2 = unmarshal(data, &cursor, loader.IncludeOrigin, path); err2 != nil { + if _, err2 = unmarshal(data, &cursor, loader.IncludeOrigin, path); err2 != nil { return nil, nil, err } if cursor, err2 = drill(cursor); err2 != nil || cursor == nil { @@ -516,6 +559,10 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv if err := codec(cursor, resolved); err != nil { return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved)) } + // The value came from a generic map in T.Extensions (a $ref to an + // arbitrary top-level key), so the json round-trip above stripped its + // origins. Re-attach them from the file, best-effort. + loader.attachOriginToResolved(resolved, componentDoc, fragment) return componentDoc, componentPath, nil default: @@ -523,6 +570,33 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv } } +// attachOriginToResolved re-attaches source origins to a component resolved +// through the generic-map path: a $ref to a schema under an arbitrary top-level +// key lands in T.Extensions, and the json round-trip in resolveComponent strips +// its origin. It walks the document's retained origin tree (see originTrees, +// populated when the document was first unmarshaled: no re-read, no re-parse) +// down to the ref fragment and applies that subtree, so the object carries the +// same origins a typed resolution would, with the original file's line numbers. +// Best-effort: a missing tree or fragment leaves the object without origins. +func (loader *Loader) attachOriginToResolved(resolved any, componentDoc *T, fragment string) { + if !loader.IncludeOrigin { + return + } + tree := loader.originTrees[componentDoc] + if tree == nil { + return + } + for part := range strings.SplitSeq(strings.Trim(fragment, "/"), "/") { + if part == "" { + continue + } + if tree = tree.Fields[unescapeRefString(part)]; tree == nil { + return + } + } + applyOrigins(resolved, tree) +} + func readableType(x any) string { switch x.(type) { case *Callback: @@ -647,7 +721,17 @@ var ( errMUSTSecurityScheme = errors.New("invalid securityScheme: value MUST be an object") ) +func applyHeaderRefMetadata(value *Header, component *HeaderRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || component.Description == nil { + return + } + + value.Description = *component.Description +} + func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if component.isEmpty() { return errMUSTHeader } @@ -658,6 +742,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*Header) + applyHeaderRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -687,6 +772,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applyHeaderRefMetadata(component.Value, component, isOpenAPI31OrLater) } value := component.Value if value == nil { @@ -706,7 +792,17 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat return nil } +func applyParameterRefMetadata(value *Parameter, component *ParameterRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || component.Description == nil { + return + } + + value.Description = *component.Description +} + func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if component.isEmpty() { return errMUSTParameter } @@ -717,6 +813,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*Parameter) + applyParameterRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -746,6 +843,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applyParameterRefMetadata(component.Value, component, isOpenAPI31OrLater) } value := component.Value if value == nil { @@ -756,16 +854,8 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum return errors.New("cannot contain both schema and content in a parameter") } for _, name := range componentNames(value.Content) { - contentType := value.Content[name] - if schema := contentType.Schema; schema != nil { - if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { - return err - } - } - for _, k := range componentNames(contentType.Examples) { - if err := loader.resolveExampleRef(doc, contentType.Examples[k], documentPath); err != nil { - return err - } + if err := loader.resolveMediaTypeRefs(doc, value.Content[name], documentPath); err != nil { + return err } } if schema := value.Schema; schema != nil { @@ -781,7 +871,17 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum return nil } +func applyRequestBodyRefMetadata(value *RequestBody, component *RequestBodyRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || component.Description == nil { + return + } + + value.Description = *component.Description +} + func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if component.isEmpty() { return errMUSTRequestBody } @@ -792,6 +892,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*RequestBody) + applyRequestBodyRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -821,6 +922,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applyRequestBodyRefMetadata(component.Value, component, isOpenAPI31OrLater) } value := component.Value if value == nil { @@ -828,27 +930,24 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d } for _, name := range componentNames(value.Content) { - contentType := value.Content[name] - if contentType == nil { - continue - } - for _, name := range componentNames(contentType.Examples) { - example := contentType.Examples[name] - if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { - return err - } - contentType.Examples[name] = example - } - if schema := contentType.Schema; schema != nil { - if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { - return err - } + if err := loader.resolveMediaTypeRefs(doc, value.Content[name], documentPath); err != nil { + return err } } return nil } +func applyResponseRefMetadata(value *Response, component *ResponseRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || component.Description == nil { + return + } + + value.Description = component.Description +} + func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if component.isEmpty() { return errMUSTResponse } @@ -859,6 +958,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*Response) + applyResponseRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -888,6 +988,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applyResponseRefMetadata(component.Value, component, isOpenAPI31OrLater) } value := component.Value if value == nil { @@ -901,22 +1002,8 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen } } for _, name := range componentNames(value.Content) { - contentType := value.Content[name] - if contentType == nil { - continue - } - for _, name := range componentNames(contentType.Examples) { - example := contentType.Examples[name] - if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { - return err - } - contentType.Examples[name] = example - } - if schema := contentType.Schema; schema != nil { - if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { - return err - } - contentType.Schema = schema + if err := loader.resolveMediaTypeRefs(doc, value.Content[name], documentPath); err != nil { + return err } } for _, name := range componentNames(value.Links) { @@ -928,6 +1015,30 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen return nil } +func (loader *Loader) resolveMediaTypeRefs(doc *T, mediaType *MediaType, documentPath *url.URL) (err error) { + if mediaType == nil { + return + } + if schema := mediaType.Schema; schema != nil { + if err = loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return + } + } + if itemSchema := mediaType.ItemSchema; itemSchema != nil { + if err = loader.resolveSchemaRef(doc, itemSchema, documentPath, []string{}); err != nil { + return + } + } + for _, name := range componentNames(mediaType.Examples) { + example := mediaType.Examples[name] + if err = loader.resolveExampleRef(doc, example, documentPath); err != nil { + return + } + mediaType.Examples[name] = example + } + return +} + func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL, visited []string) (err error) { if component.isEmpty() { return errMUSTSchema @@ -1025,19 +1136,25 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat } // Discriminator mapping refs are a special case since they are not full // ref objects but are plain strings that reference schema objects. - // Only resolve refs that look like external references (contain a path). - // Plain schema names like "Dog" or internal refs like "#/components/schemas/Dog" - // don't need to be resolved by the loader. + // Plain schema names like "Dog" are not references and are left alone. if value.Discriminator != nil { + inExternalDoc := documentPath != nil && documentPath.Path != "" && documentPath.Path != loader.rootLocation for _, k := range componentNames(value.Discriminator.Mapping) { v := value.Discriminator.Mapping[k] - // Only resolve if it looks like an external ref (contains path separator) - if strings.Contains(v.Ref, "/") && !strings.HasPrefix(v.Ref, "#") { - if err := loader.resolveSchemaRef(doc, (*SchemaRef)(&v), documentPath, visited); err != nil { - return err - } - value.Discriminator.Mapping[k] = v + if !strings.Contains(v.Ref, "/") { + continue } + // A document-local mapping needs resolving only when it lives in + // another document, because InternalizeRefs then has to rewrite it + // against that document. One in the root document is already + // written against the document it will end up in. + if strings.HasPrefix(v.Ref, "#") && !inExternalDoc { + continue + } + if err := loader.resolveSchemaRef(doc, (*SchemaRef)(&v), documentPath, visited); err != nil { + return err + } + value.Discriminator.Mapping[k] = v } } @@ -1109,7 +1226,17 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat return nil } +func applySecuritySchemeRefMetadata(value *SecurityScheme, component *SecuritySchemeRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || component.Description == nil { + return + } + + value.Description = *component.Description +} + func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if component.isEmpty() { return errMUSTSecurityScheme } @@ -1120,6 +1247,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*SecurityScheme) + applySecuritySchemeRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -1149,17 +1277,34 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applySecuritySchemeRefMetadata(component.Value, component, isOpenAPI31OrLater) } return nil } +func applyExampleRefMetadata(value *Example, component *ExampleRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || (component.Summary == nil && component.Description == nil) { + return + } + + if component.Summary != nil { + value.Summary = *component.Summary + } + if component.Description != nil { + value.Description = *component.Description + } +} + func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if ref := component.Ref; ref != "" { if component.Value != nil { return nil } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*Example) + applyExampleRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -1189,6 +1334,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applyExampleRefMetadata(component.Value, component, isOpenAPI31OrLater) } return nil } @@ -1249,7 +1395,17 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen return nil } +func applyLinkRefMetadata(value *Link, component *LinkRef, isOpenAPI31OrLater bool) { + if !isOpenAPI31OrLater || value == nil || component.Description == nil { + return + } + + value.Description = *component.Description +} + func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *url.URL) (err error) { + isOpenAPI31OrLater := doc.IsOpenAPI31OrLater() + if component.isEmpty() { return errMUSTLink } @@ -1260,6 +1416,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u } if !loader.shouldVisitRef(ref, func(value any) { component.Value = value.(*Link) + applyLinkRefMetadata(component.Value, component, isOpenAPI31OrLater) refPath, _ := loader.resolveRefPath(ref, documentPath) component.setRefPath(refPath) }) { @@ -1289,6 +1446,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + applyLinkRefMetadata(component.Value, component, isOpenAPI31OrLater) } return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go index 3a97be1..f651b52 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go @@ -80,7 +80,7 @@ func (responses Responses) JSONLookup(token string) (any, error) { // MarshalYAML returns the YAML encoding of Responses. func (responses *Responses) MarshalYAML() (any, error) { - if responses == nil { + if responses == nil || responses.isExplicitlyNull() { return nil, nil } m := make(map[string]any, responses.Len()+len(responses.Extensions)) diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go b/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go index 6483fa9..f895e6e 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go @@ -17,12 +17,15 @@ func unmarshalError(jsonUnmarshalErr error) error { return jsonUnmarshalErr } -func unmarshal(data []byte, v any, includeOrigin bool, location *url.URL) error { +// unmarshal decodes data into v. It returns the document origin tree when +// includeOrigin is set and the data took the yaml path (json input carries no +// origins), so the caller can retain it (see Loader.originTrees). +func unmarshal(data []byte, v any, includeOrigin bool, location *url.URL) (*originTree, error) { var jsonErr, yamlErr error // See https://github.com/getkin/kin-openapi/issues/680 if jsonErr = json.Unmarshal(data, v); jsonErr == nil { - return nil + return nil, nil } // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys @@ -35,11 +38,11 @@ func unmarshal(data []byte, v any, includeOrigin bool, location *url.URL) error DisableTimestamps: true, }); err == nil { applyOrigins(v, tree) - return nil + return tree, nil } else { yamlErr = err } // If both unmarshaling attempts fail, return a new error that includes both errors - return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr) + return nil, fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr) } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go index c571589..e070260 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -14,10 +14,11 @@ type MediaType struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` - Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Example any `json:"example,omitempty" yaml:"example,omitempty"` - Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` - Encoding Encodings `json:"encoding,omitempty" yaml:"encoding,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + ItemSchema *SchemaRef `json:"itemSchema,omitempty" yaml:"itemSchema,omitempty"` // OpenAPI >=3.2 + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Encoding Encodings `json:"encoding,omitempty" yaml:"encoding,omitempty"` } var _ jsonpointer.JSONPointable = (*MediaType)(nil) @@ -73,11 +74,14 @@ func (mediaType MediaType) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of MediaType. func (mediaType MediaType) MarshalYAML() (any, error) { - m := make(map[string]any, 4+len(mediaType.Extensions)) + m := make(map[string]any, 5+len(mediaType.Extensions)) maps.Copy(m, mediaType.Extensions) if x := mediaType.Schema; x != nil { m["schema"] = x } + if x := mediaType.ItemSchema; x != nil { + m["itemSchema"] = x + } if x := mediaType.Example; x != nil { m["example"] = x } @@ -99,13 +103,13 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error { } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "schema") + delete(x.Extensions, "itemSchema") delete(x.Extensions, "example") delete(x.Extensions, "examples") delete(x.Extensions, "encoding") if len(x.Extensions) == 0 { x.Extensions = nil } - delete(x.Encoding, originKey) *mediaType = MediaType(x) return nil } @@ -148,6 +152,14 @@ func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOpti } } } + if itemSchema := mediaType.ItemSchema; itemSchema != nil { + if !getValidationOptions(ctx).isOpenAPI32OrLater { + return errFieldFor32Plus("itemSchema", mediaType.Origin) + } + if err := itemSchema.Validate(ctx); err != nil { + return err + } + } return validateExtensions(ctx, mediaType.Extensions, mediaType.Origin) } @@ -164,6 +176,13 @@ func (mediaType MediaType) JSONLookup(token string) (any, error) { } case "example": return mediaType.Example, nil + case "itemSchema": + if mediaType.ItemSchema != nil { + if mediaType.ItemSchema.Ref != "" { + return &Ref{Ref: mediaType.ItemSchema.Ref}, nil + } + return mediaType.ItemSchema.Value, nil + } case "examples": return mediaType.Examples, nil case "encoding": diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go index 267064f..9671b91 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go @@ -53,8 +53,11 @@ func (doc *T) IsOpenAPI31OrLater() bool { return slices.Contains([]string{"3.1", "3.2"}, doc.OpenAPIMajorMinor()) } -func errFieldFor31Plus(field string, origin *Origin) error { - return newFieldFor31Plus(field, origin) +// IsOpenAPI32OrLater returns whether doc is an OpenAPI document version >=3.2. +// Returns true for 3.2, 3.2.0, ... +// And false for cases where IsOpenAPI31OrLater returns true for 3.1.x and for invalid strings. +func (doc *T) IsOpenAPI32OrLater() bool { + return doc.OpenAPIMajorMinor() == "3.2" } func errValueOfFieldFor31Plus(value any, field string) error { @@ -174,7 +177,6 @@ func (doc *T) UnmarshalJSON(data []byte) error { if len(x.Extensions) == 0 { x.Extensions = nil } - delete(x.Webhooks, originKey) *doc = T(x) return nil } @@ -266,6 +268,9 @@ func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error { if doc.IsOpenAPI31OrLater() { opts = append(opts, IsOpenAPI31OrLater()) } + if doc.IsOpenAPI32OrLater() { + opts = append(opts, IsOpenAPI32OrLater()) + } ctx = WithValidationOptions(ctx, opts...) me := newErrCollector(ctx) diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go index 1ec78cd..5122410 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -33,8 +33,8 @@ type Operation struct { // Optional body parameter. RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` - // Responses. - Responses *Responses `json:"responses" yaml:"responses"` // Required + // Responses are required in OpenAPI 3.0 and optional in OpenAPI 3.1 and later. + Responses *Responses `json:"responses" yaml:"responses"` // Optional callbacks Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` @@ -87,7 +87,9 @@ func (operation Operation) MarshalYAML() (any, error) { if x := operation.RequestBody; x != nil { m["requestBody"] = x } - m["responses"] = operation.Responses + if x := operation.Responses; x != nil { + m["responses"] = x + } if x := operation.Callbacks; len(x) != 0 { m["callbacks"] = x } @@ -113,7 +115,11 @@ func (operation *Operation) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &x); err != nil { return unmarshalError(err) } + _ = json.Unmarshal(data, &x.Extensions) + if value, present := x.Extensions["responses"]; present && value == nil { + x.Responses = &Responses{explicitlyNull: true} + } delete(x.Extensions, "tags") delete(x.Extensions, "summary") delete(x.Extensions, "description") @@ -203,12 +209,14 @@ func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOpti } } - if v := operation.Responses; v != nil { + if v := operation.Responses; v != nil && !v.isExplicitlyNull() { if err := me.emit(v.Validate(ctx)); err != nil { return err } - } else if err := me.emit(newOperationResponsesRequired(operation.Origin)); err != nil { - return err + } else if v != nil || !getValidationOptions(ctx).isOpenAPI31OrLater { + if err := me.emit(newOperationResponsesRequired(operation.Origin)); err != nil { + return err + } } if v := operation.ExternalDocs; v != nil { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/origin.go b/vendor/github.com/getkin/kin-openapi/openapi3/origin.go index 1403bd8..1379a7c 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/origin.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/origin.go @@ -1,32 +1,111 @@ package openapi3 import ( + "encoding/json" "reflect" + "slices" "strings" "github.com/oasdiff/yaml" ) -const originKey = "__origin__" - var originPtrType = reflect.TypeFor[*Origin]() // Origin contains the origin of a collection. // Key is the location of the collection itself. -// Fields is a map of the location of each scalar field in the collection. +// Fields holds the location of each scalar field in the collection. // Sequences is a map of the location of each item in sequence-valued fields. +// +// Sequences stays a map although Fields is a slice, which is deliberate. +// FieldLocations drops the map because Location.Name already carries the key, +// so the map was storing information the value repeated. Here Location.Name +// holds the *item's* value (an enum member, a required property) while the key +// is the *field's* name ("enum", "required", "tags"), so a slice would need a +// wrapper type invented to hold it. The memory argument is also much weaker: +// only a collection with a sequence-valued field allocates one at all, which +// measured at 5% of collections on a large spec, and a nil map is free. type Origin struct { Key *Location `json:"key,omitempty" yaml:"key,omitempty"` - Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"` + Fields FieldLocations `json:"fields,omitempty" yaml:"fields,omitempty"` Sequences map[string][]Location `json:"sequences,omitempty" yaml:"sequences,omitempty"` } +// FieldLocations holds the locations of a collection's scalar fields, in the +// order they appear in the document. +// +// It is a slice rather than a map[string]Location because a collection carries +// only a handful of fields, while a Go map allocates a whole bucket per +// collection whatever it holds. On a large document that overhead dominated +// the retained size of a parsed spec. Each Location already carries its Name, +// so the lookup key costs nothing extra here. +type FieldLocations []Location + +// Get returns the location of the named field, or the zero Location when the +// field has none. Use Lookup to tell an absent field from a zero location. +func (f FieldLocations) Get(name string) Location { + loc, _ := f.Lookup(name) + return loc +} + +// Lookup returns the location of the named field and whether it was found. +// The scan is linear: collections have few fields, and a linear scan over a +// contiguous slice beats a map lookup at these sizes. +// +// Deliberately a hand-written loop rather than slices.IndexFunc: the closure +// does not inline, so IndexFunc pays a call per element. Measured on 3/6/12 +// fields it is 5-100% slower on a hit and 2-3x slower on a miss, and misses +// are the common case here (most fields carry no recorded location). +func (f FieldLocations) Lookup(name string) (Location, bool) { + for i := range f { + if f[i].Name == name { + return f[i], true + } + } + return Location{}, false +} + +// MarshalJSON keeps the serialized shape a name-keyed object, as it was when +// this was a map, so the change is invisible to anything reading the output. +func (f FieldLocations) MarshalJSON() ([]byte, error) { + m := make(map[string]Location, len(f)) + for _, loc := range f { + m[loc.Name] = loc + } + return json.Marshal(m) +} + +// UnmarshalJSON reads the name-keyed object written by MarshalJSON. Entries are +// sorted by name, since a JSON object carries no order to restore. +func (f *FieldLocations) UnmarshalJSON(data []byte) error { + var m map[string]Location + if err := json.Unmarshal(data, &m); err != nil { + return err + } + out := make(FieldLocations, 0, len(m)) + for name, loc := range m { + if loc.Name == "" { + loc.Name = name + } + out = append(out, loc) + } + slices.SortFunc(out, func(a, b Location) int { return strings.Compare(a.Name, b.Name) }) + *f = out + return nil +} + // Location is a struct that contains the location of a field. type Location struct { File string `json:"file,omitempty" yaml:"file,omitempty"` Line int `json:"line,omitempty" yaml:"line,omitempty"` Column int `json:"column,omitempty" yaml:"column,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"` + + // EndLine and EndColumn mark the end of the block this location heads (set + // only on Origin.Key). For an operation or schema this spans the whole + // block, so a consumer can extract the entire element from its source. + // Both are zero when the underlying YAML carried no end information. + EndLine int `json:"endLine,omitempty" yaml:"endLine,omitempty"` + EndColumn int `json:"endColumn,omitempty" yaml:"endColumn,omitempty"` } // originFromSeq parses the compact []any sequence produced by yaml3's addOrigin. @@ -55,17 +134,17 @@ func originFromSeq(s []any) *Origin { nf := toInt(s[idx]) idx++ if nf > 0 && idx+nf*3 <= len(s) { - o.Fields = make(map[string]Location, nf) + o.Fields = make(FieldLocations, 0, nf) for range nf { fname, _ := s[idx].(string) delta := toInt(s[idx+1]) col := toInt(s[idx+2]) - o.Fields[fname] = Location{ + o.Fields = append(o.Fields, Location{ File: file, Line: keyLine + delta, Column: col, Name: fname, - } + }) idx += 3 } } @@ -88,17 +167,28 @@ func originFromSeq(s []any) *Origin { } count := toInt(s[idx]) idx++ - locs := make([]Location, count) + locs := make([]Location, 0, count) for j := 0; j < count && idx+2 < len(s); j++ { name, _ := s[idx].(string) delta := toInt(s[idx+1]) col := toInt(s[idx+2]) - locs[j] = Location{File: file, Line: keyLine + delta, Column: col, Name: name} + locs = append(locs, Location{File: file, Line: keyLine + delta, Column: col, Name: name}) idx += 3 } o.Sequences[sname] = locs } } + + // Trailing block end (yaml3 >= the end-position release): end_delta, end_col. + // Reconstruct the end of the whole block on Origin.Key so a consumer can + // extract the entire element. Older origin sequences omit these, leaving + // EndLine/EndColumn zero. end_col == 0 means no end information was recorded. + if o.Key != nil && idx+1 < len(s) { + if endCol := toInt(s[idx+1]); endCol > 0 { + o.Key.EndLine = keyLine + toInt(s[idx]) + o.Key.EndColumn = endCol + } + } return o } @@ -113,6 +203,48 @@ func toInt(v any) int { return 0 } +// isScalarValuedMapField reports whether v is a non-empty map whose element +// type is a scalar (string, bool, or a numeric kind). Such a map decodes +// without an Origin field of its own, unlike a pointer- or struct-valued map +// whose elements each carry their own Origin. +func isScalarValuedMapField(v reflect.Value) bool { + if v.Kind() != reflect.Map || v.IsNil() || v.Len() == 0 { + return false + } + switch v.Type().Elem().Kind() { + case reflect.String, reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return true + } + return false +} + +// recordMapKeyLocations moves the map-key locations from a scalar-valued map's +// own subtree onto parentOrigin.Sequences[field], so each key is addressable by +// name (the same shape used for sequence items). It is a no-op when the child +// carries no origin data. Keys are sorted for deterministic output. +// +// childOrigin is discarded here, and nothing else holds its Fields, so the +// slice is sorted and handed over in place rather than copied. +func recordMapKeyLocations(parentOrigin *Origin, field string, childTree *yaml.OriginTree) { + s, ok := childTree.Origin.([]any) + if !ok { + return + } + childOrigin := originFromSeq(s) + if childOrigin == nil || len(childOrigin.Fields) == 0 { + return + } + locs := childOrigin.Fields + slices.SortFunc(locs, func(a, b Location) int { return strings.Compare(a.Name, b.Name) }) + if parentOrigin.Sequences == nil { + parentOrigin.Sequences = make(map[string][]Location) + } + parentOrigin.Sequences[field] = locs +} + // applyOrigins walks a Go struct tree and a parallel OriginTree, setting // Origin fields on each struct from the extracted origin data. func applyOrigins(v any, tree *yaml.OriginTree) { @@ -150,12 +282,14 @@ func applyOriginsToStruct(val reflect.Value, ptr reflect.Value, tree *yaml.Origi typ := val.Type() // Set Origin field for structs whose Origin field has a "-" json tag. + var structOrigin *Origin if tree.Origin != nil { if sf, ok := typ.FieldByName("Origin"); ok && sf.Type == originPtrType { tag := sf.Tag.Get("json") if tag == "-" { if s, ok := tree.Origin.([]any); ok { - val.FieldByName("Origin").Set(reflect.ValueOf(originFromSeq(s))) + structOrigin = originFromSeq(s) + val.FieldByName("Origin").Set(reflect.ValueOf(structOrigin)) } } } @@ -172,9 +306,19 @@ func applyOriginsToStruct(val reflect.Value, ptr reflect.Value, tree *yaml.Origi continue } childTree := tree.Fields[tag] - if childTree != nil { - applyOriginsToValue(val.Field(i), childTree) + if childTree == nil { + continue } + // A scalar-valued map (e.g. OAuth scopes: map[string]string) decodes into + // a Go map that has no Origin field of its own, so its per-key locations — + // present in the child subtree — would otherwise be lost. Record them on + // this struct's Origin as a named sequence so a consumer can locate each + // entry by key. Object- or pointer-valued maps are excluded: their values + // carry their own Origin via the recursion below. + if structOrigin != nil && isScalarValuedMapField(val.Field(i)) { + recordMapKeyLocations(structOrigin, tag, childTree) + } + applyOriginsToValue(val.Field(i), childTree) } // Handle wrapper types whose inner struct has no json tag: @@ -253,3 +397,7 @@ func jsonTagName(f reflect.StructField) string { name, _, _ := strings.Cut(tag, ",") return name } + +// originTree aliases the decoder-side origin tree, so the loader and marsh can +// carry it without referencing the yaml package directly. +type originTree = yaml.OriginTree diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go index b1010a6..2b99626 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -409,9 +409,3 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti return validateExtensions(ctx, parameter.Extensions, parameter.Origin) } - -// UnmarshalJSON sets ParametersMap to a copy of data. -func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error) { - *parametersMap, err = unmarshalStringMapP[ParameterRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go index 701d991..ecaa7a9 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -3,9 +3,10 @@ package openapi3 import ( "context" "encoding/json" - "fmt" "maps" "net/http" + "regexp" + "strings" ) // PathItem is specified by OpenAPI/Swagger standard version 3. @@ -26,10 +27,47 @@ type PathItem struct { Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"` + Query *Operation `json:"query,omitempty" yaml:"query,omitempty"` // OpenAPI >=3.2 Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` + + // AdditionalOperations maps custom HTTP method names to operations. + // Keys are case-sensitive HTTP method tokens and must not duplicate + // the methods covered by the fixed fields above. + AdditionalOperations map[string]*Operation `json:"additionalOperations,omitempty" yaml:"additionalOperations,omitempty"` // OpenAPI >=3.2 } +// MethodQuery is the HTTP QUERY method, introduced as a fixed Path Item +// Object field by OpenAPI 3.2. The net/http package has no constant for it. +const MethodQuery = "QUERY" + +// fixedPathItemMethods are the HTTP methods that have a dedicated PathItem +// field; they must not appear as AdditionalOperations keys. +var fixedPathItemMethods = map[string]struct{}{ + http.MethodConnect: {}, + http.MethodDelete: {}, + http.MethodGet: {}, + http.MethodHead: {}, + http.MethodOptions: {}, + http.MethodPatch: {}, + http.MethodPost: {}, + http.MethodPut: {}, + http.MethodTrace: {}, + MethodQuery: {}, +} + +// PathItemMethods returns the HTTP methods that have a dedicated Path Item +// Object field, sorted. Any other method an operation is registered under +// lives in AdditionalOperations, so a caller that treats the two groups +// differently can tell them apart without listing the fixed methods itself. +func PathItemMethods() []string { + return componentNames(fixedPathItemMethods) +} + +// httpTokenRe matches an RFC 9110 token, the grammar HTTP method names +// must follow. +var httpTokenRe = regexp.MustCompile("^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$") + // MarshalJSON returns the JSON encoding of PathItem. func (pathItem PathItem) MarshalJSON() ([]byte, error) { x, err := pathItem.MarshalYAML() @@ -45,7 +83,7 @@ func (pathItem PathItem) MarshalYAML() (any, error) { return Ref{Ref: ref}, nil } - m := make(map[string]any, 13+len(pathItem.Extensions)) + m := make(map[string]any, 15+len(pathItem.Extensions)) maps.Copy(m, pathItem.Extensions) if x := pathItem.Summary; x != "" { m["summary"] = x @@ -80,6 +118,12 @@ func (pathItem PathItem) MarshalYAML() (any, error) { if x := pathItem.Trace; x != nil { m["trace"] = x } + if x := pathItem.Query; x != nil { + m["query"] = x + } + if x := pathItem.AdditionalOperations; len(x) != 0 { + m["additionalOperations"] = x + } if x := pathItem.Servers; len(x) != 0 { m["servers"] = x } @@ -109,6 +153,8 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error { delete(x.Extensions, "post") delete(x.Extensions, "put") delete(x.Extensions, "trace") + delete(x.Extensions, "query") + delete(x.Extensions, "additionalOperations") delete(x.Extensions, "servers") delete(x.Extensions, "parameters") if len(x.Extensions) == 0 { @@ -118,8 +164,50 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error { return nil } -func (pathItem *PathItem) Operations() map[string]*Operation { - operations := make(map[string]*Operation) +// operationEntry pairs an operation with the method it is registered under +// and the JSON pointer suffix that addresses it inside a PathItem. +type operationEntry struct { + method string + operation *Operation + // pointerSuffix is "/get" for a fixed field and + // "/additionalOperations/COPY" for a custom method. + pointerSuffix string +} + +// operationEntries returns pathItem's operations in a deterministic order +// (fixed fields first, then AdditionalOperations, each sorted by method), +// paired with the JSON pointer suffix addressing them. Custom methods +// shadowed by a fixed field are skipped, matching Operations(). +func (pathItem *PathItem) operationEntries() []operationEntry { + fixed := pathItem.fixedOperations() + entries := make([]operationEntry, 0, len(fixed)+len(pathItem.AdditionalOperations)) + for _, method := range componentNames(fixed) { + entries = append(entries, operationEntry{ + method: method, + operation: fixed[method], + pointerSuffix: "/" + strings.ToLower(method), + }) + } + for _, method := range componentNames(pathItem.AdditionalOperations) { + operation := pathItem.AdditionalOperations[method] + if operation == nil { + continue + } + if _, ok := fixed[method]; ok { + continue + } + entries = append(entries, operationEntry{ + method: method, + operation: operation, + pointerSuffix: "/additionalOperations/" + escapeRefString(method), + }) + } + return entries +} + +// fixedOperations returns the operations held by PathItem's fixed fields. +func (pathItem *PathItem) fixedOperations() map[string]*Operation { + operations := make(map[string]*Operation, len(fixedPathItemMethods)) if v := pathItem.Connect; v != nil { operations[http.MethodConnect] = v } @@ -147,6 +235,27 @@ func (pathItem *PathItem) Operations() map[string]*Operation { if v := pathItem.Trace; v != nil { operations[http.MethodTrace] = v } + if v := pathItem.Query; v != nil { + operations[MethodQuery] = v + } + return operations +} + +// Operations returns pathItem's operations keyed by HTTP method, including +// the OpenAPI 3.2 QUERY field and any AdditionalOperations. When an +// AdditionalOperations key collides with a fixed field, the fixed field +// wins (Validate reports the duplicate separately). +func (pathItem *PathItem) Operations() map[string]*Operation { + operations := pathItem.fixedOperations() + for method, operation := range pathItem.AdditionalOperations { + if operation == nil { + continue + } + if _, ok := operations[method]; ok { + continue + } + operations[method] = operation + } return operations } @@ -170,8 +279,10 @@ func (pathItem *PathItem) GetOperation(method string) *Operation { return pathItem.Put case http.MethodTrace: return pathItem.Trace + case MethodQuery: + return pathItem.Query default: - panic(fmt.Errorf("unsupported HTTP method %q", method)) + return pathItem.AdditionalOperations[method] } } @@ -195,8 +306,18 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) { pathItem.Put = operation case http.MethodTrace: pathItem.Trace = operation + case MethodQuery: + pathItem.Query = operation default: - panic(fmt.Errorf("unsupported HTTP method %q", method)) + // Custom (OpenAPI >=3.2) methods live in AdditionalOperations. + if operation == nil { + delete(pathItem.AdditionalOperations, method) + return + } + if pathItem.AdditionalOperations == nil { + pathItem.AdditionalOperations = make(map[string]*Operation, 1) + } + pathItem.AdditionalOperations[method] = operation } } @@ -205,6 +326,33 @@ func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption ctx = WithValidationOptions(ctx, opts...) me := newErrCollector(ctx) + if !getValidationOptions(ctx).isOpenAPI32OrLater { + if pathItem.Query != nil { + if err := me.emit(errFieldFor32Plus("query", pathItem.Origin)); err != nil { + return err + } + } + if len(pathItem.AdditionalOperations) != 0 { + if err := me.emit(errFieldFor32Plus("additionalOperations", pathItem.Origin)); err != nil { + return err + } + } + } + + for _, method := range componentNames(pathItem.AdditionalOperations) { + if _, ok := fixedPathItemMethods[method]; ok { + if err := me.emit(newAdditionalOperationsDuplicateMethod(method, pathItem.Origin)); err != nil { + return err + } + continue + } + if !httpTokenRe.MatchString(method) { + if err := me.emit(newAdditionalOperationsInvalidMethod(method, pathItem.Origin)); err != nil { + return err + } + } + } + operations := pathItem.Operations() for _, method := range componentNames(operations) { @@ -239,6 +387,8 @@ func (pathItem *PathItem) isEmpty() bool { pathItem.Post == nil && pathItem.Put == nil && pathItem.Trace == nil && + pathItem.Query == nil && + len(pathItem.AdditionalOperations) == 0 && len(pathItem.Servers) == 0 && len(pathItem.Parameters) == 0 } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/ref.go b/vendor/github.com/getkin/kin-openapi/openapi3/ref.go index 095178d..6d2ae82 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/ref.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/ref.go @@ -8,12 +8,15 @@ import ( //go:generate go run refsgenerator.go -// Ref is specified by OpenAPI/Swagger 3.0 standard. -// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object +// Ref represents the common fields of an OpenAPI Reference Object. +// Summary and Description are supported by OpenAPI 3.1 and later. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#reference-object type Ref struct { - Ref string `json:"$ref" yaml:"$ref"` - Extensions map[string]any `json:"-" yaml:"-"` - Origin *Origin `json:"-" yaml:"-"` + Ref string `json:"$ref" yaml:"$ref"` + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"-" yaml:"-"` } // MarshalYAML returns the YAML encoding of Ref. @@ -23,6 +26,12 @@ func (x Ref) MarshalYAML() (any, error) { if x := x.Ref; x != "" { m["$ref"] = x } + if x.Summary != nil { + m["summary"] = *x.Summary + } + if x.Description != nil { + m["description"] = *x.Description + } return m, nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go index 2a4066f..c04d305 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -17,6 +17,9 @@ type CallbackRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *Callback @@ -51,7 +54,12 @@ func (x *CallbackRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of CallbackRef. func (x CallbackRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -74,6 +82,10 @@ func (x *CallbackRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -100,6 +112,14 @@ func (x *CallbackRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -159,6 +179,9 @@ type ExampleRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *Example @@ -193,7 +216,12 @@ func (x *ExampleRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of ExampleRef. func (x ExampleRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -216,6 +244,10 @@ func (x *ExampleRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -242,6 +274,14 @@ func (x *ExampleRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -301,6 +341,9 @@ type HeaderRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *Header @@ -335,7 +378,12 @@ func (x *HeaderRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of HeaderRef. func (x HeaderRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -358,6 +406,10 @@ func (x *HeaderRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -384,6 +436,14 @@ func (x *HeaderRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -443,6 +503,9 @@ type LinkRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *Link @@ -477,7 +540,12 @@ func (x *LinkRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of LinkRef. func (x LinkRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -500,6 +568,10 @@ func (x *LinkRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -526,6 +598,14 @@ func (x *LinkRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -585,6 +665,9 @@ type ParameterRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *Parameter @@ -619,7 +702,12 @@ func (x *ParameterRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of ParameterRef. func (x ParameterRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -642,6 +730,10 @@ func (x *ParameterRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -668,6 +760,14 @@ func (x *ParameterRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -727,6 +827,9 @@ type RequestBodyRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *RequestBody @@ -761,7 +864,12 @@ func (x *RequestBodyRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of RequestBodyRef. func (x RequestBodyRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -784,6 +892,10 @@ func (x *RequestBodyRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -810,6 +922,14 @@ func (x *RequestBodyRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -869,6 +989,9 @@ type ResponseRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *Response @@ -903,7 +1026,12 @@ func (x *ResponseRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of ResponseRef. func (x ResponseRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -926,6 +1054,10 @@ func (x *ResponseRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -952,6 +1084,14 @@ func (x *ResponseRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { @@ -1048,7 +1188,10 @@ func (x *SchemaRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of SchemaRef. func (x SchemaRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + }, nil } return x.Value.MarshalYAML() } @@ -1174,6 +1317,9 @@ type SecuritySchemeRef struct { // are allowed by the openapi spec. Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Ref string Value *SecurityScheme @@ -1208,7 +1354,12 @@ func (x *SecuritySchemeRef) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of SecuritySchemeRef. func (x SecuritySchemeRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + Summary: x.Summary, + Description: x.Description, + }, nil } return x.Value.MarshalYAML() } @@ -1231,6 +1382,10 @@ func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") if len(extra) != 0 { x.extra = componentNames(extra) for k := range extra { @@ -1257,6 +1412,14 @@ func (x *SecuritySchemeRef) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl b/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl index fdff1c9..1b20b44 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl @@ -18,6 +18,12 @@ type {{ $type.Name }}Ref struct { Extensions map[string]any Origin *Origin `json:"-" yaml:"-"` + {{- if ne $type.Name "Schema" }} + // Reference Object summary and description are supported in OpenAPI 3.1+. + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + {{- end }} + Ref string Value *{{ $type.Name }} extra []string @@ -56,7 +62,14 @@ func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) { // MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref. func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { - return &Ref{Ref: ref, Extensions: x.Extensions}, nil + return &Ref{ + Ref: ref, + Extensions: x.Extensions, + {{- if ne $type.Name "Schema" }} + Summary: x.Summary, + Description: x.Description, + {{- end }} + }, nil } return x.Value.MarshalYAML() } @@ -79,6 +92,12 @@ func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error { delete(extra, "$ref") x.Ref = refOnly.Ref x.Origin = refOnly.Origin + {{- if ne $type.Name "Schema" }} + x.Summary = refOnly.Summary + x.Description = refOnly.Description + delete(extra, "summary") + delete(extra, "description") + {{- end }} if len(extra) != 0 { x.extra = componentNames(extra) {{- if eq $type.Name "Schema" }} @@ -123,6 +142,16 @@ func (x *{{ $type.Name }}Ref) validateExtras(ctx context.Context) error { if allowed == nil { allowed = make(map[string]struct{}) } + {{- if ne $type.Name "Schema" }} + if !validationOpts.isOpenAPI31OrLater { + if _, ok := allowed["description"]; x.Description != nil && !ok { + extras = append(extras, "description") + } + if _, ok := allowed["summary"]; x.Summary != nil && !ok { + extras = append(extras, "summary") + } + } + {{- end }} for _, ex := range x.extra { if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go index e8c52a1..55c9b97 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -135,9 +135,3 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation return validateExtensions(ctx, requestBody.Extensions, requestBody.Origin) } - -// UnmarshalJSON sets RequestBodies to a copy of data. -func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error) { - *requestBodies, err = unmarshalStringMapP[RequestBodyRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/vendor/github.com/getkin/kin-openapi/openapi3/response.go index ddc3a7b..2cbf833 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/response.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -14,6 +14,8 @@ type Responses struct { Origin *Origin `json:"-" yaml:"-"` m map[string]*ResponseRef + + explicitlyNull bool } // NewResponses builds a responses object with response objects in insertion order. @@ -26,6 +28,10 @@ func NewResponses(opts ...NewResponsesOption) *Responses { return responses } +func (responses *Responses) isExplicitlyNull() bool { + return responses != nil && responses.explicitlyNull && responses.m == nil && len(responses.Extensions) == 0 +} + // NewResponsesOption describes options to NewResponses func type NewResponsesOption func(*Responses) @@ -211,9 +217,3 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption return validateExtensions(ctx, response.Extensions, response.Origin) } - -// UnmarshalJSON sets ResponseBodies to a copy of data. -func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) { - *responseBodies, err = unmarshalStringMapP[ResponseRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go index 1f5eebd..0984f72 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -151,6 +151,17 @@ type Schema struct { DependentRequired map[string][]string `json:"dependentRequired,omitempty" yaml:"dependentRequired,omitempty"` // OpenAPI >=3.1 + // Always is set when the schema was written as a JSON Schema boolean + // schema rather than an object, and says what that schema always does: + // `true` accepts every instance, `false` accepts none. It is nil for an + // ordinary object schema, and Validate rejects one that carries any other + // keyword. + // + // Neither direction goes through a struct tag, hence the "-" on both: + // UnmarshalJSON takes the bare boolean before the field decode, and + // MarshalYAML emits it again ahead of the field map. OpenAPI >=3.1. + Always *bool `json:"-" yaml:"-"` + Defs Schemas `json:"$defs,omitempty" yaml:"$defs,omitempty"` // OpenAPI >=3.1 SchemaDialect string `json:"$schema,omitempty" yaml:"$schema,omitempty"` // OpenAPI >=3.1 Comment string `json:"$comment,omitempty" yaml:"$comment,omitempty"` // OpenAPI >=3.1 @@ -480,6 +491,10 @@ func (schema Schema) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Schema. func (schema Schema) MarshalYAML() (any, error) { + if x := schema.Always; x != nil { + return *x, nil + } + m := make(map[string]any, 61+len(schema.Extensions)) maps.Copy(m, schema.Extensions) @@ -684,8 +699,28 @@ func (schema Schema) MarshalYAML() (any, error) { return m, nil } +// unmarshalBooleanSchema reports whether data is a JSON Schema boolean schema, +// and its value if so. Only a bare `true` or `false` qualifies; an object, a +// string or anything else is an ordinary schema for the caller to decode. +func unmarshalBooleanSchema(data []byte) (bool, bool) { + var boolean bool + if err := json.Unmarshal(data, &boolean); err != nil { + return false, false + } + return boolean, true +} + // UnmarshalJSON sets Schema to a copy of data. func (schema *Schema) UnmarshalJSON(data []byte) error { + // JSON Schema 2020-12 allows a boolean wherever a schema is expected, so + // every SchemaRef position can hold one. SchemaRef.UnmarshalJSON funnels + // into here, which is why this one case covers them all. + if boolean, ok := unmarshalBooleanSchema(data); ok { + origin := schema.Origin + *schema = Schema{Always: &boolean, Origin: origin} + return nil + } + type SchemaBis Schema var x SchemaBis if err := json.Unmarshal(data, &x); err != nil { @@ -1379,6 +1414,14 @@ func (schema *Schema) IsEmpty() bool { if cs := schema.ContentSchema; cs != nil && cs.Value != nil && !cs.Value.IsEmpty() { return false } + // Last, so a schema carrying both a boolean and other keywords is judged by + // the keywords. Neither boolean is empty: `false` rejects every instance, + // and `true` is accepted by visitJSON explicitly rather than by skipping + // validation, which is what leaves `not: true` free to reject. + if schema.Always != nil { + return false + } + return true } @@ -1402,10 +1445,39 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, stack = append(stack, schema) + // A boolean schema carries no other keyword. One that does would marshal + // back as the bare boolean and silently drop the rest, so reject it rather + // than let a hand-built value lose content on the next serialization. + if schema.Always != nil { + // A boolean schema is JSON Schema 2020-12, so 3.0 has no such thing: + // there a schema MUST be a Schema Object. + if !validationOpts.isOpenAPI31OrLater { + return stack, newBooleanSchemaFor31Plus(schema.Origin) + } + rest := *schema + rest.Always = nil + if !rest.IsEmpty() || len(schema.Extensions) != 0 { + return stack, newSchemaBooleanFieldsExclusive(schema.Origin) + } + return stack, nil + } + if schema.ReadOnly && schema.WriteOnly { return stack, newSchemaReadOnlyWriteOnlyExclusive(schema.Origin) } + // The elements of `required` MUST be unique (JSON Schema 2020-12 §6.5.3 + // for OAS 3.1, draft-04 for OAS 3.0). + if len(schema.Required) > 1 { + seen := make(map[string]struct{}, len(schema.Required)) + for _, name := range schema.Required { + if _, dup := seen[name]; dup { + return stack, &DuplicateRequiredFieldError{Field: name, Origin: schema.Origin} + } + seen[name] = struct{}{} + } + } + // Reject fields that only exist in OAS 3.1 / JSON Schema 2020-12 when the // document is OAS 3.0. Fields explicitly allowed via AllowExtraSiblingFields // are skipped (opt-in escape hatch for 3.0 docs that reference external @@ -1554,7 +1626,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, var err error if stack, err = v.validate(ctx, stack); err != nil { - return stack, err + return stack, &SchemaCombinatorElementValidationError{Combinator: "oneOf", Cause: err} } } @@ -1566,7 +1638,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, var err error if stack, err = v.validate(ctx, stack); err != nil { - return stack, err + return stack, &SchemaCombinatorElementValidationError{Combinator: "anyOf", Cause: err} } } @@ -1578,7 +1650,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, var err error if stack, err = v.validate(ctx, stack); err != nil { - return stack, err + return stack, &SchemaCombinatorElementValidationError{Combinator: "allOf", Cause: err} } } @@ -1937,6 +2009,15 @@ func (schema *Schema) VisitJSON(value any, opts ...SchemaValidationOption) error } func (schema *Schema) visitJSON(settings *schemaValidationSettings, value any) (err error) { + if settings.visitedSchemas == nil { + settings.visitedSchemas = make(map[*Schema]struct{}) + } + if _, visited := settings.visitedSchemas[schema]; visited { + return nil + } + settings.visitedSchemas[schema] = struct{}{} + defer delete(settings.visitedSchemas, schema) + switch value := value.(type) { case nil: // Don't use VisitJSONNull, as we still want to reach 'visitXOFOperations', since @@ -1953,6 +2034,22 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value any) ( } } + if x := schema.Always; x != nil { + if *x { + return + } + if settings.failfast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "boolean", + Reason: "the false schema accepts no value", + customizeMessageError: settings.customizeMessageError, + } + } + if schema.IsEmpty() { switch value.(type) { case nil: @@ -2349,6 +2446,80 @@ func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value return } +// multipleOfSlack bounds, as a negative power of two, how far a value may sit +// from the nearest multiple and still count as one, measured relative to the +// value itself. A float64 carries about 2^-53 of relative error, so this is a +// couple of units in the last place: enough to absorb the error a caller's own +// arithmetic introduced, small enough that a genuine remainder never fits. +// +// The window must be measured against the value and not against the quotient. +// A quotient-relative window grows without bound, and once value/multipleOf +// reaches 2^49 it exceeds one half, at which point every number validates. +const multipleOfSlack = 51 + +// multipleOfFailure reports whether value satisfies multipleOf, and the reason +// when it does not. +// +// The comparison runs on exact rationals rather than on decimals formatted at a +// fixed precision. A fixed precision is only correct inside a narrow magnitude +// band: above it the formatting emits binary representation error and rejects +// valid values, and below it every operand formats to zero, which both accepts +// any value and divides by zero. +func multipleOfFailure(value, multipleOf float64) (string, bool) { + // A non-finite multipleOf cannot be reported: SchemaError renders the whole + // schema as JSON and NaN has no JSON encoding, so building that error would + // panic instead of describing the problem. Treat it as no constraint, which + // at least replaces the division-by-zero panic the fixed-precision + // formatting produced here. + if math.IsNaN(multipleOf) || math.IsInf(multipleOf, 0) { + return "", true + } + // JSON Schema requires multipleOf to be strictly greater than zero. Without + // this guard a zero or negative value either divided by zero or accepted + // every number. + if multipleOf <= 0 { + return fmt.Sprintf("multipleOf must be greater than zero, got %g", multipleOf), false + } + // VisitJSON rejects a non-finite number before any keyword runs, but the + // exported VisitJSONNumber does not, so one can still reach this point. + // Reporting it here is out of scope: SchemaError marshals Value, and NaN has + // no JSON encoding, so the error would panic when rendered. Preserve the + // existing outcome and leave that entry point to a separate change. + if math.IsNaN(value) || math.IsInf(value, 0) { + return "", true + } + notAMultiple := fmt.Sprintf("number must be a multiple of %g", multipleOf) + + exactValue := new(big.Rat).SetFloat64(value) + exactMultipleOf := new(big.Rat).SetFloat64(multipleOf) + quotient := new(big.Rat).Quo(exactValue, exactMultipleOf) + if quotient.IsInt() { + return "", true + } + + // The quotient is not an integer, so floor and floor+1 bracket it. Both are + // computed through big.Int, which no quotient can overflow. Compare the two + // candidate multiples against the value itself rather than comparing the + // quotient against an integer, so the window stays tied to the value's own + // precision at every magnitude. + floor := new(big.Int).Div(quotient.Num(), quotient.Denom()) + var distance *big.Rat + for _, k := range []*big.Int{floor, new(big.Int).Add(floor, big.NewInt(1))} { + gap := new(big.Rat).Sub(exactValue, new(big.Rat).Mul(new(big.Rat).SetInt(k), exactMultipleOf)) + gap.Abs(gap) + if distance == nil || gap.Cmp(distance) < 0 { + distance = gap + } + } + + slack := new(big.Rat).SetFrac(big.NewInt(1), new(big.Int).Lsh(big.NewInt(1), multipleOfSlack)) + tolerance := new(big.Rat).Mul(new(big.Rat).Abs(exactValue), slack) + if distance.Cmp(tolerance) <= 0 { + return "", true + } + return notAMultiple, false +} + func (schema *Schema) VisitJSONNumber(value float64) error { settings := newSchemaValidationSettings() return schema.visitJSONNumber(settings, value) @@ -2551,10 +2722,8 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value if v := schema.MultipleOf; v != nil { // "A numeric instance is valid only if division by this keyword's // value results in an integer." - numRat, denRat := &big.Rat{}, &big.Rat{} - numRat.SetString(fmt.Sprintf("%.10f", value)) - denRat.SetString(fmt.Sprintf("%.10f", *v)) - if !(&big.Rat{}).Quo(numRat, denRat).IsInt() { + reason, ok := multipleOfFailure(value, *v) + if !ok { if settings.failfast { return errSchema } @@ -2562,7 +2731,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "multipleOf", - Reason: fmt.Sprintf("number must be a multiple of %g", *v), + Reason: reason, customizeMessageError: settings.customizeMessageError, } if !settings.multiError { @@ -2651,7 +2820,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value me = append(me, err) } } - if !cp.MatchString(value) { + if cp != nil && !cp.MatchString(value) { err := &SchemaError{ Value: value, Schema: schema, @@ -2783,14 +2952,38 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [ me = append(me, err) } - // "items" + // "prefixItems": the schema at index i validates the item at index i. + // Positions the array does not reach are simply unconstrained. + for i, prefixItemRef := range schema.PrefixItems { + if i >= len(value) { + break + } + prefixItemSchema := prefixItemRef.Value + if prefixItemSchema == nil { + return newUnresolvedRef(prefixItemRef.Ref, prefixItemRef.Origin) + } + if err := prefixItemSchema.visitJSON(settings, value[i]); err != nil { + err = markSchemaErrorIndex(err, i) + if !settings.multiError { + return err + } + if itemMe, ok := err.(MultiError); ok { + me = append(me, itemMe...) + } else { + me = append(me, err) + } + } + } + + // "items" applies to the positions prefixItems does not cover. Without + // prefixItems that is every position, which is the OAS 3.0 behaviour. if itemSchemaRef := schema.Items; itemSchemaRef != nil { itemSchema := itemSchemaRef.Value if itemSchema == nil { return newUnresolvedRef(itemSchemaRef.Ref, itemSchemaRef.Origin) } - for i, item := range value { - if err := itemSchema.visitJSON(settings, item); err != nil { + for i := len(schema.PrefixItems); i < len(value); i++ { + if err := itemSchema.visitJSON(settings, value[i]); err != nil { err = markSchemaErrorIndex(err, i) if !settings.multiError { return err @@ -3165,18 +3358,12 @@ func deepCopyJSONValue(v any) any { } return cp case []any: - cp := make([]any, len(v)) - for i, val := range v { - cp[i] = deepCopyJSONValue(val) + cp := make([]any, 0, len(v)) + for _, val := range v { + cp = append(cp, deepCopyJSONValue(val)) } return cp default: return v // string, float64, bool, nil — all immutable } } - -// UnmarshalJSON sets Schemas to a copy of data. -func (schemas *Schemas) UnmarshalJSON(data []byte) (err error) { - *schemas, err = unmarshalStringMapP[SchemaRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go index 5053510..dd8d218 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -34,6 +34,11 @@ const ( // FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122 FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$` + // FormatOfStringForUUIDOfRFC9562 is an optional predefined format for UUID v1-v8 as specified by RFC9562, + // which obsoletes RFC4122. In addition to the versioned layout it also accepts the special Nil UUID + // (all zeroes) and Max UUID (all ones) defined in sections 5.9 and 5.10 of the RFC. + FormatOfStringForUUIDOfRFC9562 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|[fF]{8}-[fF]{4}-[fF]{4}-[fF]{4}-[fF]{12})$` + // FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses. // Use DefineStringFormat(...) if you need something stricter. FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_jsonschema_validator.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_jsonschema_validator.go index d610e53..7d6a761 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_jsonschema_validator.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_jsonschema_validator.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "strings" "github.com/santhosh-tekuri/jsonschema/v6" @@ -16,7 +17,7 @@ type jsonSchemaValidator struct { } // newJSONSchemaValidator creates a new validator using JSON Schema 2020-12 -func newJSONSchemaValidator(schema *Schema) (*jsonSchemaValidator, error) { +func newJSONSchemaValidator(schema *Schema, settings *schemaValidationSettings) (*jsonSchemaValidator, error) { // Convert OpenAPI Schema to JSON Schema format schemaBytes, err := json.Marshal(schema) if err != nil { @@ -35,6 +36,9 @@ func newJSONSchemaValidator(schema *Schema) (*jsonSchemaValidator, error) { compiler := jsonschema.NewCompiler() compiler.DefaultDraft(jsonschema.Draft2020) + // Keep enforcing the formats the built-in validator enforces + registerFormatValidators(compiler, schemaMap, settings) + // Add the schema schemaURL := "https://example.com/schema.json" if err := compiler.AddResource(schemaURL, schemaMap); err != nil { @@ -53,6 +57,88 @@ func newJSONSchemaValidator(schema *Schema) (*jsonSchemaValidator, error) { }, nil } +func registerFormatValidators(compiler *jsonschema.Compiler, schemaMap map[string]any, settings *schemaValidationSettings) { + formats := make(map[string]struct{}) + collectFormats(schemaMap, formats) + if len(formats) == 0 { + return + } + + for format := range formats { + compiler.RegisterFormat(&jsonschema.Format{ + Name: format, + Validate: formatValidator(format, settings), + }) + } + compiler.AssertFormat() // has to be explicitly asserted +} + +func collectFormats(node any, formats map[string]struct{}) { + switch node := node.(type) { + case map[string]any: + if format, ok := node["format"].(string); ok && format != "" { + formats[format] = struct{}{} + } + for _, value := range node { + collectFormats(value, formats) + } + case []any: + for _, value := range node { + collectFormats(value, formats) + } + } +} + +func formatValidator(format string, settings *schemaValidationSettings) func(any) error { + return func(value any) error { + switch value := value.(type) { + case string: + f, ok := settings.stringFormats[format] + if !ok { + if f, ok = SchemaStringFormats[format]; !ok { + return nil + } + } + return f.Validate(value) + case json.Number: + if number, err := value.Float64(); err == nil { + return validateNumberFormat(format, settings, number) + } + case float64: + return validateNumberFormat(format, settings, value) + case float32: + return validateNumberFormat(format, settings, float64(value)) + case int: + return validateNumberFormat(format, settings, float64(value)) + case int32: + return validateNumberFormat(format, settings, float64(value)) + case int64: + return validateNumberFormat(format, settings, float64(value)) + } + return nil + } +} + +func validateNumberFormat(format string, settings *schemaValidationSettings, value float64) error { + if value == math.Trunc(value) && !math.IsInf(value, 0) { + f, ok := settings.integerFormats[format] + if !ok { + f, ok = SchemaIntegerFormats[format] + } + if ok { + return f.Validate(int64(value)) + } + } + + f, ok := settings.numberFormats[format] + if !ok { + if f, ok = SchemaNumberFormats[format]; !ok { + return nil + } + } + return f.Validate(value) +} + // transformOpenAPIToJSONSchema converts OpenAPI 3.0/3.1 specific keywords to JSON Schema format func transformOpenAPIToJSONSchema(schema map[string]any) { // Handle nullable - in OpenAPI 3.0, nullable is a boolean flag @@ -202,7 +288,7 @@ func formatValidationError(verr *jsonschema.ValidationError, parentPath string) // useJSONSchema2020 validates using the JSON Schema 2020-12 validator func (schema *Schema) useJSONSchema2020(settings *schemaValidationSettings, value any) error { - validator, err := newJSONSchemaValidator(schema) + validator, err := newJSONSchemaValidator(schema, settings) if err != nil { // Fall back to built-in validator if compilation fails return schema.visitJSON(settings, value) diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go index 0be8895..38cedb9 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go @@ -27,6 +27,8 @@ func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err Origin: err, Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), } + // A failed compile can yield a typed nil, which no call site's nil check catches. + cp = nil err = newSchemaPatternRegexError(pattern, schemaErr, schema.Origin) return } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go index 205d95d..61d3dfa 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go @@ -34,6 +34,10 @@ type schemaValidationSettings struct { stringFormats map[string]StringFormatValidator numberFormats map[string]NumberFormatValidator integerFormats map[string]IntegerFormatValidator + + // visitedSchemas provides pointer-identity cycle detection for runtime + // validation, matching the stack-based detection in Schema.validate(). + visitedSchemas map[*Schema]struct{} } // FailFast returns schema validation errors quicker. diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go index 36b6401..3f5bd95 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -49,9 +49,3 @@ func (security *SecurityRequirement) Validate(ctx context.Context, opts ...Valid return nil } - -// UnmarshalJSON sets SecurityRequirement to a copy of data. -func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) { - *security, err = unmarshalStringMap[[]string](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go index efe7f20..0905b61 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -325,7 +325,7 @@ type OAuthFlow struct { AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` - Scopes StringMap[string] `json:"scopes" yaml:"scopes"` // required + Scopes map[string]string `json:"scopes" yaml:"scopes"` // required } // MarshalJSON returns the JSON encoding of OAuthFlow. @@ -426,9 +426,3 @@ func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ... return flow.Validate(ctx, opts...) } - -// UnmarshalJSON sets SecuritySchemes to a copy of data. -func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error) { - *securitySchemes, err = unmarshalStringMapP[SecuritySchemeRef](data) - return -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/vendor/github.com/getkin/kin-openapi/openapi3/server.go index 181eace..ca460d7 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/server.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -119,7 +119,6 @@ func (server *Server) UnmarshalJSON(data []byte) error { if len(x.Extensions) == 0 { x.Extensions = nil } - delete(x.Variables, originKey) *server = Server(x) return nil } @@ -244,12 +243,6 @@ func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) er // ServerVariables is a map of ServerVariable objects keyed by variable name. type ServerVariables map[string]*ServerVariable -// UnmarshalJSON sets ServerVariables to a copy of data. -func (serverVariables *ServerVariables) UnmarshalJSON(data []byte) (err error) { - *serverVariables, err = unmarshalStringMapP[ServerVariable](data) - return -} - type ServerVariable struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go b/vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go deleted file mode 100644 index f416899..0000000 --- a/vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go +++ /dev/null @@ -1,64 +0,0 @@ -package openapi3 - -import "encoding/json" - -// StringMap is a map[string]string that ignores the origin in the underlying json representation. -type StringMap[V any] map[string]V - -// UnmarshalJSON sets StringMap to a copy of data. -func (stringMap *StringMap[V]) UnmarshalJSON(data []byte) (err error) { - *stringMap, err = unmarshalStringMap[V](data) - return -} - -// unmarshalStringMapP unmarshals given json into a map[string]*V -func unmarshalStringMapP[V any](data []byte) (map[string]*V, error) { - var m map[string]any - if err := json.Unmarshal(data, &m); err != nil { - return nil, err - } - - result := make(map[string]*V, len(m)) - for _, k := range componentNames(m) { - value, err := deepCast[V](m[k]) - if err != nil { - return nil, err - } - result[k] = value - } - - return result, nil -} - -// unmarshalStringMap unmarshals given json into a map[string]V -func unmarshalStringMap[V any](data []byte) (map[string]V, error) { - var m map[string]any - if err := json.Unmarshal(data, &m); err != nil { - return nil, err - } - - result := make(map[string]V, len(m)) - for _, k := range componentNames(m) { - value, err := deepCast[V](m[k]) - if err != nil { - return nil, err - } - result[k] = *value - } - - return result, nil -} - -// deepCast casts any value to a value of type V. -func deepCast[V any](value any) (*V, error) { - data, err := json.Marshal(value) - if err != nil { - return nil, err - } - - var result V - if err = json.Unmarshal(data, &result); err != nil { - return nil, err - } - return &result, nil -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go index c022a25..55b302f 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go @@ -23,8 +23,21 @@ func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) me := newErrCollector(ctx) + // Each tag name in the list MUST be unique (OpenAPI Object). Empty names + // are left to per-tag validation, not flagged as duplicates here. + seen := make(map[string]struct{}, len(tags)) + for _, v := range tags { - if err := me.emit(v.Validate(ctx)); err != nil { + if v.Name != "" { + if _, dup := seen[v.Name]; dup { + if err := me.emit(&DuplicateTagError{Name: v.Name, Origin: v.Origin}); err != nil { + return err + } + } + seen[v.Name] = struct{}{} + } + wrap := func(e error) error { return &TagValidationError{Name: v.Name, Cause: e} } + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/validation_code.go b/vendor/github.com/getkin/kin-openapi/openapi3/validation_code.go new file mode 100644 index 0000000..96a3a89 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/validation_code.go @@ -0,0 +1,278 @@ +package openapi3 + +import "slices" + +// CodedError is implemented by every typed validation error. Code returns a +// stable, kebab-case identifier for the validation rule that failed (e.g. +// "operation-responses-required"), so consumers can suppress specific +// findings, assign per-rule severities, emit machine-readable diagnostics +// (e.g. the LSP Diagnostic.code field), or link to per-rule documentation +// without depending on message text. +// +// Every code is declared as a literal on its error type; nothing is derived. +// Codes are a contract: message wording and Go type names may change freely, +// codes may not. Renaming or removing a code is a breaking change and is +// recorded in the README changelog. The full set is available from +// ValidationErrorCodes. +// +// Like net.Error, this interface exists as an assertion target for consumers; +// the package itself constructs concrete error types and does not consume it. +// Reach the code of a wrapped error with errors.As: +// +// var coded openapi3.CodedError +// if errors.As(err, &coded) { +// fmt.Println(coded.Code()) +// } +type CodedError interface { + error + Code() string +} + +func (e *AdditionalOperationsDuplicateMethodError) Code() string { + return "additional-operations-duplicate-method" +} +func (e *AdditionalOperationsFieldFor32Plus) Code() string { + return "additional-operations-field-for-3-2-plus" +} +func (e *AdditionalOperationsInvalidMethodError) Code() string { + return "additional-operations-invalid-method" +} +func (e *AnchorFieldFor31Plus) Code() string { return "anchor-field-for-3-1-plus" } +func (e *APIKeyInInvalidError) Code() string { return "security-scheme-apikey-in-invalid" } +func (e *APIKeySecuritySchemeNameRequired) Code() string { return "security-scheme-name-required" } +func (e *CommentFieldFor31Plus) Code() string { return "comment-field-for-3-1-plus" } +func (e *ConflictingPathsError) Code() string { return "conflicting-paths" } +func (e *BooleanSchemaFor31Plus) Code() string { return "boolean-schema-for-3-1-plus" } +func (e *ConstFieldFor31Plus) Code() string { return "const-field-for-3-1-plus" } +func (e *ContainsFieldFor31Plus) Code() string { return "contains-field-for-3-1-plus" } +func (e *ContentEncodingFieldFor31Plus) Code() string { return "content-encoding-field-for-3-1-plus" } +func (e *ContentMediaTypeFieldFor31Plus) Code() string { + return "content-media-type-field-for-3-1-plus" +} +func (e *ContentSchemaFieldFor31Plus) Code() string { return "content-schema-field-for-3-1-plus" } +func (e *DefaultViolatesSchema) Code() string { return "default-violates-schema" } +func (e *DefsFieldFor31Plus) Code() string { return "defs-field-for-3-1-plus" } +func (e *DependentRequiredFieldFor31Plus) Code() string { + return "dependent-required-field-for-3-1-plus" +} +func (e *DependentSchemasFieldFor31Plus) Code() string { return "dependent-schemas-field-for-3-1-plus" } +func (e *DuplicateOperationIDError) Code() string { return "duplicate-operation-id" } +func (e *DuplicateParameterError) Code() string { return "duplicate-parameter" } +func (e *DuplicateRequiredFieldError) Code() string { return "duplicate-required-field" } +func (e *DuplicateTagError) Code() string { return "duplicate-tag" } +func (e *DynamicAnchorFieldFor31Plus) Code() string { return "dynamic-anchor-field-for-3-1-plus" } +func (e *DynamicRefFieldFor31Plus) Code() string { return "dynamic-ref-field-for-3-1-plus" } +func (e *ElseFieldFor31Plus) Code() string { return "else-field-for-3-1-plus" } +func (e *ExamplesFieldFor31Plus) Code() string { return "examples-field-for-3-1-plus" } +func (e *ExampleValueExternalValueExclusive) Code() string { + return "value-external-value-mutually-exclusive" +} +func (e *ExampleValueOrExternalValueRequired) Code() string { + return "value-or-external-value-required" +} +func (e *ExampleViolatesSchema) Code() string { return "example-violates-schema" } +func (e *ExternalDocsURLRequired) Code() string { return "external-docs-url-required" } +func (e *ExtraSiblingFieldsError) Code() string { return "extra-sibling-fields" } +func (e *HeaderContentSchemaExactlyOne) Code() string { return "content-or-schema-exactly-one" } +func (e *HeaderContentSingleEntry) Code() string { return "header-content-single-entry" } +func (e *HeaderInForbidden) Code() string { return "in-forbidden" } +func (e *HeaderNameForbidden) Code() string { return "name-forbidden" } +func (e *IDFieldFor31Plus) Code() string { return "id-field-for-3-1-plus" } +func (e *IfFieldFor31Plus) Code() string { return "if-field-for-3-1-plus" } +func (e *InfoRequired) Code() string { return "info-required" } +func (e *InfoSummaryFieldFor31Plus) Code() string { return "summary-field-for-3-1-plus" } +func (e *InfoTitleRequired) Code() string { return "info-title-required" } +func (e *InfoVersionRequired) Code() string { return "info-version-required" } +func (e *InvalidHTTPSchemeError) Code() string { return "security-scheme-http-scheme-invalid" } +func (e *InvalidParameterInError) Code() string { return "parameter-in-invalid" } +func (e *InvalidSecuritySchemeTypeError) Code() string { return "security-scheme-type-invalid" } +func (e *InvalidSerializationMethodError) Code() string { return "serialization-method-invalid" } +func (e *ItemSchemaFieldFor32Plus) Code() string { return "item-schema-field-for-3-2-plus" } +func (e *JSONSchemaDialectAbsoluteURIRequired) Code() string { return "json-schema-dialect-required" } +func (e *JSONSchemaDialectFieldFor31Plus) Code() string { + return "jsonschemadialect-field-for-3-1-plus" +} +func (e *LicenseIdentifierFieldFor31Plus) Code() string { return "identifier-field-for-3-1-plus" } +func (e *LicenseNameRequired) Code() string { return "license-name-required" } +func (e *LicenseURLIdentifierExclusive) Code() string { return "url-identifier-mutually-exclusive" } +func (e *LinkOperationIDOrRefRequired) Code() string { return "operation-id-or-operation-ref-required" } +func (e *LinkOperationIDRefExclusive) Code() string { + return "operation-id-operation-ref-mutually-exclusive" +} +func (e *MaxContainsFieldFor31Plus) Code() string { return "max-contains-field-for-3-1-plus" } +func (e *MediaTypeExampleExamplesExclusive) Code() string { + return "example-examples-mutually-exclusive" +} +func (e *MinContainsFieldFor31Plus) Code() string { return "min-contains-field-for-3-1-plus" } +func (e *OAuthFlowAuthorizationURLForbidden) Code() string { return "authorization-url-forbidden" } +func (e *OAuthFlowAuthorizationURLRequired) Code() string { + return "oauth-flow-authorization-url-required" +} +func (e *OAuthFlowScopesRequired) Code() string { return "oauth-flow-scopes-required" } +func (e *OAuthFlowTokenURLForbidden) Code() string { return "token-url-forbidden" } +func (e *OAuthFlowTokenURLRequired) Code() string { return "oauth-flow-token-url-required" } +func (e *OpenAPIVersionRequired) Code() string { return "openapi-required" } +func (e *OpenIDConnectURLRequired) Code() string { return "openid-connect-url-required" } +func (e *OperationResponsesRequired) Code() string { return "operation-responses-required" } +func (e *ParameterContentSchemaExactlyOne) Code() string { return "content-or-schema-exactly-one" } +func (e *ParameterContentSingleEntry) Code() string { return "parameter-content-single-entry" } +func (e *ParameterExampleAndExamplesExclusive) Code() string { + return "example-examples-mutually-exclusive" +} +func (e *ParameterNameRequired) Code() string { return "parameter-name-required" } +func (e *PathMustStartWithSlashError) Code() string { return "path-must-start-with-slash" } +func (e *PathParameterRequiredError) Code() string { return "path-parameter-required" } +func (e *PathParametersError) Code() string { return "path-parameters-mismatch" } +func (e *PathsRequired) Code() string { return "paths-required" } +func (e *PatternPropertiesFieldFor31Plus) Code() string { + return "pattern-properties-field-for-3-1-plus" +} +func (e *PrefixItemsFieldFor31Plus) Code() string { return "prefix-items-field-for-3-1-plus" } +func (e *PropertyNamesFieldFor31Plus) Code() string { return "property-names-field-for-3-1-plus" } +func (e *QueryFieldFor32Plus) Code() string { return "query-field-for-3-2-plus" } +func (e *RequestBodyContentRequired) Code() string { return "request-body-content-required" } +func (e *ResponseDescriptionRequired) Code() string { return "response-description-required" } +func (e *ResponsesNonEmptyRequired) Code() string { return "responses-required" } +func (e *SchemaAdditionalPropertiesBothForms) Code() string { + return "additional-properties-both-forms-exclusive" +} +func (e *SchemaFieldFor31Plus) Code() string { return "schema-field-for-3-1-plus" } +func (e *SchemaItemsRequired) Code() string { return "schema-items-required" } +func (e *SchemaPatternRegexError) Code() string { return "schema-pattern-regex-invalid" } +func (e *SchemaReadOnlyWriteOnlyExclusive) Code() string { + return "read-only-write-only-mutually-exclusive" +} +func (e *SchemaBooleanFieldsExclusive) Code() string { + return "boolean-schema-with-other-keywords" +} +func (e *SchemaTypeError) Code() string { return "schema-type-unsupported" } +func (e *SchemaUnevaluatedItemsBothForms) Code() string { + return "unevaluated-items-both-forms-exclusive" +} +func (e *SchemaUnevaluatedPropertiesBothForms) Code() string { + return "unevaluated-properties-both-forms-exclusive" +} +func (e *SecuritySchemeBearerFormatForbidden) Code() string { return "bearer-format-forbidden" } +func (e *SecuritySchemeFlowsForbidden) Code() string { return "flows-forbidden" } +func (e *SecuritySchemeFlowsRequired) Code() string { return "flows-required" } +func (e *SecuritySchemeInForbidden) Code() string { return "in-forbidden" } +func (e *SecuritySchemeNameForbidden) Code() string { return "name-forbidden" } +func (e *ServerURLRequired) Code() string { return "server-url-required" } +func (e *ServerURLTemplateError) Code() string { return "server-url-template-invalid" } +func (e *ServerVariableDefaultRequired) Code() string { return "default-required" } +func (e *ThenFieldFor31Plus) Code() string { return "then-field-for-3-1-plus" } +func (e *UnevaluatedItemsFieldFor31Plus) Code() string { return "unevaluated-items-field-for-3-1-plus" } +func (e *UnevaluatedPropertiesFieldFor31Plus) Code() string { + return "unevaluated-properties-field-for-3-1-plus" +} +func (e *UnresolvedRefError) Code() string { return "unresolved-ref" } +func (e *WebhookNilError) Code() string { return "webhook-nil" } +func (e *WebhooksFieldFor31Plus) Code() string { return "webhooks-field-for-3-1-plus" } + +// ValidationErrorCodes returns every code a validation error can carry, +// sorted. TestValidationErrorCodes keeps it in sync with the errors the +// validators emit. +func ValidationErrorCodes() []string { + return slices.Clone(validationErrorCodes) +} + +var validationErrorCodes = []string{ + "additional-operations-duplicate-method", + "additional-operations-field-for-3-2-plus", + "additional-operations-invalid-method", + "additional-properties-both-forms-exclusive", + "anchor-field-for-3-1-plus", + "authorization-url-forbidden", + "bearer-format-forbidden", + "boolean-schema-for-3-1-plus", + "boolean-schema-with-other-keywords", + "comment-field-for-3-1-plus", + "conflicting-paths", + "const-field-for-3-1-plus", + "contains-field-for-3-1-plus", + "content-encoding-field-for-3-1-plus", + "content-media-type-field-for-3-1-plus", + "content-or-schema-exactly-one", + "content-schema-field-for-3-1-plus", + "default-required", + "default-violates-schema", + "defs-field-for-3-1-plus", + "dependent-required-field-for-3-1-plus", + "dependent-schemas-field-for-3-1-plus", + "duplicate-operation-id", + "duplicate-parameter", + "duplicate-required-field", + "duplicate-tag", + "dynamic-anchor-field-for-3-1-plus", + "dynamic-ref-field-for-3-1-plus", + "else-field-for-3-1-plus", + "example-examples-mutually-exclusive", + "example-violates-schema", + "examples-field-for-3-1-plus", + "external-docs-url-required", + "extra-sibling-fields", + "flows-forbidden", + "flows-required", + "header-content-single-entry", + "id-field-for-3-1-plus", + "identifier-field-for-3-1-plus", + "if-field-for-3-1-plus", + "in-forbidden", + "info-required", + "info-title-required", + "info-version-required", + "item-schema-field-for-3-2-plus", + "json-schema-dialect-required", + "jsonschemadialect-field-for-3-1-plus", + "license-name-required", + "max-contains-field-for-3-1-plus", + "min-contains-field-for-3-1-plus", + "name-forbidden", + "oauth-flow-authorization-url-required", + "oauth-flow-scopes-required", + "oauth-flow-token-url-required", + "openapi-required", + "openid-connect-url-required", + "operation-id-operation-ref-mutually-exclusive", + "operation-id-or-operation-ref-required", + "operation-responses-required", + "parameter-content-single-entry", + "parameter-in-invalid", + "parameter-name-required", + "path-must-start-with-slash", + "path-parameter-required", + "path-parameters-mismatch", + "paths-required", + "pattern-properties-field-for-3-1-plus", + "prefix-items-field-for-3-1-plus", + "property-names-field-for-3-1-plus", + "query-field-for-3-2-plus", + "read-only-write-only-mutually-exclusive", + "request-body-content-required", + "response-description-required", + "responses-required", + "schema-field-for-3-1-plus", + "schema-items-required", + "schema-pattern-regex-invalid", + "schema-type-unsupported", + "security-scheme-apikey-in-invalid", + "security-scheme-http-scheme-invalid", + "security-scheme-name-required", + "security-scheme-type-invalid", + "serialization-method-invalid", + "server-url-required", + "server-url-template-invalid", + "summary-field-for-3-1-plus", + "then-field-for-3-1-plus", + "token-url-forbidden", + "unevaluated-items-both-forms-exclusive", + "unevaluated-items-field-for-3-1-plus", + "unevaluated-properties-both-forms-exclusive", + "unevaluated-properties-field-for-3-1-plus", + "unresolved-ref", + "url-identifier-mutually-exclusive", + "value-external-value-mutually-exclusive", + "value-or-external-value-required", + "webhook-nil", + "webhooks-field-for-3-1-plus", +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/validation_error.go b/vendor/github.com/getkin/kin-openapi/openapi3/validation_error.go index 8929a1f..c8de44f 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/validation_error.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/validation_error.go @@ -535,6 +535,65 @@ func (e *DuplicateParameterError) Error() string { return fmt.Sprintf("more than one %q parameter has name %q", e.In, e.Name) } +// AdditionalOperationsDuplicateMethodError clusters "additionalOperations +// key duplicates a fixed field" failures. OpenAPI 3.2's +// `additionalOperations` map may only carry methods that have no dedicated +// Path Item Object field. +type AdditionalOperationsDuplicateMethodError struct { + // Method is the offending additionalOperations key. + Method string + // Origin is the source location of the offending path item when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *AdditionalOperationsDuplicateMethodError) Error() string { + return fmt.Sprintf("additionalOperations key %q duplicates a fixed path item field", e.Method) +} + +// AdditionalOperationsInvalidMethodError clusters "additionalOperations key +// is not an HTTP method" failures. Keys must be RFC 9110 tokens. +type AdditionalOperationsInvalidMethodError struct { + // Method is the offending additionalOperations key. + Method string + // Origin is the source location of the offending path item when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *AdditionalOperationsInvalidMethodError) Error() string { + return fmt.Sprintf("additionalOperations key %q is not a valid HTTP method token", e.Method) +} + +// DuplicateRequiredFieldError clusters "duplicate field in required" failures. +// The elements of a schema's `required` array MUST be unique (JSON Schema +// 2020-12 §6.5.3 for OpenAPI 3.1, draft-04 for OpenAPI 3.0). +type DuplicateRequiredFieldError struct { + // Field is the field name listed more than once in `required`. + Field string + // Origin is the source location of the offending schema when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *DuplicateRequiredFieldError) Error() string { + return fmt.Sprintf("duplicate field %q in required", e.Field) +} + +// DuplicateTagError clusters "more than one tag has name X" failures. Each tag +// name in the document-root `tags` list MUST be unique (OpenAPI Object). +type DuplicateTagError struct { + // Name is the tag name that appears more than once. + Name string + // Origin is the source location of the offending tag when the document + // was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *DuplicateTagError) Error() string { + return fmt.Sprintf("more than one tag has name %q", e.Name) +} + // InvalidSerializationMethodError clusters "serialization method with // style=X and explode=Y is not supported by Z" failures. Fires for // invalid (style, explode) combinations on encodings, parameters, @@ -855,6 +914,12 @@ func (e *SchemaReadOnlyWriteOnlyExclusive) As(target any) bool { return asValidationError(target, &e.ValidationError) } +type SchemaBooleanFieldsExclusive struct{ ValidationError } + +func (e *SchemaBooleanFieldsExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + // ForbiddenFieldError leaves. type HeaderNameForbidden struct{ ValidationError } @@ -916,6 +981,12 @@ func (e *ConstFieldFor31Plus) As(target any) bool { return asValidationError(target, &e.ValidationError) } +type BooleanSchemaFor31Plus struct{ ValidationError } + +func (e *BooleanSchemaFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + type ExamplesFieldFor31Plus struct{ ValidationError } func (e *ExamplesFieldFor31Plus) As(target any) bool { @@ -1300,6 +1371,12 @@ func newSchemaReadOnlyWriteOnlyExclusive(origin *Origin) error { &SchemaReadOnlyWriteOnlyExclusive{ValidationError{Message: msg}}, origin) } +func newSchemaBooleanFieldsExclusive(origin *Origin) error { + const msg = "a boolean schema carries no other keyword, and the others would be lost on marshal" + return newMutuallyExclusiveFields("boolean schema", "schema keywords", + &SchemaBooleanFieldsExclusive{ValidationError{Message: msg}}, origin) +} + // newForbiddenField wraps leaf in a *ForbiddenFieldError carrying the // name of the field that the spec forbids in the current context. func newForbiddenField(field string, leaf error, origin *Origin) error { @@ -1347,12 +1424,43 @@ func newAPIKeySecuritySchemeNameRequired(origin *Origin) error { &APIKeySecuritySchemeNameRequired{ValidationError{Message: msg}}, origin) } +// ExampleViolatesSchema and DefaultViolatesSchema mark which schema value kind +// failed. They embed SchemaValueError, and their As exposes it, so errors.As +// with a *SchemaValueError target keeps matching. +type ExampleViolatesSchema struct{ SchemaValueError } + +func (e *ExampleViolatesSchema) As(target any) bool { + return asSchemaValueError(target, &e.SchemaValueError) +} + +type DefaultViolatesSchema struct{ SchemaValueError } + +func (e *DefaultViolatesSchema) As(target any) bool { + return asSchemaValueError(target, &e.SchemaValueError) +} + +func asSchemaValueError(target any, sve *SchemaValueError) bool { + t, ok := target.(**SchemaValueError) + if !ok { + return false + } + *t = sve + return true +} + // newSchemaValueError wraps the result of schema.VisitJSON in a // *SchemaValueError cluster, identifying which schema sub-field // (example, default, ...) carried the offending value. cause is // either a *SchemaError or a MultiError of them. func newSchemaValueError(valueKind string, cause error, origin *Origin) error { - return &SchemaValueError{ValueKind: valueKind, Cause: cause, Origin: origin} + sve := SchemaValueError{ValueKind: valueKind, Cause: cause, Origin: origin} + switch valueKind { + case "example": + return &ExampleViolatesSchema{sve} + case "default": + return &DefaultViolatesSchema{sve} + } + return &sve } // exampleValueOrigin returns an Origin pinned to the example's `value:` @@ -1364,20 +1472,19 @@ func exampleValueOrigin(ex *Example, fallback *Origin) *Origin { if ex == nil || ex.Origin == nil { return fallback } - if loc, ok := ex.Origin.Fields["value"]; ok { + if loc, ok := ex.Origin.Fields.Lookup("value"); ok { return &Origin{Key: &loc} } return ex.Origin } // newFieldVersionMismatch wraps leaf in a FieldVersionMismatchError for the -// given field at minimum version 3.1. Used by per-call-site constructors -// (newInfoSummaryFieldFor31Plus, etc.) and by the dispatch helper -// newFieldFor31Plus that schema.go's reject closure goes through. -func newFieldVersionMismatch(field string, leaf error, origin *Origin) error { +// given field at minimum version. Used by per-call-site constructors +// (newInfoSummaryFieldFor31Plus, etc.) and by dispatch helpers. +func newFieldVersionMismatch(field, minVersion string, leaf error, origin *Origin) error { return &FieldVersionMismatchError{ Field: field, - MinVersion: "3.1", + MinVersion: minVersion, Cause: leaf, Origin: origin, } @@ -1391,25 +1498,31 @@ func newFieldVersionMismatch(field string, leaf error, origin *Origin) error { func newInfoSummaryFieldFor31Plus(origin *Origin) error { const msg = "field summary is for OpenAPI >=3.1" return newFieldVersionMismatch("summary", - &InfoSummaryFieldFor31Plus{ValidationError{Message: msg}}, origin) + "3.1", &InfoSummaryFieldFor31Plus{ValidationError{Message: msg}}, origin) } func newLicenseIdentifierFieldFor31Plus(origin *Origin) error { const msg = "field identifier is for OpenAPI >=3.1" return newFieldVersionMismatch("identifier", - &LicenseIdentifierFieldFor31Plus{ValidationError{Message: msg}}, origin) + "3.1", &LicenseIdentifierFieldFor31Plus{ValidationError{Message: msg}}, origin) +} + +func newBooleanSchemaFor31Plus(origin *Origin) error { + const msg = "a boolean schema is for OpenAPI >=3.1; before that a schema MUST be a Schema Object" + return newFieldVersionMismatch("boolean schema", + "3.1", &BooleanSchemaFor31Plus{ValidationError{Message: msg}}, origin) } func newWebhooksFieldFor31Plus(origin *Origin) error { const msg = "field webhooks is for OpenAPI >=3.1" return newFieldVersionMismatch("webhooks", - &WebhooksFieldFor31Plus{ValidationError{Message: msg}}, origin) + "3.1", &WebhooksFieldFor31Plus{ValidationError{Message: msg}}, origin) } func newJSONSchemaDialectFieldFor31Plus(origin *Origin) error { const msg = "field jsonschemadialect is for OpenAPI >=3.1" return newFieldVersionMismatch("jsonschemadialect", - &JSONSchemaDialectFieldFor31Plus{ValidationError{Message: msg}}, origin) + "3.1", &JSONSchemaDialectFieldFor31Plus{ValidationError{Message: msg}}, origin) } // fieldFor31PlusLeaves maps field names (as passed to errFieldFor31Plus) @@ -1447,7 +1560,7 @@ var fieldFor31PlusLeaves = map[string]func(msg string) error{ "$dynamicRef": func(m string) error { return &DynamicRefFieldFor31Plus{ValidationError{Message: m}} }, } -// newFieldFor31Plus dispatches errFieldFor31Plus's per-field message +// errFieldFor31Plus dispatches errFieldFor31Plus's per-field message // to the right typed leaf and wraps it in a FieldVersionMismatchError. // Fields not in fieldFor31PlusLeaves fall back to a bare // *ValidationError so the caller still gets a stable Message and the @@ -1455,7 +1568,7 @@ var fieldFor31PlusLeaves = map[string]func(msg string) error{ // // Reached only from schema.go's reject closure with a runtime field // name; the four non-schema sites use direct constructors instead. -func newFieldFor31Plus(field string, origin *Origin) error { +func errFieldFor31Plus(field string, origin *Origin) error { msg := "field " + field + " is for OpenAPI >=3.1" var leaf error if ctor, ok := fieldFor31PlusLeaves[field]; ok { @@ -1463,7 +1576,52 @@ func newFieldFor31Plus(field string, origin *Origin) error { } else { leaf = &ValidationError{Message: msg} } - return newFieldVersionMismatch(field, leaf, origin) + return newFieldVersionMismatch(field, "3.1", leaf, origin) +} + +type ItemSchemaFieldFor32Plus struct{ ValidationError } + +func (e *ItemSchemaFieldFor32Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type QueryFieldFor32Plus struct{ ValidationError } + +func (e *QueryFieldFor32Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type AdditionalOperationsFieldFor32Plus struct{ ValidationError } + +func (e *AdditionalOperationsFieldFor32Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +var fieldFor32PlusLeaves = map[string]func(msg string) error{ + "itemSchema": func(m string) error { return &ItemSchemaFieldFor32Plus{ValidationError{Message: m}} }, + "query": func(m string) error { return &QueryFieldFor32Plus{ValidationError{Message: m}} }, + "additionalOperations": func(m string) error { + return &AdditionalOperationsFieldFor32Plus{ValidationError{Message: m}} + }, +} + +func errFieldFor32Plus(field string, origin *Origin) error { + msg := "field " + field + " is for OpenAPI >=3.2" + var leaf error + if ctor, ok := fieldFor32PlusLeaves[field]; ok { + leaf = ctor(msg) + } else { + leaf = &ValidationError{Message: msg} + } + return newFieldVersionMismatch(field, "3.2", leaf, origin) +} + +func newAdditionalOperationsDuplicateMethod(method string, origin *Origin) error { + return &AdditionalOperationsDuplicateMethodError{Method: method, Origin: origin} +} + +func newAdditionalOperationsInvalidMethod(method string, origin *Origin) error { + return &AdditionalOperationsInvalidMethodError{Method: method, Origin: origin} } func newPathParameterRequired(param string, origin *Origin) error { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/validation_error_context.go b/vendor/github.com/getkin/kin-openapi/openapi3/validation_error_context.go index 3172230..50da3dc 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/validation_error_context.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/validation_error_context.go @@ -26,8 +26,9 @@ // MediaTypeExampleValidationError, WebhookValidationError, // ParameterFieldValidationError, ParameterExampleValidationError, // SecuritySchemeFlowValidationError, OAuthFlowValidationError, -// OAuthFlowFieldValidationError. Cover a specific validation -// surface inside a section. +// OAuthFlowFieldValidationError, SchemaCombinatorElementValidationError, +// TagValidationError. Cover a specific validation surface inside a +// section. // // Both scopes follow the same shape: // @@ -250,3 +251,50 @@ func (e *OAuthFlowFieldValidationError) Error() string { } func (e *OAuthFlowFieldValidationError) Unwrap() error { return e.Cause } + +// SchemaCombinatorElementValidationError wraps a validation error +// originating in one of the sub-schemas listed under a schema's oneOf, +// anyOf, or allOf keyword. Combinator names which keyword the offending +// element belongs to ("oneOf", "anyOf", "allOf"). The wrapper adds the +// combinator scope; the actual failure lives in Cause. +type SchemaCombinatorElementValidationError struct { + // Combinator is the keyword whose element failed ("oneOf", "anyOf", + // "allOf"). + Combinator string + Cause error +} + +func (e *SchemaCombinatorElementValidationError) Error() string { + // Collapse a run of same-combinator wrappers so deeply nested + // allOf/anyOf/oneOf does not stutter "invalid allOf element: " once per + // nesting level. The typed chain is untouched (Unwrap and errors.As still + // see every level); only the rendered message drops the repeats. A run of + // a different combinator is preserved, so an allOf inside a oneOf still + // shows both scopes. + cause := e.Cause + for { + inner, ok := cause.(*SchemaCombinatorElementValidationError) + if !ok || inner.Combinator != e.Combinator { + break + } + cause = inner.Cause + } + return fmt.Sprintf("invalid %s element: %v", e.Combinator, cause) +} + +func (e *SchemaCombinatorElementValidationError) Unwrap() error { return e.Cause } + +// TagValidationError wraps a validation error originating inside a single +// tag in the document-root Tags list. Name is the tag's `name:` value, so +// callers can tell which tag failed without parsing the rendered message. +type TagValidationError struct { + // Name is the tag's `name:` value. + Name string + Cause error +} + +func (e *TagValidationError) Error() string { + return fmt.Sprintf("tag %q: %v", e.Name, e.Cause) +} + +func (e *TagValidationError) Unwrap() error { return e.Cause } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go b/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go index fd2be87..a7ad0f3 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go @@ -15,6 +15,7 @@ type ValidationOptions struct { schemaExtensionsInRefProhibited bool jsonSchema2020ValidationEnabled bool isOpenAPI31OrLater bool + isOpenAPI32OrLater bool multiErrorEnabled bool regexCompilerFunc RegexCompilerFunc extraSiblingFieldsAllowed map[string]struct{} @@ -42,6 +43,15 @@ func IsOpenAPI31OrLater() ValidationOption { } } +// IsOpenAPI32OrLater enables validation for OpenAPI 3.2 documents. +func IsOpenAPI32OrLater() ValidationOption { + return func(options *ValidationOptions) { + options.isOpenAPI31OrLater = true + options.isOpenAPI32OrLater = true + options.jsonSchema2020ValidationEnabled = true + } +} + // EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. // By default, schema format validation is disabled. func EnableSchemaFormatValidation() ValidationOption { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/walk_parameters.go b/vendor/github.com/getkin/kin-openapi/openapi3/walk_parameters.go new file mode 100644 index 0000000..89a7ab7 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/walk_parameters.go @@ -0,0 +1,126 @@ +package openapi3 + +import ( + "maps" + "slices" + "strconv" +) + +// WalkParametersFunc is called once for each parameter visited by +// WalkParameters. +// +// jsonPointer is the RFC 6901 JSON Pointer of the parameter within the +// document, e.g. "/components/parameters/Limit" or +// "/paths/~1pets/get/parameters/0". For a parameter referenced from several +// places it is the pointer of the first visit, and components are visited +// first, so a shared parameter is reported at its definition. param is +// non-nil and param.Value is non-nil; the callback may modify param.Value in +// place, so WalkParameters serves transformers and not only read-only +// inspection. +// +// Returning a non-nil error aborts the walk and is returned by +// WalkParameters. +type WalkParametersFunc func(jsonPointer string, param *ParameterRef) error + +// WalkParameters visits every parameter reachable from the document exactly +// once, invoking fn for each. It follows resolved $ref targets (param.Value), +// so each distinct *Parameter is visited a single time regardless of how many +// references point at it. Maps are visited in sorted key order, so the +// traversal is deterministic. +// +// It covers components.parameters, path items, operations, callbacks, and +// webhooks. It is the parameter counterpart of WalkSchemas: useful for +// validation, linting, parameter transformation, and documentation, without +// re-deriving the (easy to get wrong) traversal. +func (doc *T) WalkParameters(fn WalkParametersFunc) error { + if doc == nil { + return nil + } + w := parameterWalker{fn: fn, seen: make(map[*Parameter]struct{})} + return w.document(doc) +} + +type parameterWalker struct { + fn WalkParametersFunc + seen map[*Parameter]struct{} +} + +func (w *parameterWalker) document(doc *T) error { + if c := doc.Components; c != nil { + for _, name := range slices.Sorted(maps.Keys(c.Parameters)) { + if err := w.parameter("/components/parameters/"+escapeRefString(name), c.Parameters[name]); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(c.Callbacks)) { + if cbr := c.Callbacks[name]; cbr != nil && cbr.Value != nil { + if err := w.callback("/components/callbacks/"+escapeRefString(name), cbr.Value); err != nil { + return err + } + } + } + } + if doc.Paths != nil { + items := doc.Paths.Map() + for _, path := range slices.Sorted(maps.Keys(items)) { + if err := w.pathItem("/paths/"+escapeRefString(path), items[path]); err != nil { + return err + } + } + } + for _, name := range slices.Sorted(maps.Keys(doc.Webhooks)) { + if err := w.pathItem("/webhooks/"+escapeRefString(name), doc.Webhooks[name]); err != nil { + return err + } + } + return nil +} + +func (w *parameterWalker) pathItem(ptr string, item *PathItem) error { + if item == nil { + return nil + } + for i, pr := range item.Parameters { + if err := w.parameter(ptr+"/parameters/"+strconv.Itoa(i), pr); err != nil { + return err + } + } + for _, entry := range item.operationEntries() { + op := entry.operation + opPtr := ptr + entry.pointerSuffix + for i, pr := range op.Parameters { + if err := w.parameter(opPtr+"/parameters/"+strconv.Itoa(i), pr); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(op.Callbacks)) { + if cbr := op.Callbacks[name]; cbr != nil && cbr.Value != nil { + if err := w.callback(opPtr+"/callbacks/"+escapeRefString(name), cbr.Value); err != nil { + return err + } + } + } + } + return nil +} + +func (w *parameterWalker) callback(ptr string, cb *Callback) error { + items := cb.Map() + for _, expr := range slices.Sorted(maps.Keys(items)) { + if err := w.pathItem(ptr+"/"+escapeRefString(expr), items[expr]); err != nil { + return err + } + } + return nil +} + +func (w *parameterWalker) parameter(ptr string, pr *ParameterRef) error { + if pr == nil || pr.Value == nil { + return nil + } + if _, ok := w.seen[pr.Value]; ok { + return nil + } + w.seen[pr.Value] = struct{}{} + return w.fn(ptr, pr) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/walk_schemas.go b/vendor/github.com/getkin/kin-openapi/openapi3/walk_schemas.go new file mode 100644 index 0000000..8e0e41b --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/walk_schemas.go @@ -0,0 +1,317 @@ +package openapi3 + +import ( + "errors" + "maps" + "slices" + "strconv" + "strings" +) + +// SkipSubtree, when returned by a WalkSchemasFunc, tells WalkSchemas not to +// descend into the current schema's sub-schemas. The schema itself has already +// been visited. Any other non-nil error stops the walk and is returned by +// WalkSchemas. It mirrors filepath.SkipDir. +var SkipSubtree = errors.New("skip schema subtree") + +// WalkSchemasFunc is called once for each schema visited by WalkSchemas. +// +// jsonPointer is the RFC 6901 JSON Pointer of the schema within the document, +// e.g. "/components/schemas/Pet/properties/tag" or +// "/paths/~1pets/get/responses/200/content/application~1json/schema". It is +// file-agnostic: once the loader has resolved external $refs the document is a +// single tree, so the pointer addresses a position in that tree and never +// encodes a source file. schema is non-nil and schema.Value is non-nil; the +// callback may modify schema.Value in place, so WalkSchemas serves transformers +// and not only read-only inspection. +// +// Returning SkipSubtree skips this schema's sub-schemas; returning any other +// error aborts the walk and is returned by WalkSchemas. +type WalkSchemasFunc func(jsonPointer string, schema *SchemaRef) error + +// WalkSchemas visits every schema reachable from the document exactly once, +// invoking fn for each. It follows resolved $ref targets (schema.Value) and +// guards against reference cycles, so each distinct *Schema is visited a single +// time regardless of how many references point at it. Maps are visited in +// sorted key order, so the traversal is deterministic. +// +// It covers schemas under components (schemas, parameters, headers, request +// bodies, responses, callbacks), the paths and their operations (parameters, +// request bodies, responses, headers, callbacks), and webhooks, then recurses +// through every sub-schema keyword: properties, items, itemSchema, +// allOf/anyOf/oneOf, not, additionalProperties, prefixItems, contains, patternProperties, +// dependentSchemas, propertyNames, if/then/else, and $defs. +// +// It is useful for validation, code generation, schema transformation, +// $ref/dependency analysis, and documentation: any consumer that needs to act +// on every schema in a document without re-deriving the (easy to get wrong) +// traversal itself. +func (doc *T) WalkSchemas(fn WalkSchemasFunc) error { + if doc == nil { + return nil + } + w := schemaWalker{fn: fn, seen: make(map[*Schema]struct{})} + return w.document(doc) +} + +type schemaWalker struct { + fn WalkSchemasFunc + seen map[*Schema]struct{} +} + +// escapeRefString escapes a single JSON Pointer reference token per RFC 6901: +// '~' becomes '~0' and '/' becomes '~1'. It is the inverse of unescapeRefString. +func escapeRefString(s string) string { + if !strings.ContainsAny(s, "~/") { + return s + } + return strings.NewReplacer("~", "~0", "/", "~1").Replace(s) +} + +func (w *schemaWalker) document(doc *T) error { + if c := doc.Components; c != nil { + for _, name := range slices.Sorted(maps.Keys(c.Schemas)) { + if err := w.schemaRef("/components/schemas/"+escapeRefString(name), c.Schemas[name]); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(c.Parameters)) { + if err := w.parameter("/components/parameters/"+escapeRefString(name), c.Parameters[name]); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(c.Headers)) { + if err := w.header("/components/headers/"+escapeRefString(name), c.Headers[name]); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(c.RequestBodies)) { + if rbr := c.RequestBodies[name]; rbr != nil && rbr.Value != nil { + if err := w.content("/components/requestBodies/"+escapeRefString(name)+"/content", rbr.Value.Content); err != nil { + return err + } + } + } + for _, name := range slices.Sorted(maps.Keys(c.Responses)) { + if err := w.response("/components/responses/"+escapeRefString(name), c.Responses[name]); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(c.Callbacks)) { + if cbr := c.Callbacks[name]; cbr != nil && cbr.Value != nil { + if err := w.callback("/components/callbacks/"+escapeRefString(name), cbr.Value); err != nil { + return err + } + } + } + } + if doc.Paths != nil { + items := doc.Paths.Map() + for _, path := range slices.Sorted(maps.Keys(items)) { + if err := w.pathItem("/paths/"+escapeRefString(path), items[path]); err != nil { + return err + } + } + } + for _, name := range slices.Sorted(maps.Keys(doc.Webhooks)) { + if err := w.pathItem("/webhooks/"+escapeRefString(name), doc.Webhooks[name]); err != nil { + return err + } + } + return nil +} + +func (w *schemaWalker) pathItem(ptr string, item *PathItem) error { + if item == nil { + return nil + } + for i, pr := range item.Parameters { + if err := w.parameter(ptr+"/parameters/"+strconv.Itoa(i), pr); err != nil { + return err + } + } + for _, entry := range item.operationEntries() { + if err := w.operation(ptr+entry.pointerSuffix, entry.operation); err != nil { + return err + } + } + return nil +} + +func (w *schemaWalker) operation(ptr string, op *Operation) error { + if op == nil { + return nil + } + for i, pr := range op.Parameters { + if err := w.parameter(ptr+"/parameters/"+strconv.Itoa(i), pr); err != nil { + return err + } + } + if op.RequestBody != nil && op.RequestBody.Value != nil { + if err := w.content(ptr+"/requestBody/content", op.RequestBody.Value.Content); err != nil { + return err + } + } + if op.Responses != nil { + responses := op.Responses.Map() + for _, code := range slices.Sorted(maps.Keys(responses)) { + if err := w.response(ptr+"/responses/"+escapeRefString(code), responses[code]); err != nil { + return err + } + } + } + for _, name := range slices.Sorted(maps.Keys(op.Callbacks)) { + if cbr := op.Callbacks[name]; cbr != nil && cbr.Value != nil { + if err := w.callback(ptr+"/callbacks/"+escapeRefString(name), cbr.Value); err != nil { + return err + } + } + } + return nil +} + +func (w *schemaWalker) callback(ptr string, cb *Callback) error { + items := cb.Map() + for _, expr := range slices.Sorted(maps.Keys(items)) { + if err := w.pathItem(ptr+"/"+escapeRefString(expr), items[expr]); err != nil { + return err + } + } + return nil +} + +func (w *schemaWalker) parameter(ptr string, pr *ParameterRef) error { + if pr == nil || pr.Value == nil { + return nil + } + if err := w.schemaRef(ptr+"/schema", pr.Value.Schema); err != nil { + return err + } + return w.content(ptr+"/content", pr.Value.Content) +} + +func (w *schemaWalker) header(ptr string, hr *HeaderRef) error { + if hr == nil || hr.Value == nil { + return nil + } + if err := w.schemaRef(ptr+"/schema", hr.Value.Schema); err != nil { + return err + } + return w.content(ptr+"/content", hr.Value.Content) +} + +func (w *schemaWalker) response(ptr string, rr *ResponseRef) error { + if rr == nil || rr.Value == nil { + return nil + } + for _, name := range slices.Sorted(maps.Keys(rr.Value.Headers)) { + if err := w.header(ptr+"/headers/"+escapeRefString(name), rr.Value.Headers[name]); err != nil { + return err + } + } + return w.content(ptr+"/content", rr.Value.Content) +} + +func (w *schemaWalker) content(ptr string, content Content) error { + for _, mediaType := range slices.Sorted(maps.Keys(content)) { + media := content[mediaType] + if media == nil { + continue + } + if err := w.schemaRef(ptr+"/"+escapeRefString(mediaType)+"/schema", media.Schema); err != nil { + return err + } + if err := w.schemaRef(ptr+"/"+escapeRefString(mediaType)+"/itemSchema", media.ItemSchema); err != nil { + return err + } + } + return nil +} + +func (w *schemaWalker) schemaRefs(ptr string, refs SchemaRefs) error { + for i, sub := range refs { + if err := w.schemaRef(ptr+"/"+strconv.Itoa(i), sub); err != nil { + return err + } + } + return nil +} + +func (w *schemaWalker) schemaRef(ptr string, sr *SchemaRef) error { + if sr == nil || sr.Value == nil { + return nil + } + s := sr.Value + if _, ok := w.seen[s]; ok { + // Already visited (shared $ref target or reference cycle). + return nil + } + w.seen[s] = struct{}{} + + if err := w.fn(ptr, sr); err != nil { + if errors.Is(err, SkipSubtree) { + return nil + } + return err + } + + for _, name := range slices.Sorted(maps.Keys(s.Properties)) { + if err := w.schemaRef(ptr+"/properties/"+escapeRefString(name), s.Properties[name]); err != nil { + return err + } + } + if err := w.schemaRef(ptr+"/items", s.Items); err != nil { + return err + } + if s.AdditionalProperties.Schema != nil { + if err := w.schemaRef(ptr+"/additionalProperties", s.AdditionalProperties.Schema); err != nil { + return err + } + } + if err := w.schemaRefs(ptr+"/allOf", s.AllOf); err != nil { + return err + } + if err := w.schemaRefs(ptr+"/anyOf", s.AnyOf); err != nil { + return err + } + if err := w.schemaRefs(ptr+"/oneOf", s.OneOf); err != nil { + return err + } + if err := w.schemaRef(ptr+"/not", s.Not); err != nil { + return err + } + if err := w.schemaRefs(ptr+"/prefixItems", s.PrefixItems); err != nil { + return err + } + if err := w.schemaRef(ptr+"/contains", s.Contains); err != nil { + return err + } + for _, name := range slices.Sorted(maps.Keys(s.PatternProperties)) { + if err := w.schemaRef(ptr+"/patternProperties/"+escapeRefString(name), s.PatternProperties[name]); err != nil { + return err + } + } + for _, name := range slices.Sorted(maps.Keys(s.DependentSchemas)) { + if err := w.schemaRef(ptr+"/dependentSchemas/"+escapeRefString(name), s.DependentSchemas[name]); err != nil { + return err + } + } + if err := w.schemaRef(ptr+"/propertyNames", s.PropertyNames); err != nil { + return err + } + if err := w.schemaRef(ptr+"/if", s.If); err != nil { + return err + } + if err := w.schemaRef(ptr+"/then", s.Then); err != nil { + return err + } + if err := w.schemaRef(ptr+"/else", s.Else); err != nil { + return err + } + for _, name := range slices.Sorted(maps.Keys(s.Defs)) { + if err := w.schemaRef(ptr+"/$defs/"+escapeRefString(name), s.Defs[name]); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/oasdiff/yaml/.golangci.toml b/vendor/github.com/oasdiff/yaml/.golangci.toml deleted file mode 100644 index 61b2b79..0000000 --- a/vendor/github.com/oasdiff/yaml/.golangci.toml +++ /dev/null @@ -1,16 +0,0 @@ -[run] -timeout = "120s" - -[output] -format = "colored-line-number" - -[linters] -enable = [ - "gocyclo", "unconvert", "goimports", "unused", "unused", - "vetshadow", "nakedret", "errcheck", "revive", "ineffassign", - "goconst", "vet", "unparam", "gofmt" -] - -[issues] -exclude-use-default = false - diff --git a/vendor/github.com/oasdiff/yaml/.golangci.yml b/vendor/github.com/oasdiff/yaml/.golangci.yml new file mode 100644 index 0000000..1324e85 --- /dev/null +++ b/vendor/github.com/oasdiff/yaml/.golangci.yml @@ -0,0 +1,26 @@ +version: "2" + +run: + timeout: 120s + +linters: + enable: + - errcheck + - goconst + - gocyclo + - govet + - ineffassign + - nakedret + - revive + - unconvert + - unparam + - unused + settings: + govet: + enable: + - shadow + +formatters: + enable: + - gofmt + - goimports diff --git a/vendor/github.com/oasdiff/yaml/README.md b/vendor/github.com/oasdiff/yaml/README.md index a40f33d..dd6036e 100644 --- a/vendor/github.com/oasdiff/yaml/README.md +++ b/vendor/github.com/oasdiff/yaml/README.md @@ -1,9 +1,9 @@ # YAML marshaling and unmarshaling support for Go -[![Lint](https://github.com/invopop/yaml/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/lint.yaml) -[![Test Go](https://github.com/invopop/yaml/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/test.yaml) -[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/yaml)](https://goreportcard.com/report/github.com/invopop/yaml) -![Latest Tag](https://img.shields.io/github/v/tag/invopop/yaml) +[![Lint](https://github.com/oasdiff/yaml/actions/workflows/lint.yaml/badge.svg)](https://github.com/oasdiff/yaml/actions/workflows/lint.yaml) +[![Test Go](https://github.com/oasdiff/yaml/actions/workflows/test.yaml/badge.svg)](https://github.com/oasdiff/yaml/actions/workflows/test.yaml) +[![Go Report Card](https://goreportcard.com/badge/github.com/oasdiff/yaml)](https://goreportcard.com/report/github.com/oasdiff/yaml) +![Latest Tag](https://img.shields.io/github/v/tag/oasdiff/yaml) ## Fork This fork is an improved version of the invopop/yaml package, designed to include line and column location information for YAML elements during unmarshalling. @@ -63,13 +63,13 @@ GOOD: To install, run: ``` -$ go get github.com/invopop/yaml +$ go get github.com/oasdiff/yaml ``` And import using: ``` -import "github.com/invopop/yaml" +import "github.com/oasdiff/yaml" ``` Usage is very similar to the JSON library: @@ -80,7 +80,7 @@ package main import ( "fmt" - "github.com/invopop/yaml" + "github.com/oasdiff/yaml" ) type Person struct { @@ -124,7 +124,7 @@ package main import ( "fmt" - "github.com/invopop/yaml" + "github.com/oasdiff/yaml" ) func main() { diff --git a/vendor/github.com/oasdiff/yaml/yaml.go b/vendor/github.com/oasdiff/yaml/yaml.go index 86882f2..3ec5366 100644 --- a/vendor/github.com/oasdiff/yaml/yaml.go +++ b/vendor/github.com/oasdiff/yaml/yaml.go @@ -5,7 +5,7 @@ // uses json.Marshal and json.Unmarshal to convert to or from the struct. This // means that it effectively reuses the JSON struct tags as well as the custom // JSON methods MarshalJSON and UnmarshalJSON unlike go-yaml. -package yaml // import "github.com/invopop/yaml" +package yaml // import "github.com/oasdiff/yaml" import ( "bytes" diff --git a/vendor/github.com/oasdiff/yaml3/decode.go b/vendor/github.com/oasdiff/yaml3/decode.go index 4a6e076..f36d6bf 100644 --- a/vendor/github.com/oasdiff/yaml3/decode.go +++ b/vendor/github.com/oasdiff/yaml3/decode.go @@ -187,6 +187,13 @@ func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node { if !p.textless { n.Line = p.event.start_mark.line + 1 n.Column = p.event.start_mark.column + 1 + // end_mark is the position just past this event. For scalars and aliases + // it already spans the whole node. For mappings and sequences this is the + // MAPPING-START/SEQUENCE-START event, so it only marks the start for now; + // mapping()/sequence() overwrite it from the matching END event so the + // span covers the whole block. + n.EndLine = p.event.end_mark.line + 1 + n.EndColumn = p.event.end_mark.column + 1 n.HeadComment = string(p.event.head_comment) n.LineComment = string(p.event.line_comment) n.FootComment = string(p.event.foot_comment) @@ -264,6 +271,26 @@ func (p *parser) sequence() *Node { } n.LineComment = string(p.event.line_comment) n.FootComment = string(p.event.foot_comment) + // End at the last item's end so the span reaches the end of the actual + // content, consistent with scalars/aliases. The SEQUENCE-END token sits at + // the start of the following line after a block dedent, which would + // overshoot the element. Empty sequences fall back to that token's mark. + if !p.textless { + if n.Style&FlowStyle != 0 { + // Flow collections close with an explicit `}`/`]`; the END token's + // mark is just past that delimiter, so the span covers the whole + // collection (and stays consistent with the empty-flow fallback, + // which has no last child and uses the same mark). + n.EndLine = p.event.end_mark.line + 1 + n.EndColumn = p.event.end_mark.column + 1 + } else if len(n.Content) > 0 { + last := n.Content[len(n.Content)-1] + n.EndLine, n.EndColumn = last.EndLine, last.EndColumn + } else { + n.EndLine = p.event.end_mark.line + 1 + n.EndColumn = p.event.end_mark.column + 1 + } + } p.expect(yaml_SEQUENCE_END_EVENT) return n } @@ -304,6 +331,28 @@ func (p *parser) mapping() *Node { n.Content[len(n.Content)-2].FootComment = n.FootComment n.FootComment = "" } + // End at the last entry's value end so the span reaches the end of the + // actual content, consistent with scalars/aliases. The MAPPING-END token + // sits at the start of the following line after a block dedent, which would + // overshoot the element. Empty mappings fall back to that token's mark. + // (Origin __origin__ nodes are appended later, during decode, so the last + // element here is a real value.) + if !p.textless { + if n.Style&FlowStyle != 0 { + // Flow collections close with an explicit `}`/`]`; the END token's + // mark is just past that delimiter, so the span covers the whole + // collection (and stays consistent with the empty-flow fallback, + // which has no last child and uses the same mark). + n.EndLine = p.event.end_mark.line + 1 + n.EndColumn = p.event.end_mark.column + 1 + } else if len(n.Content) > 0 { + last := n.Content[len(n.Content)-1] + n.EndLine, n.EndColumn = last.EndLine, last.EndColumn + } else { + n.EndLine = p.event.end_mark.line + 1 + n.EndColumn = p.event.end_mark.column + 1 + } + } p.expect(yaml_MAPPING_END_EVENT) return n } diff --git a/vendor/github.com/oasdiff/yaml3/origin.go b/vendor/github.com/oasdiff/yaml3/origin.go index 3f2f0a7..2edec47 100644 --- a/vendor/github.com/oasdiff/yaml3/origin.go +++ b/vendor/github.com/oasdiff/yaml3/origin.go @@ -33,7 +33,7 @@ func addOriginInMap(key, n *Node, file string) *Node { // addOrigin injects a compact __origin__ sequence into the mapping node n. // -// Format: [file, key_name, key_line, key_col, nf, f1_name, f1_delta, f1_col, ..., ns, s1_name, s1_count, s1_l0_delta, s1_c0, ...] +// Format: [file, key_name, key_line, key_col, nf, f1_name, f1_delta, f1_col, ..., ns, s1_name, s1_count, s1_l0_delta, s1_c0, ..., end_delta, end_col] // // - file: source file path // - key_name: the YAML key whose value is this mapping @@ -42,6 +42,10 @@ func addOriginInMap(key, n *Node, file string) *Node { // - per field: name (string), line delta from key_line (int), column (int) // - ns: number of sequence fields that have item locations // - per sequence: name (string), item count (int), then count × (line delta, col) +// - end_delta, end_col: end of the whole mapping block — line delta from +// key_line and absolute column of the position just past its last content. +// Appended last so a consumer that stops after the sequences section +// simply ignores it (backward compatible). func addOrigin(key, n *Node, file string) *Node { if isOrigin(key) { return n @@ -108,6 +112,16 @@ func buildOriginSeq(key, n *Node, file string) []*Node { nodes = append(nodes, fieldNodes...) nodes = append(nodes, intNode(ns)) nodes = append(nodes, seqNodes...) + + // Block end: line delta from key_line and absolute end column of the whole + // mapping. Lets a consumer reconstruct the full block span + // [key_line, key_line+end_delta] -- e.g. an entire endpoint operation block. + endDelta, endCol := 0, 0 + if n.EndLine > 0 { + endDelta = n.EndLine - key.Line + endCol = n.EndColumn + } + nodes = append(nodes, intNode(endDelta), intNode(endCol)) return nodes } diff --git a/vendor/github.com/oasdiff/yaml3/scannerc.go b/vendor/github.com/oasdiff/yaml3/scannerc.go index ca00701..7ee4f10 100644 --- a/vendor/github.com/oasdiff/yaml3/scannerc.go +++ b/vendor/github.com/oasdiff/yaml3/scannerc.go @@ -2309,6 +2309,12 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l return false } var leading_blank, trailing_blank bool + // content_end_mark tracks the position just past the last content character + // (captured after each content line is read, before its line break is + // consumed). The block scalar's end belongs here: end_mark otherwise advances + // to the start of the next line, which overshoots the span into the following + // node. Initialized to end_mark so an empty block scalar is unchanged. + content_end_mark := end_mark for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { // We are at the beginning of a non-empty line. @@ -2340,6 +2346,7 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l return false } } + content_end_mark = parser.mark // Consume the line break. if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { @@ -2366,7 +2373,7 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l *token = yaml_token_t{ typ: yaml_SCALAR_TOKEN, start_mark: start_mark, - end_mark: end_mark, + end_mark: content_end_mark, value: s, style: yaml_LITERAL_SCALAR_STYLE, } diff --git a/vendor/github.com/oasdiff/yaml3/yaml.go b/vendor/github.com/oasdiff/yaml3/yaml.go index 4f91be8..f8095e3 100644 --- a/vendor/github.com/oasdiff/yaml3/yaml.go +++ b/vendor/github.com/oasdiff/yaml3/yaml.go @@ -439,6 +439,14 @@ type Node struct { // These fields are not respected when encoding the node. Line int Column int + + // EndLine and EndColumn hold the position just past the end of the node in + // the decoded YAML text. For a mapping or sequence this spans the whole + // block (so a caller can extract the entire collection), not just its first + // line. Like Line and Column, these are 1-based and are not respected when + // encoding the node. + EndLine int + EndColumn int } // IsZero returns whether the node has all of its fields unset. diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/.golangci.yml b/vendor/github.com/santhosh-tekuri/jsonschema/v6/.golangci.yml index 6534d53..94a3eca 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/.golangci.yml +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/.golangci.yml @@ -1,4 +1,3 @@ -version: "2" linters: enable: - nakedret diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/.swp b/vendor/github.com/santhosh-tekuri/jsonschema/v6/.swp deleted file mode 100644 index e511900313abf7ae50f168b5af6bf15bdabc11fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeHNON`q@7@krnlu|$-0hJJ%2C7Xqu{YW5BfFx~0#YRk?XI+Kp<1nA5<44ry>=8k z*=|wQD-sf103jiuAb~ja#tjZ|sl*K-jyw(=3tW&;Z@@QWXY+J6n>de3W9hSFJD&OH zn}2@K_j|I*3oo9b3u=;JdzkOM?dEAh9A>!%+bLA91szxY|2fJTmV4f|HyiP9z?b(B@-Gq+UjYT+3H}=ijCJ4}Bswkv zKcT(6&1b_+P|yKyag<-u-EzsIMRm1UGAuVAjp$Z+i{_bO>RKd525bUV+S)L|jhGmo z6;}t^&Q^{q%q;74tL(Tmt5e$o^7MogNl?c{H*7fSY1^=(+gJ;eptu@|4W=hTZTHd= zRZ{Z{iFq3L4!B_2c5yLGa;K&gCB*W=D=IuH+Yd`P$#UKw5pa`o*2xw0E#}Y@S^}V3 z%*6!_&a$bl?%rKXt~Xsst%v7I0fSeVS{=RPVrw}4!~2GBtwXx8e=I(xt6dTf;hb!~ zwN^dRM0m)R8+mH#R zq?22^}xEGI``%{9*@)P)TUu+VyAA{wYn5Vp)BAY9?&mSmY&aNlUY@r*H|hw&1fo_oST`c z6`>jVJOcfO;Wm{NTq(8Krmo@DFG|rZmNj+Et4;A{bQXzH(MyhDTZ=R@9U;8^sl*KC z;fXX|S|VP1&c=A{d2SF8ErPLstYqLt><>_=him-ALcfk6R!WC zf%Lx)aQ$EXzW))l{UY!qbbK0k8@l~JX!T9td+75C;4|p*ThQXK1MfkL9|E?a!#{@x z{~a3qIPey<_dn3x7lG^0-G4%Fp8|M&pYS^O*yvbpUhjC*dGJ~Dld-ba^OteA((~Zy zWM)RsBvn<*XEGUmwq*p46|)WDV3%MFuHD~b>Jpt^J|kv$EE%hNLyef8y6T0ZZPk=H z8=A{fBr1c74EuVHIXbd@$WSGa-z~0r z={|lw{C;(|4dnbO|4Kv`Pa^OC3Vx|kcc-YwdP_j|#FqC4Ln;?x%;{6p_hi5x4+%BA zFekoCi9#3=q%y&&h*iKr$d1kPJu$Bm*@D`hQmsw*)fW>iVnApH)}*l)5tf z*>r!#Q(9pq$`yb^&++{oK)uipmcA|QjaDeMRE$oFvmvUOo_P;*Ia<#~bbe47kAzJ} zBcc6$*}v-w!i~dOJk_!4er_tld(T$ASC~vyRrT@$cZwgAekd69vz${em{EN09%oab z<-M?_TRB#A$|iFWhfM>yHLUNw0~y RxuO-kkumD8)T>*U_diiX^HBf* diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/format.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/format.go index b78b22e..0d7f590 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/format.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/format.go @@ -318,7 +318,7 @@ func validateEmail(v any) error { return LocalizableError("local part more than 64 characters long") } - if len(local) > 1 && strings.HasPrefix(local, `"`) && strings.HasPrefix(local, `"`) { + if len(local) > 1 && strings.HasPrefix(local, `"`) && strings.HasSuffix(local, `"`) { // quoted local := local[1 : len(local)-1] if strings.IndexByte(local, '\\') != -1 || strings.IndexByte(local, '"') != -1 { diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/go.work.sum b/vendor/github.com/santhosh-tekuri/jsonschema/v6/go.work.sum index 2b5b811..c268feb 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/go.work.sum +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/go.work.sum @@ -1,4 +1,4 @@ -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/kind/kind.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/kind/kind.go index a37fb0b..62efa75 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/kind/kind.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/kind/kind.go @@ -511,7 +511,7 @@ func (*ContentMediaType) KeywordPath() []string { } func (k *ContentMediaType) LocalizedString(p *message.Printer) string { - return p.Sprintf("value if not of mediatype %s: %v", quote(k.Want), k.Err) + return p.Sprintf("value is not of mediatype %s: %v", quote(k.Want), k.Err) } // -- diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/objcompiler.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/objcompiler.go index f1494b1..d47efa8 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/objcompiler.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/objcompiler.go @@ -355,9 +355,13 @@ func (c *objCompiler) enqueueRef(pname string) (*Schema, error) { if ref == nil { return nil, nil } + return c.enqueueRefVal(*ref) +} + +func (c *objCompiler) enqueueRefVal(ref string) (*Schema, error) { baseURL := c.res.id // baseURL := c.r.baseURL(c.up.ptr) - uf, err := baseURL.join(*ref) + uf, err := baseURL.join(ref) if err != nil { return nil, err } diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/schema.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/schema.go index b4c1f37..ab2474f 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/schema.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/schema.go @@ -1,8 +1,25 @@ +/* +Package jsonschema provides json-schema compilation and validation. + +The schema is compiled against the version specified in "$schema" property. +If "$schema" property is missing, it uses latest draft which currently implemented +by this library. + +You can force to use specific draft, when "$schema" is missing, as follows: + + compiler := jsonschema.NewCompiler() + compiler.DefaultDraft(jsonschema.Draft4) + +This package supports loading json-schema from filePath and fileURL. + +see examples for usage. +*/ package jsonschema import ( "encoding/json" "fmt" + "math" "math/big" ) @@ -110,12 +127,22 @@ const ( ) func typeOf(v any) jsonType { - switch v.(type) { + switch v := v.(type) { case nil: return nullType case bool: return booleanType - case json.Number, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + case float64: + if math.IsNaN(v) || math.IsInf(v, 0) { + return invalidType + } + return numberType + case float32: + if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) { + return invalidType + } + return numberType + case json.Number, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return numberType case string: return stringType diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/util.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/util.go index c6f8e77..8cd32aa 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/util.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/util.go @@ -320,6 +320,9 @@ func equals(v1, v2 any) (bool, ErrorKind) { v2, ok := v2.(string) return ok && v1 == v2, nil case json.Number, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + if typeOf(v2) != numberType { + return false, nil + } num1, ok1 := new(big.Rat).SetString(fmt.Sprint(v1)) num2, ok2 := new(big.Rat).SetString(fmt.Sprint(v2)) return ok1 && ok2 && num1.Cmp(num2) == 0, nil diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/validator.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/validator.go index e2ace37..53a4c73 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/validator.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/validator.go @@ -293,6 +293,7 @@ func (vd *validator) objValidate(obj map[string]any) { } if err := sch.validate(pname, vd.regexpEngine, meta, resources, vd.assertVocabs, vd.vocabularies); err != nil { verr := err.(*ValidationError) + verr.InstanceLocation = vd.vloc verr.SchemaURL = s.PropertyNames.Location verr.ErrorKind = &kind.PropertyNames{Property: pname} vd.addErr(verr) @@ -375,7 +376,7 @@ func (vd *validator) arrValidate(arr []any) { } case *Schema: for i, item := range arr[evaluated:] { - vd.addErr(vd.validateVal(additional, item, strconv.Itoa(i))) + vd.addErr(vd.validateVal(additional, item, strconv.Itoa(evaluated+i))) } } } @@ -390,7 +391,7 @@ func (vd *validator) arrValidate(arr []any) { // items2020 -- if s.Items2020 != nil { for i, item := range arr[evaluated:] { - vd.addErr(vd.validateVal(s.Items2020, item, strconv.Itoa(i))) + vd.addErr(vd.validateVal(s.Items2020, item, strconv.Itoa(evaluated+i))) } } } @@ -503,6 +504,7 @@ func (vd *validator) strValidate(str string) { } if err = sch.validate(*deserialized, vd.regexpEngine, meta, resources, vd.assertVocabs, vd.vocabularies); err != nil { verr := err.(*ValidationError) + verr.InstanceLocation = vd.vloc verr.SchemaURL = s.Location verr.ErrorKind = &kind.ContentSchema{} vd.addErr(verr) diff --git a/vendor/github.com/santhosh-tekuri/jsonschema/v6/vocab.go b/vendor/github.com/santhosh-tekuri/jsonschema/v6/vocab.go index c81cb70..eaed8c9 100644 --- a/vendor/github.com/santhosh-tekuri/jsonschema/v6/vocab.go +++ b/vendor/github.com/santhosh-tekuri/jsonschema/v6/vocab.go @@ -14,6 +14,10 @@ func (ctx *CompilerContext) Enqueue(schPath []string) *Schema { return ctx.c.enqueuePtr(ptr) } +func (ctx *CompilerContext) EnqueueRef(ref string) (*Schema, error) { + return ctx.c.enqueueRefVal(ref) +} + // Vocabulary defines a set of keywords, their syntax and // their semantics. type Vocabulary struct { diff --git a/vendor/modules.txt b/vendor/modules.txt index d41bd12..aac0c7b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,7 +4,7 @@ github.com/clipperhouse/uax29/v2/graphemes # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/getkin/kin-openapi v0.140.0 +# github.com/getkin/kin-openapi v0.149.0 ## explicit; go 1.25 github.com/getkin/kin-openapi/openapi3 # github.com/go-chi/chi/v5 v5.3.0 @@ -75,11 +75,11 @@ github.com/mattn/go-runewidth # github.com/mitchellh/go-homedir v1.1.0 ## explicit github.com/mitchellh/go-homedir -# github.com/oasdiff/yaml v0.1.0 -## explicit; go 1.22.5 +# github.com/oasdiff/yaml v0.1.1 +## explicit; go 1.25 github.com/oasdiff/yaml -# github.com/oasdiff/yaml3 v0.0.13 -## explicit; go 1.22.5 +# github.com/oasdiff/yaml3 v0.0.14 +## explicit; go 1.25 github.com/oasdiff/yaml3 # github.com/pmezard/go-difflib v1.0.0 ## explicit @@ -90,7 +90,7 @@ github.com/rs/zerolog github.com/rs/zerolog/internal/cbor github.com/rs/zerolog/internal/json github.com/rs/zerolog/log -# github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 +# github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 ## explicit; go 1.21 github.com/santhosh-tekuri/jsonschema/v6 github.com/santhosh-tekuri/jsonschema/v6/kind