diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1fe04c1..c7beb839b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * [#2786](https://github.com/ruby-grape/grape/pull/2786): Make route `requirements` and `anchor` explicit keyword arguments and first-class endpoint inputs instead of opaque `route_options` keys - [@ericproulx](https://github.com/ericproulx). * [#2788](https://github.com/ruby-grape/grape/pull/2788): Compare the base API instead of `to_s` when refreshing a mounted app - [@ericproulx](https://github.com/ericproulx). * [#2796](https://github.com/ruby-grape/grape/pull/2796): Remove `Grape::Util::ReverseStackableValues`, storing rescue handlers in plain per-scope hashes merged over `InheritableSetting#parent` - [@ericproulx](https://github.com/ericproulx). +* [#2804](https://github.com/ruby-grape/grape/pull/2804): Internalize contract key maps behind `Grape::Util::InheritableSetting` accessors (`contract_key_maps` / `add_contract_key_map`) - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/UPGRADING.md b/UPGRADING.md index 9bd07d5df..aebd69eae 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -82,6 +82,10 @@ A route's declared params were previously carried inside the `route_options` bag Like `params` above, a route's `requirements` and `anchor` were previously carried inside the `route_options` bag. They are now composed into their own endpoint inputs (`Grape::Endpoint::Options` gains `:requirements` and `:anchor` members, and `Grape::Endpoint.new` gains `requirements:` and `anchor:` keywords) and exposed only through the `route.requirements` and `route.anchor` readers. `route.options[:requirements]` and `route.options[:anchor]` now return `nil` — including for a mount's `anchor: false`. Nothing in Grape or grape-swagger read them that way, so this only affects code that reached into the options Hash for these keys directly. +#### Contract key maps are recorded through `InheritableSetting` accessors + +The Dry::Schema key maps registered by `contract` blocks are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`contract_key_maps` / `add_contract_key_map(key_map)`) instead of the raw `namespace_stackable[:contract_key_map]` key, following the same move made for rescue handlers. The key's storage is unchanged for now, so `namespace_stackable[:contract_key_map]` still returns the same values, but it should be considered internal. + ### Upgrading to >= 3.3 #### Minimum required Ruby is now 3.3 diff --git a/lib/grape/dsl/declared.rb b/lib/grape/dsl/declared.rb index 907dcf90f..2efc12a3a 100644 --- a/lib/grape/dsl/declared.rb +++ b/lib/grape/dsl/declared.rb @@ -22,7 +22,7 @@ def initialize(msg = '#declared is not available prior to parameter validation') def declared(passed_params, include_parent_namespaces: true, include_missing: true, evaluate_given: false, stringify: false) raise MethodNotYetAvailable unless before_filter_passed - contract_key_map = inheritable_setting.namespace_stackable[:contract_key_map] + contract_key_map = inheritable_setting.contract_key_maps handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map:) declared_params = include_parent_namespaces ? inheritable_setting.route[:declared_params] : (inheritable_setting.namespace_stackable[:declared_params].last || []) renamed_params = inheritable_setting.route[:renamed_params] || {} diff --git a/lib/grape/util/inheritable_setting.rb b/lib/grape/util/inheritable_setting.rb index 912b1f68d..91becdfee 100644 --- a/lib/grape/util/inheritable_setting.rb +++ b/lib/grape/util/inheritable_setting.rb @@ -106,6 +106,19 @@ def namespace_stackable_with_hash(key) data.each_with_object({}) { |value, result| result.deep_merge!(value) } end + # Dry::Schema key maps registered by +contract+ blocks (see + # Validations::ContractScope), one per contract, outermost scope first; + # +declared+ uses them to write coerced params back under their + # declared keys. Record them with #add_contract_key_map; the backing + # store is an internal detail. + def contract_key_maps + namespace_stackable[:contract_key_map] + end + + def add_contract_key_map(key_map) + namespace_stackable[:contract_key_map] = key_map + end + # Rescue-handler maps registered by +rescue_from+, keyed by exception # class and merged so a nested scope's handler wins. Record them with # #add_rescue_handlers; the backing store is an internal detail. diff --git a/lib/grape/validations/contract_scope.rb b/lib/grape/validations/contract_scope.rb index 07db7132d..95451a948 100644 --- a/lib/grape/validations/contract_scope.rb +++ b/lib/grape/validations/contract_scope.rb @@ -20,7 +20,7 @@ def initialize(api, contract = nil, &block) key_map = contract.key_map end - api.inheritable_setting.namespace_stackable[:contract_key_map] = key_map + api.inheritable_setting.add_contract_key_map(key_map) api.inheritable_setting.namespace_stackable[:validations] = Validators::ContractScopeValidator.new(schema: contract) end end