Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Metrics/BlockLength:
- spec/**/*_spec.rb

Metrics/ClassLength:
Max: 320
Max: 340

Metrics/CyclomaticComplexity:
Max: 15
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
* [#2810](https://github.com/ruby-grape/grape/pull/2810): Make `Grape::Util::InheritableSetting`'s raw stores internal where the ecosystem allows: `namespace_inheritable` is now protected and `namespace_stackable_with_hash` private, while `namespace_stackable` stays public for grape-swagger but is deprecated via `Grape.deprecator`; `Router::Pattern::Path` reads a dedicated `path_settings` snapshot, and `mount_paths` exposes the full mount-path stack - [@ericproulx](https://github.com/ericproulx).
* [#2797](https://github.com/ruby-grape/grape/pull/2797): Internalize params state (validations, declared params, param documentation, named params) behind `Grape::Util::InheritableSetting` accessors, and remove the unused `api_class` / `point_in_time_copies` readers - [@ericproulx](https://github.com/ericproulx).
* [#2798](https://github.com/ruby-grape/grape/pull/2798): Internalize callback filters behind `Grape::Util::InheritableSetting` accessors (`callbacks` / `add_callback`) - [@ericproulx](https://github.com/ericproulx).
* [#2799](https://github.com/ruby-grape/grape/pull/2799): Internalize the remaining rescue configuration (`rescue_options`, meta-selector handlers and flags) behind `Grape::Util::InheritableSetting` accessors - [@ericproulx](https://github.com/ericproulx).
Expand Down
11 changes: 11 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ The flags flipped by `do_not_route_head!`, `do_not_route_options!`, `do_not_docu

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.

#### `InheritableSetting`'s raw stores are internal, except `namespace_stackable`

The per-key encapsulation documented in the sections above is now enforced where the ecosystem allows: `Grape::Util::InheritableSetting#namespace_inheritable` is protected and `#namespace_stackable_with_hash` is private — every `namespace_inheritable` key is reachable only through its dedicated accessor.

`#namespace_stackable` deliberately **remains public but is deprecated** — reading it warns through `Grape.deprecator` (Grape itself reads the underlying store directly and never warns): grape-swagger reads it directly (`swagger_documentation_adder.rb` reads `[:namespace]` and `[:mount_path]`, `token_owner_resolver.rb` reads `[:helpers]`, and `request_param_parsers/route.rb` walks the `StackableValues#inherited_values` chain to collect per-scope params). Migrate to the dedicated accessors (`namespaces`, `mount_paths`, `helpers`, …) for everything they cover; the reader will be removed or narrowed in a future major once grape-swagger has migrated. The `route`, `namespace` and `global` scratch stores (backing `route_setting` / `namespace_setting` / `global_setting`) also remain public and unchanged.

Two supporting changes:

* `Router::Pattern::Path` no longer receives a merged dump of both stores; `Endpoint#to_routes` passes the new `InheritableSetting#path_settings` snapshot — a `Grape::Util::InheritableSetting::PathSettings` value object (`Data`) — which carries exactly the six values `Path` reads (full mount-path stack, root prefix, format, raw content-types stack, version, version options) with always-present keys. `Path`'s `key?`-presence guards became nil-tolerant truthiness checks — equivalent under the snapshot. `Endpoint#prepare_default_path_settings` is removed.
* `InheritableSetting#mount_paths` is added, exposing the full mount-path stack (one entry per mount level, outermost first), complementing `#mount_path`, which returns only the outermost entry.

### Upgrading to >= 3.3

#### Minimum required Ruby is now 3.3
Expand Down
8 changes: 1 addition & 7 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def compile!
def to_routes
route_options = config.route_options
params = config.params
path_settings = prepare_default_path_settings
path_settings = inheritable_setting.path_settings
forward_match = bare_rack_app?
version = prepare_version(inheritable_setting.version)
prefix = inheritable_setting.root_prefix
Expand Down Expand Up @@ -313,12 +313,6 @@ def bare_rack_app?
config.app && !config.app.is_a?(Grape::Mountable)
end

def prepare_default_path_settings
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
namespace_stackable_hash.merge!(namespace_inheritable_hash)
end

def prepare_routes_requirements(route_options_requirements)
namespace_requirements = inheritable_setting.namespace_requirements
namespace_requirements << route_options_requirements if route_options_requirements.present?
Expand Down
23 changes: 10 additions & 13 deletions lib/grape/router/pattern/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ module Grape
class Router
class Pattern
# Assembles the path template a {Pattern} compiles into a matcher. It turns
# a raw path plus the API's inheritable settings (mount path, root prefix,
# path-versioning and format) into an +origin+ (the route prefix) and a
# +suffix+ (the format segment). {Pattern.build} is the entry point that
# wires this into pattern construction.
# a raw path plus the path-settings snapshot taken by
# {Grape::Util::InheritableSetting#path_settings} (mount paths, root
# prefix, path-versioning and format) into an +origin+ (the route prefix)
# and a +suffix+ (the format segment). {Pattern.build} is the entry point
# that wires this into pattern construction.
class Path
DEFAULT_FORMAT_SEGMENT = '(/.:format)'
NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'
Expand All @@ -27,16 +28,16 @@ def to_s
private

def build_suffix(raw_path, raw_namespace, settings)
return "(.#{settings[:format]})" if uses_specific_format?(settings)
return "(.#{settings.format})" if uses_specific_format?(settings)
return NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT if !uses_path_versioning?(settings) || valid_part?(raw_namespace) || valid_part?(raw_path)

DEFAULT_FORMAT_SEGMENT
end

def build_parts(raw_path, raw_namespace, settings)
parts = []
add_part(parts, settings[:mount_path])
add_part(parts, settings[:root_prefix])
add_part(parts, settings.mount_path)
add_part(parts, settings.root_prefix)
parts << VERSION_SEGMENT if uses_path_versioning?(settings)
add_part(parts, raw_namespace)
add_part(parts, raw_path)
Expand All @@ -52,15 +53,11 @@ def not_slash?(value)
end

def uses_specific_format?(settings)
return false unless settings.key?(:format) && settings.key?(:content_types)

settings[:format] && Array(settings[:content_types]).size == 1
settings.format && Array(settings.content_types).size == 1
end

def uses_path_versioning?(settings)
return false unless settings.key?(:version) && settings[:version_options]

settings[:version] && settings[:version_options].using == :path
settings.version && settings.version_options&.using == :path
end

def valid_part?(part)
Expand Down
Loading
Loading