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
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).
* [#2803](https://github.com/ruby-grape/grape/pull/2803): Internalize namespace and mount-path registrations behind `Grape::Util::InheritableSetting` accessors (`namespaces` / `add_namespace` / `namespace_path` / `namespace_requirements`, `mount_path` / `add_mount_path`) - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

#### Fixes
Expand Down
10 changes: 10 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ 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.

#### Namespace and mount-path registrations are recorded through `InheritableSetting` accessors

The `Grape::Namespace` objects registered by the `namespace` DSL (and its `group` / `resource` / `resources` / `segment` aliases) and the mount path recorded by `mount` are now written and read through dedicated accessors on `Grape::Util::InheritableSetting` instead of raw `namespace_stackable` keys, following the same move made for rescue handlers:

* `namespaces` / `add_namespace(namespace)` replace `namespace_stackable[:namespace]` (not to be confused with the pre-existing `namespace` reader, which returns the `InheritableValues` store).
* `namespace_path` returns the normalized joined path prefix, absorbing the `Grape::Namespace.joined_space_path(...)` call previously spelled out at the read sites, and `namespace_requirements` returns the requirements declared by registered namespaces, absorbing the `filter_map(&:requirements)`.
* `mount_path` / `add_mount_path(path)` replace `namespace_stackable[:mount_path]`; the reader absorbs the outermost-wins convention (previously the `.first` at the read site in `Endpoint#build_stack`).

The keys' storage is unchanged for now, so `namespace_stackable[:namespace]` and `namespace_stackable[:mount_path]` still return the same values, but they should be considered internal.

### Upgrading to >= 3.3

#### Minimum required Ruby is now 3.3
Expand Down
6 changes: 3 additions & 3 deletions lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def mount(mounts, *opts)
# instantiated Grape::API::Instance (vs. a bare Rack app).
if app.is_a?(Grape::Mountable)
mount_path = Grape::Util::PathNormalizer.call(path)
app.top_level_setting.namespace_stackable[:mount_path] = mount_path
app.top_level_setting.add_mount_path(mount_path)

app.inherit_settings(inheritable_setting)

Expand Down Expand Up @@ -229,11 +229,11 @@ def route(methods, paths = ['/'], requirements: nil, anchor: true, **route_optio
# end
# end
def namespace(space = nil, requirements: nil, **options, &block)
return Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace]) unless space || block
return inheritable_setting.namespace_path unless space || block

within_namespace do
nest(block) do
inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, requirements:, **options) if space
inheritable_setting.add_namespace(Grape::Namespace.new(space, requirements:, **options)) if space
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def mount_in(router)
end

def namespace
@namespace ||= Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace])
@namespace ||= inheritable_setting.namespace_path
end

def call(env)
Expand Down Expand Up @@ -328,7 +328,7 @@ def prepare_default_path_settings
end

def prepare_routes_requirements(route_options_requirements)
namespace_requirements = inheritable_setting.namespace_stackable[:namespace].filter_map(&:requirements)
namespace_requirements = inheritable_setting.namespace_requirements
namespace_requirements << route_options_requirements if route_options_requirements.present?
namespace_requirements.reduce({}, :merge)
end
Expand Down Expand Up @@ -357,7 +357,7 @@ def build_stack
versions: inheritable_setting.namespace_inheritable[:version].flatten,
version_options:,
prefix: inheritable_setting.namespace_inheritable[:root_prefix],
mount_path: inheritable_setting.namespace_stackable[:mount_path].first
mount_path: inheritable_setting.mount_path
end

stack.use Grape::Middleware::Formatter,
Expand Down
5 changes: 3 additions & 2 deletions lib/grape/namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ def hash
[self.class, space, requirements, options].hash
end

# Join the namespaces from a list of settings to create a path prefix.
# @param settings [Array] list of Grape::Util::InheritableSettings.
# Join the namespaces from a list of Namespace objects to create a path
# prefix.
# @param settings [Array] list of Grape::Namespace objects.
def self.joined_space_path(settings)
JoinedSpaceCache[joined_space(settings)]
end
Expand Down
37 changes: 37 additions & 0 deletions lib/grape/util/inheritable_setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,43 @@ def namespace_stackable_with_hash(key)
data.each_with_object({}) { |value, result| result.deep_merge!(value) }
end

# Grape::Namespace objects registered by the +namespace+ DSL and its
# aliases (group, resource, resources, segment; see DSL::Routing),
# outermost scope first. Not to be confused with the #namespace values
# store. Record them with #add_namespace; the backing store is an
# internal detail.
def namespaces
namespace_stackable[:namespace]
end

def add_namespace(namespace)
namespace_stackable[:namespace] = namespace
end

# The normalized path prefix formed by joining every registered
# namespace's space (see Grape::Namespace.joined_space_path).
def namespace_path
Grape::Namespace.joined_space_path(namespaces)
end

# The param requirements declared by registered namespaces, outermost
# scope first.
def namespace_requirements
namespaces.filter_map(&:requirements)
end

# The path a Grape API is mounted under, recorded on the mounted API's
# top-level settings by +mount+ (see DSL::Routing). Reading returns the
# outermost mount path — nil when the API is not mounted; the backing
# store is an internal detail.
def mount_path
namespace_stackable[:mount_path].first
end

def add_mount_path(mount_path)
namespace_stackable[:mount_path] = mount_path
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.
Expand Down
Loading