From f4f811ff4b2509441c509444a857ba102ac40599 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sat, 18 Jul 2026 10:47:40 -0400 Subject: [PATCH] Internalize build_params_with and auth behind Grape::Util::InheritableSetting accessors Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 1 + UPGRADING.md | 4 ++++ lib/grape/dsl/parameters.rb | 2 +- lib/grape/dsl/routing.rb | 2 +- lib/grape/endpoint.rb | 4 ++-- lib/grape/middleware/auth/dsl.rb | 7 +++---- lib/grape/util/inheritable_setting.rb | 26 ++++++++++++++++++++++++++ 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1fe04c1..ee45a62b0 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). +* [#2808](https://github.com/ruby-grape/grape/pull/2808): Internalize `build_params_with` and `auth` behind `Grape::Util::InheritableSetting` accessors - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/UPGRADING.md b/UPGRADING.md index 9bd07d5df..dafec7265 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. +#### `build_params_with` and `auth` are recorded through `InheritableSetting` accessors + +The params-builder strategy written by `build_with` (both the API-level and the params-block DSL) and the authentication configuration written by the `auth` DSL are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`build_params_with` / `build_params_with=`, `auth` / `auth=`) instead of raw `namespace_inheritable` keys, completing the per-key `namespace_inheritable` cleanup. The keys' storage is unchanged for now, so `namespace_inheritable[:build_params_with]` and `namespace_inheritable[:auth]` still return the same values, but they should be considered internal. + ### Upgrading to >= 3.3 #### Minimum required Ruby is now 3.3 diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index babb10d18..b6e5446b2 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -29,7 +29,7 @@ module Parameters # end # end def build_with(build_with) - @api.inheritable_setting.namespace_inheritable[:build_params_with] = build_with + @api.inheritable_setting.build_params_with = build_with end # Include reusable params rules among current. diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index 20bbc7d07..bf7c32df0 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -102,7 +102,7 @@ def scope(_name = nil, &block) end def build_with(build_with) - inheritable_setting.namespace_inheritable[:build_params_with] = build_with + inheritable_setting.build_params_with = build_with end # Do not route HEAD requests to GET requests automatically. diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 9502cdc2c..5632302fa 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -284,7 +284,7 @@ def compile! @after_validations = stackable[:after_validations] @afters = stackable[:afters] @finallies = stackable[:finallies] - @build_params_with = inheritable_setting.namespace_inheritable[:build_params_with] + @build_params_with = inheritable_setting.build_params_with end def to_routes @@ -406,7 +406,7 @@ def build_helpers # inherited settings. Warn so this bypass isn't silent. def warn_unauthenticated_mounted_app return unless bare_rack_app? - return unless inheritable_setting.namespace_inheritable[:auth] + return unless inheritable_setting.auth warn "Grape: #{config.app} is mounted under an API that declares authentication, but authentication " \ 'middleware does not wrap mounted Rack applications. Requests to this mount are not authenticated by Grape.' diff --git a/lib/grape/middleware/auth/dsl.rb b/lib/grape/middleware/auth/dsl.rb index eba95211a..52829cd22 100644 --- a/lib/grape/middleware/auth/dsl.rb +++ b/lib/grape/middleware/auth/dsl.rb @@ -5,12 +5,11 @@ module Middleware module Auth module DSL def auth(type = nil, *legacy_options, **options, &block) - namespace_inheritable = inheritable_setting.namespace_inheritable - return namespace_inheritable[:auth] unless type + return inheritable_setting.auth unless type options = merge_legacy_auth_options(:auth, legacy_options, options) - namespace_inheritable[:auth] = { type: type.to_sym, proc: block }.merge!(options) - use Grape::Middleware::Auth::Base, namespace_inheritable[:auth] + inheritable_setting.auth = { type: type.to_sym, proc: block }.merge!(options) + use Grape::Middleware::Auth::Base, inheritable_setting.auth end # Add HTTP Basic authorization to the API. diff --git a/lib/grape/util/inheritable_setting.rb b/lib/grape/util/inheritable_setting.rb index 912b1f68d..23e07294e 100644 --- a/lib/grape/util/inheritable_setting.rb +++ b/lib/grape/util/inheritable_setting.rb @@ -106,6 +106,32 @@ def namespace_stackable_with_hash(key) data.each_with_object({}) { |value, result| result.deep_merge!(value) } end + # The params-builder strategy set by +build_with+ (both the + # API-level DSL::Routing#build_with and the params-block + # DSL::Parameters#build_with write it), consumed when the endpoint + # builds its Grape::Request. Nearest-wins scalar; nil when never set; + # the backing store is an internal detail. + def build_params_with + namespace_inheritable[:build_params_with] + end + + def build_params_with=(strategy) + namespace_inheritable[:build_params_with] = strategy + end + + # The authentication configuration Hash recorded by the +auth+ DSL + # (see Middleware::Auth::DSL): {type:, proc:, **options}. Nearest-wins + # scalar; nil when no authenticator is declared — Endpoint uses that + # to warn about unauthenticated bare Rack mounts; the backing store is + # an internal detail. + def auth + namespace_inheritable[:auth] + end + + def auth=(auth_options) + namespace_inheritable[:auth] = auth_options + 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.