From eab5230f5ae92c88ea7d2c50358a1a4008a6c7d0 Mon Sep 17 00:00:00 2001 From: Pierre Fenoll Date: Sat, 11 Jul 2026 15:03:21 +0200 Subject: [PATCH 1/8] openapi3: fix test that relied on now non-resolving domain Signed-off-by: Pierre Fenoll --- openapi3/issue513_test.go | 64 ++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/openapi3/issue513_test.go b/openapi3/issue513_test.go index d26fb7667..2801d5a46 100644 --- a/openapi3/issue513_test.go +++ b/openapi3/issue513_test.go @@ -2,6 +2,7 @@ package openapi3 import ( "encoding/json" + "net/url" "strings" "testing" @@ -34,40 +35,47 @@ paths: $ref: http://schemas.sentex.io/store/categories.json ` - // When that site fails to respond: - // see https://github.com/getkin/kin-openapi/issues/495 + resolver := func(loader *Loader, url *url.URL) (data []byte, err error) { + switch url.String() { + case "http://schemas.sentex.io/store/categories.json": + data = []byte(` + { + "$id": "http://schemas.sentex.io/store/categories.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "array of category strings", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "http://schemas.sentex.io/store/category.json" + } + ] + } + }`) - // http://schemas.sentex.io/store/categories.json - // { - // "$id": "http://schemas.sentex.io/store/categories.json", - // "$schema": "http://json-schema.org/draft-07/schema#", - // "description": "array of category strings", - // "type": "array", - // "items": { - // "allOf": [ - // { - // "$ref": "http://schemas.sentex.io/store/category.json" - // } - // ] - // } - // } + case "http://schemas.sentex.io/store/category.json": + data = []byte(` + { + "$id": "http://schemas.sentex.io/store/category.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "category name for products", + "type": "string", + "pattern": "^[A-Za-z0-9\\-]+$", + "minimum": 1, + "maximum": 30 + }`) - // http://schemas.sentex.io/store/category.json - // { - // "$id": "http://schemas.sentex.io/store/category.json", - // "$schema": "http://json-schema.org/draft-07/schema#", - // "description": "category name for products", - // "type": "string", - // "pattern": "^[A-Za-z0-9\\-]+$", - // "minimum": 1, - // "maximum": 30 - // } + default: + panic(url) + } + return + } - for _, majmin := range []string{"'3.0'", "'3.1'"} { + for _, majmin := range []string{"'3.0'", "'3.1'", "'3.2'"} { t.Run(majmin, func(t *testing.T) { t.Parallel() sl := NewLoader() - sl.IsExternalRefsAllowed = true + sl.ReadFromURIFunc = resolver doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.1", majmin))) require.NoError(t, err) From 61f37b693702a391985bd49638954004eda0135e Mon Sep 17 00:00:00 2001 From: Pierre Fenoll Date: Sat, 11 Jul 2026 15:05:18 +0200 Subject: [PATCH 2/8] openapi3: move a test file under a test package Signed-off-by: Pierre Fenoll --- openapi3/issue513_test.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openapi3/issue513_test.go b/openapi3/issue513_test.go index 2801d5a46..55a399d02 100644 --- a/openapi3/issue513_test.go +++ b/openapi3/issue513_test.go @@ -1,4 +1,4 @@ -package openapi3 +package openapi3_test import ( "encoding/json" @@ -7,6 +7,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestExtraSiblingsInRemoteRef(t *testing.T) { @@ -35,7 +37,7 @@ paths: $ref: http://schemas.sentex.io/store/categories.json ` - resolver := func(loader *Loader, url *url.URL) (data []byte, err error) { + resolver := func(loader *openapi3.Loader, url *url.URL) (data []byte, err error) { switch url.String() { case "http://schemas.sentex.io/store/categories.json": data = []byte(` @@ -74,13 +76,13 @@ paths: for _, majmin := range []string{"'3.0'", "'3.1'", "'3.2'"} { t.Run(majmin, func(t *testing.T) { t.Parallel() - sl := NewLoader() + sl := openapi3.NewLoader() sl.ReadFromURIFunc = resolver doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.1", majmin))) require.NoError(t, err) - err = doc.Validate(sl.Context, AllowExtraSiblingFields("$id", "$schema")) + err = doc.Validate(sl.Context, openapi3.AllowExtraSiblingFields("$id", "$schema")) require.NoError(t, err) }) } @@ -122,7 +124,7 @@ components: for _, majmin := range []string{"3.0", "3.1"} { t.Run(majmin, func(t *testing.T) { t.Parallel() - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) require.NoError(t, err) err = doc.Validate(sl.Context) @@ -173,7 +175,7 @@ components: for _, majmin := range []string{"3.0", "3.1"} { t.Run(majmin, func(t *testing.T) { t.Parallel() - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) require.NoError(t, err) require.Contains(t, doc.Paths.Value("/v1/operation").Delete.Responses.Default().Value.Extensions, `x-my-extension`) @@ -220,7 +222,7 @@ components: for _, majmin := range []string{"3.0", "3.1"} { t.Run(majmin, func(t *testing.T) { t.Parallel() - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) require.NoError(t, err) err = doc.Validate(sl.Context) @@ -266,10 +268,10 @@ components: for _, majmin := range []string{"3.0", "3.1"} { t.Run(majmin, func(t *testing.T) { t.Parallel() - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) require.NoError(t, err) - err = doc.Validate(sl.Context, AllowExtraSiblingFields("description")) + err = doc.Validate(sl.Context, openapi3.AllowExtraSiblingFields("description")) require.NoError(t, err) }) } From 1223a0f215d2cf9beb2d9eb9ea2649d001c21388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Insaurralde?= Date: Sat, 11 Jul 2026 17:56:57 -0300 Subject: [PATCH 3/8] openapi3filter: fix unbounded allocation in deepObject array decoding (C-02) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sliceMapToSlice built a []any sized to the largest attacker-supplied array index before schema validation (maxItems) ever ran, letting a ~24-byte deepObject query parameter (e.g. param[items][50000000]=x) force multi-gigabyte heap allocation. Cap the gap between supplied elements and the reconstructed slice size, and reject negative indexes. Signed-off-by: Matías Insaurralde --- openapi3filter/req_resp_decoder.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index 539603898..6e7efdeef 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -932,6 +932,13 @@ func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string return result, nil } +// maxSliceMapToSliceGap bounds how many synthesized nil holes sliceMapToSlice +// will fill in for a sparse array before rejecting the input. Without this, +// an attacker-supplied index (e.g. from a deepObject query parameter) drives +// an allocation proportional to the index itself, regardless of how many +// elements were actually provided. +const maxSliceMapToSliceGap = 10000 + // example: map[0:map[key:true] 1:map[key:false]] -> [map[key:true] map[key:false]] func sliceMapToSlice(m map[string]any) ([]any, error) { var result []any @@ -942,6 +949,9 @@ func sliceMapToSlice(m map[string]any) ([]any, error) { if err != nil { return nil, fmt.Errorf("array indexes must be integers: %w", err) } + if key < 0 { + return nil, fmt.Errorf("array indexes must not be negative: %d", key) + } keys = append(keys, key) } max := -1 @@ -950,6 +960,12 @@ func sliceMapToSlice(m map[string]any) ([]any, error) { max = k } } + // max+1 is the size of the slice this loop is about to build; bound the + // gap between what was actually supplied (len(m)) and that size so a + // single huge index can't force an outsized allocation. + if gap := max + 1 - len(m); gap > maxSliceMapToSliceGap { + return nil, fmt.Errorf("array index %d is too sparse relative to the %d supplied items", max, len(m)) + } for i := 0; i <= max; i++ { val, ok := m[strconv.Itoa(i)] if !ok { From 3be7b11efbaf6e9f33c2e07f4d0679e1d6e3ea77 Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Sat, 11 Jul 2026 12:53:08 +0300 Subject: [PATCH 4/8] openapi3: remove pre-OriginTree origin-stripping leftovers The OriginTree rewrite (#1142) moved origin tracking to a separate pass that extracts __origin__ before JSON conversion, so no unmarshaler sees it anymore (the 'if k == originKey' blocks were removed then; TestOrigin_* asserts __origin__ never appears as a map key). Three delete(map, originKey) calls -- Encoding, Webhooks, Server.Variables -- and the StringMap doc comment were missed by that cleanup. Remove the dead deletes and the now-unused originKey const, and correct the StringMap comment: it is a generic map[string]V, and no longer filters origin from the json representation. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BqJA1X6suZYtR3tRbX8sLj --- openapi3/media_type.go | 1 - openapi3/openapi3.go | 1 - openapi3/origin.go | 2 -- openapi3/server.go | 1 - openapi3/stringmap.go | 2 +- 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/openapi3/media_type.go b/openapi3/media_type.go index 842b320d6..e07026028 100644 --- a/openapi3/media_type.go +++ b/openapi3/media_type.go @@ -110,7 +110,6 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error { if len(x.Extensions) == 0 { x.Extensions = nil } - delete(x.Encoding, originKey) *mediaType = MediaType(x) return nil } diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go index 287327fa2..9671b913c 100644 --- a/openapi3/openapi3.go +++ b/openapi3/openapi3.go @@ -177,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 } diff --git a/openapi3/origin.go b/openapi3/origin.go index c6890e8ca..e55695a5b 100644 --- a/openapi3/origin.go +++ b/openapi3/origin.go @@ -8,8 +8,6 @@ import ( "github.com/oasdiff/yaml" ) -const originKey = "__origin__" - var originPtrType = reflect.TypeFor[*Origin]() // Origin contains the origin of a collection. diff --git a/openapi3/server.go b/openapi3/server.go index 181eace24..4dc4c5a85 100644 --- a/openapi3/server.go +++ b/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 } diff --git a/openapi3/stringmap.go b/openapi3/stringmap.go index f416899e7..7c21ec67f 100644 --- a/openapi3/stringmap.go +++ b/openapi3/stringmap.go @@ -2,7 +2,7 @@ package openapi3 import "encoding/json" -// StringMap is a map[string]string that ignores the origin in the underlying json representation. +// StringMap is a generic map[string]V with a custom UnmarshalJSON. type StringMap[V any] map[string]V // UnmarshalJSON sets StringMap to a copy of data. From 97cd1530d9ddd72d7725ad23de6f208edbcb0ce2 Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Sat, 11 Jul 2026 12:59:42 +0300 Subject: [PATCH 5/8] openapi3: simplify StringMap decode to standard json.Unmarshal The two-step decode (json -> map[string]any -> deepCast back into V, iterating sorted keys via componentNames) existed to strip __origin__ and control ordering before the OriginTree rewrite (#1142). Post-rewrite there is no __origin__ in the json and the result is an (unordered) map, so it is equivalent to a plain json.Unmarshal -- verified by the full suite. The round-trip also forced every value's numbers through float64; a direct decode avoids that precision risk. Decode straight into the map and drop the now-unused deepCast. componentNames stays (still used for deterministic validation ordering). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BqJA1X6suZYtR3tRbX8sLj --- openapi3/stringmap.go | 46 ++++++------------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/openapi3/stringmap.go b/openapi3/stringmap.go index 7c21ec67f..b95c3098d 100644 --- a/openapi3/stringmap.go +++ b/openapi3/stringmap.go @@ -11,54 +11,20 @@ func (stringMap *StringMap[V]) UnmarshalJSON(data []byte) (err error) { return } -// unmarshalStringMapP unmarshals given json into a map[string]*V +// unmarshalStringMapP unmarshals given json into a map[string]*V. func unmarshalStringMapP[V any](data []byte) (map[string]*V, error) { - var m map[string]any + var m map[string]*V 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 + return m, nil } -// unmarshalStringMap unmarshals given json into a map[string]V +// unmarshalStringMap unmarshals given json into a map[string]V. func unmarshalStringMap[V any](data []byte) (map[string]V, error) { - var m map[string]any + var m map[string]V 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 + return m, nil } From 473b3f164f58538de43d6860b9a1dc78525c3bf1 Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Sat, 11 Jul 2026 13:07:05 +0300 Subject: [PATCH 6/8] openapi3: remove the StringMap type StringMap[V] was a map[string]V whose only custom behavior was the two-step origin-aware decode, now gone (the previous commit made it a plain json.Unmarshal). Its two fields decode identically as plain maps -- the same json.Unmarshal target -- so use map[string]MappingRef (Discriminator.Mapping) and map[string]string (OAuthFlow.Scopes) directly and drop the type. The unmarshalStringMap/P helpers stay; other map types still call them. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BqJA1X6suZYtR3tRbX8sLj --- openapi3/discriminator.go | 2 +- openapi3/security_scheme.go | 2 +- openapi3/stringmap.go | 9 --------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go index d797701c3..64219d873 100644 --- a/openapi3/discriminator.go +++ b/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/openapi3/security_scheme.go b/openapi3/security_scheme.go index efe7f20bb..0a5b30e29 100644 --- a/openapi3/security_scheme.go +++ b/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. diff --git a/openapi3/stringmap.go b/openapi3/stringmap.go index b95c3098d..f1eeb3d2e 100644 --- a/openapi3/stringmap.go +++ b/openapi3/stringmap.go @@ -2,15 +2,6 @@ package openapi3 import "encoding/json" -// StringMap is a generic map[string]V with a custom UnmarshalJSON. -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]*V From 4bac3192059929c967250efe4fd0fbde3a074cc4 Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Sat, 11 Jul 2026 13:20:58 +0300 Subject: [PATCH 7/8] openapi3: drop the StringMap unmarshal helpers and their pass-through UnmarshalJSON With deepCast/componentNames gone from the decode, unmarshalStringMap and unmarshalStringMapP are plain json.Unmarshal wrappers, and the 13 named map types that called them from a custom UnmarshalJSON were pure pass-throughs. Remove those UnmarshalJSON methods so the types decode via default map unmarshalling (identical result), and delete the now-unused helpers (stringmap.go). None of the types have a MarshalJSON, so marshalling was already default -- the change stays symmetric. The existing TestOrigin_* assertions confirm __origin__ still never leaks into these maps. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BqJA1X6suZYtR3tRbX8sLj --- openapi3/callback.go | 6 ------ openapi3/content.go | 6 ------ openapi3/encoding.go | 6 ------ openapi3/example.go | 6 ------ openapi3/header.go | 6 ------ openapi3/link.go | 6 ------ openapi3/parameter.go | 6 ------ openapi3/request_body.go | 6 ------ openapi3/response.go | 6 ------ openapi3/schema.go | 6 ------ openapi3/security_requirements.go | 6 ------ openapi3/security_scheme.go | 6 ------ openapi3/server.go | 6 ------ openapi3/stringmap.go | 21 --------------------- 14 files changed, 99 deletions(-) delete mode 100644 openapi3/stringmap.go diff --git a/openapi3/callback.go b/openapi3/callback.go index 407f730a9..6350d7947 100644 --- a/openapi3/callback.go +++ b/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/openapi3/content.go b/openapi3/content.go index 8bf7921a5..ab79147f8 100644 --- a/openapi3/content.go +++ b/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/openapi3/encoding.go b/openapi3/encoding.go index fd9316ae2..1094488a4 100644 --- a/openapi3/encoding.go +++ b/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/openapi3/example.go b/openapi3/example.go index eeb4c6d4f..eeda1370e 100644 --- a/openapi3/example.go +++ b/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/openapi3/header.go b/openapi3/header.go index 466c8ea21..36b74dd87 100644 --- a/openapi3/header.go +++ b/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/openapi3/link.go b/openapi3/link.go index 60f78a029..eafeb42da 100644 --- a/openapi3/link.go +++ b/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/openapi3/parameter.go b/openapi3/parameter.go index b1010a61e..2b9962676 100644 --- a/openapi3/parameter.go +++ b/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/openapi3/request_body.go b/openapi3/request_body.go index e8c52a1cb..55c9b972f 100644 --- a/openapi3/request_body.go +++ b/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/openapi3/response.go b/openapi3/response.go index 87a52fe24..2cbf83375 100644 --- a/openapi3/response.go +++ b/openapi3/response.go @@ -217,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/openapi3/schema.go b/openapi3/schema.go index 2934971d2..4388a3981 100644 --- a/openapi3/schema.go +++ b/openapi3/schema.go @@ -3186,9 +3186,3 @@ func deepCopyJSONValue(v any) any { 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/openapi3/security_requirements.go b/openapi3/security_requirements.go index 36b6401b1..3f5bd9510 100644 --- a/openapi3/security_requirements.go +++ b/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/openapi3/security_scheme.go b/openapi3/security_scheme.go index 0a5b30e29..0905b61aa 100644 --- a/openapi3/security_scheme.go +++ b/openapi3/security_scheme.go @@ -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/openapi3/server.go b/openapi3/server.go index 4dc4c5a85..ca460d7c2 100644 --- a/openapi3/server.go +++ b/openapi3/server.go @@ -243,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/openapi3/stringmap.go b/openapi3/stringmap.go deleted file mode 100644 index f1eeb3d2e..000000000 --- a/openapi3/stringmap.go +++ /dev/null @@ -1,21 +0,0 @@ -package openapi3 - -import "encoding/json" - -// unmarshalStringMapP unmarshals given json into a map[string]*V. -func unmarshalStringMapP[V any](data []byte) (map[string]*V, error) { - var m map[string]*V - if err := json.Unmarshal(data, &m); err != nil { - return nil, err - } - return m, nil -} - -// unmarshalStringMap unmarshals given json into a map[string]V. -func unmarshalStringMap[V any](data []byte) (map[string]V, error) { - var m map[string]V - if err := json.Unmarshal(data, &m); err != nil { - return nil, err - } - return m, nil -} From 6e029e7bb1f0d2be531df94abe87b3f03387012c Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Sat, 11 Jul 2026 13:33:24 +0300 Subject: [PATCH 8/8] docs: regenerate API docs and note the StringMap removal in the changelog Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BqJA1X6suZYtR3tRbX8sLj --- .github/docs/openapi3.txt | 50 ++------------------------------------- README.md | 3 +++ 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt index 78a4f7e6c..622ae2ef0 100644 --- a/.github/docs/openapi3.txt +++ b/.github/docs/openapi3.txt @@ -407,9 +407,6 @@ func (m Callbacks) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Callbacks to a copy of data. - type CommentFieldFor31Plus struct{ ValidationError } func (e *CommentFieldFor31Plus) As(target any) bool @@ -534,9 +531,6 @@ func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content func (content Content) Get(mime string) *MediaType -func (content *Content) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Content to a copy of data. - func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Content does not comply with the OpenAPI spec. @@ -569,7 +563,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"` } Discriminator is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object @@ -714,9 +708,6 @@ func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding type Encodings map[string]*Encoding Encodings is a map of encoding objects keyed by field name. -func (encodings *Encodings) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Encodings to a copy of data. - type ExactlyOneFieldError struct { // Fields is the set of fields, exactly one of which must be set // (e.g. ["content", "schema"]). @@ -816,9 +807,6 @@ func (m Examples) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (examples *Examples) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Examples to a copy of data. - type ExamplesFieldFor31Plus struct{ ValidationError } func (e *ExamplesFieldFor31Plus) As(target any) bool @@ -1059,9 +1047,6 @@ func (m Headers) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (headers *Headers) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Headers to a copy of data. - type IDFieldFor31Plus struct{ ValidationError } func (e *IDFieldFor31Plus) As(target any) bool @@ -1310,9 +1295,6 @@ func (m Links) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (links *Links) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Links to a copy of data. - type Loader struct { // IsExternalRefsAllowed enables visiting other files. Enforced only when // ReadFromURIFunc is nil; a custom ReadFromURIFunc bypasses this flag and @@ -1529,7 +1511,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 } OAuthFlow is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object @@ -1881,9 +1863,6 @@ func (m ParametersMap) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets ParametersMap to a copy of data. - type PathItem struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -2129,9 +2108,6 @@ func (m RequestBodies) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets RequestBodies to a copy of data. - type RequestBody struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -2290,9 +2266,6 @@ func (m ResponseBodies) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets ResponseBodies to a copy of data. - type ResponseDescriptionRequired struct{ ValidationError } func (e *ResponseDescriptionRequired) As(target any) bool @@ -2925,9 +2898,6 @@ func (m Schemas) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (schemas *Schemas) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets Schemas to a copy of data. - type SectionValidationError struct { Section string Cause error @@ -2952,9 +2922,6 @@ func NewSecurityRequirement() SecurityRequirement func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement -func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets SecurityRequirement to a copy of data. - func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec. @@ -3098,9 +3065,6 @@ func (m SecuritySchemes) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable -func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets SecuritySchemes to a copy of data. - type SerializationMethod struct { Style string Explode bool @@ -3199,9 +3163,6 @@ type ServerVariables map[string]*ServerVariable https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object ServerVariables is a map of ServerVariable objects keyed by variable name. -func (serverVariables *ServerVariables) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets ServerVariables to a copy of data. - type Servers []*Server Servers is specified by OpenAPI/Swagger standard version 3. @@ -3241,13 +3202,6 @@ func NewRegexpFormatValidator(pattern string) StringFormatValidator NewRegexpFormatValidator creates a new FormatValidator that uses a regular expression to validate the value. -type StringMap[V any] map[string]V - StringMap is a map[string]string that ignores the origin in the underlying - json representation. - -func (stringMap *StringMap[V]) UnmarshalJSON(data []byte) (err error) - UnmarshalJSON sets StringMap to a copy of data. - type T struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` diff --git a/README.md b/README.md index b0ee339a2..5cf2fbbab 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,9 @@ for _, path := range doc.Paths.InMatchingOrder() { ## CHANGELOG: Sub-v1 breaking API changes +### v0.142.0 +* Removed the `openapi3.StringMap[V]` type (an internal helper for origin-aware map unmarshalling, obsolete since origin tracking moved to a separate `OriginTree` pass). `openapi3.Discriminator.Mapping` field type changed from `StringMap[MappingRef]` to `map[string]MappingRef`, and `openapi3.OAuthFlow.Scopes` from `StringMap[string]` to `map[string]string`. + ### v0.137.0 * Reinstated `openapi3.*Ptr(..)` funcs for Go 1.25