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).
* [#2806](https://github.com/ruby-grape/grape/pull/2806): Internalize versioning state behind `Grape::Util::InheritableSetting` accessors (`version`, `version_options`, `root_prefix`, `cascade` / `cascade_defined?`) - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

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

#### Versioning state is recorded through `InheritableSetting` accessors

The nearest-wins scalars written by the `version`, `prefix` and `cascade` DSL methods are now recorded and read through dedicated accessors on `Grape::Util::InheritableSetting` (`version` / `version=`, `version_options` / `version_options=`, `root_prefix` / `root_prefix=`, `cascade` / `cascade=`) instead of raw `namespace_inheritable` keys. `cascade` is presence-sensitive — an explicit `nil` is distinct from never-set — so the accessor family includes `cascade_defined?`, absorbing the `namespace_inheritable.key?(:cascade)` checks previously spelled out in `DSL::Routing#cascade` and `Grape::API::Instance#cascade?`. The keys' storage is unchanged for now, so `namespace_inheritable[:version]` and friends 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/api/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ def call(env)
# errors from reaching upstream. This is effectivelly done by unsetting
# X-Cascade. Default :cascade is true.
def cascade?
namespace_inheritable = self.class.inheritable_setting.namespace_inheritable
return namespace_inheritable[:cascade] if namespace_inheritable.key?(:cascade)
return namespace_inheritable[:version_options].cascade if namespace_inheritable[:version_options]
setting = self.class.inheritable_setting
return setting.cascade if setting.cascade_defined?
return setting.version_options.cascade if setting.version_options

true
end
Expand Down
16 changes: 8 additions & 8 deletions lib/grape/dsl/routing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def mounted(&block)
end

def cascade(value = nil)
return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable[:cascade].nil? : true if value.nil?
return inheritable_setting.cascade_defined? ? !inheritable_setting.cascade.nil? : true if value.nil?

inheritable_setting.namespace_inheritable[:cascade] = value
inheritable_setting.cascade = value
end

# Specify an API version.
Expand Down Expand Up @@ -72,23 +72,23 @@ def version(*args, using: :path, cascade: true, parameter: 'apiver', strict: fal

if block
within_namespace do
inheritable_setting.namespace_inheritable[:version] = requested_versions
inheritable_setting.namespace_inheritable[:version_options] = options
inheritable_setting.version = requested_versions
inheritable_setting.version_options = options
instance_eval(&block)
end
else
inheritable_setting.namespace_inheritable[:version] = requested_versions
inheritable_setting.namespace_inheritable[:version_options] = options
inheritable_setting.version = requested_versions
inheritable_setting.version_options = options
end

@versions&.last
end

# Define a root URL prefix for your entire API.
def prefix(prefix = nil)
return inheritable_setting.namespace_inheritable[:root_prefix] if prefix.nil?
return inheritable_setting.root_prefix if prefix.nil?

inheritable_setting.namespace_inheritable[:root_prefix] = prefix.to_s
inheritable_setting.root_prefix = prefix.to_s
end

# Create a scope without affecting the URL.
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/dsl/version_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module DSL
# Immutable value object holding the resolved options from
# +Grape::DSL::Routing#version+. Stored on the inheritable settings as
# +namespace_inheritable[:version_options]+ and read by internal call
# +Grape::Util::InheritableSetting#version_options+ and read by internal call
# sites (`Path`, `Endpoint`, `API::Instance#cascade?`,
# `Middleware::Versioner::Base`) via accessors.
#
Expand Down
12 changes: 6 additions & 6 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ def to_routes
params = config.params
path_settings = prepare_default_path_settings
forward_match = bare_rack_app?
version = prepare_version(inheritable_setting.namespace_inheritable[:version])
prefix = inheritable_setting.namespace_inheritable[:root_prefix]
version = prepare_version(inheritable_setting.version)
prefix = inheritable_setting.root_prefix
requirements = prepare_routes_requirements(config.requirements)
anchor = config.anchor
settings = inheritable_setting.route.except(:declared_params, :saved_validations)
Expand Down Expand Up @@ -351,12 +351,12 @@ def build_stack

stack.concat inheritable_setting.namespace_stackable[:middleware]

if inheritable_setting.namespace_inheritable[:version].present?
version_options = inheritable_setting.namespace_inheritable[:version_options]
if inheritable_setting.version.present?
version_options = inheritable_setting.version_options
stack.use Grape::Middleware::Versioner.using(version_options.using),
versions: inheritable_setting.namespace_inheritable[:version].flatten,
versions: inheritable_setting.version.flatten,
version_options:,
prefix: inheritable_setting.namespace_inheritable[:root_prefix],
prefix: inheritable_setting.root_prefix,
mount_path: inheritable_setting.namespace_stackable[:mount_path].first
end

Expand Down
47 changes: 47 additions & 0 deletions lib/grape/util/inheritable_setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,53 @@ def namespace_stackable_with_hash(key)
data.each_with_object({}) { |value, result| result.deep_merge!(value) }
end

# Versioning state recorded by the routing DSL (see DSL::Routing):
# +version+ holds the Array of version strings registered by the
# +version+ DSL method, +version_options+ its DSL::VersionOptions
# value object, and +root_prefix+ the path prefix set by +prefix+.
# Nearest-wins scalars with plain += writers; readers return nil when
# never set; the backing store is an internal detail.
def version
namespace_inheritable[:version]
end

def version=(versions)
namespace_inheritable[:version] = versions
end

def version_options
namespace_inheritable[:version_options]
end

def version_options=(options)
namespace_inheritable[:version_options] = options
end

def root_prefix
namespace_inheritable[:root_prefix]
end

def root_prefix=(prefix)
namespace_inheritable[:root_prefix] = prefix
end

# Cascade flag assigned by the +cascade+ DSL. An explicit nil is
# meaningful and distinct from never-set (the backing store is
# key-presence based), so #cascade_defined? reports whether any scope
# assigned it — Grape::API::Instance#cascade? falls back to the
# version options' cascade, then to true, when it was never assigned.
def cascade
namespace_inheritable[:cascade]
end

def cascade=(value)
namespace_inheritable[:cascade] = value
end

def cascade_defined?
namespace_inheritable.key?(:cascade)
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
6 changes: 3 additions & 3 deletions spec/grape/dsl/routing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ class << self
it 'sets a version for route' do
version = 'v1'
expect(subject.version(version)).to eq(version)
expect(subject.inheritable_setting.namespace_inheritable[:version]).to eq([version])
expect(subject.inheritable_setting.namespace_inheritable[:version_options]).to eq(Grape::DSL::VersionOptions.new)
expect(subject.inheritable_setting.version).to eq([version])
expect(subject.inheritable_setting.version_options).to eq(Grape::DSL::VersionOptions.new)
end
end

describe '.prefix' do
it 'sets a prefix for route' do
prefix = '/api'
subject.prefix prefix
expect(subject.inheritable_setting.namespace_inheritable[:root_prefix]).to eq(prefix)
expect(subject.inheritable_setting.root_prefix).to eq(prefix)
end
end

Expand Down
Loading