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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
* [#2665](https://github.com/ruby-grape/grape/pull/2665): Pass `attrs` directly to `AttributesIterator` instead of `validator` - [@ericproulx](https://github.com/ericproulx).
* [#2657](https://github.com/ruby-grape/grape/pull/2657): Instantiate validators at definition time - [@ericproulx](https://github.com/ericproulx).
* [#2667](https://github.com/ruby-grape/grape/pull/2667): Skip instrumentation in run_validators when no validators present - [@ericproulx](https://github.com/ericproulx).
* [#2670](https://github.com/ruby-grape/grape/pull/2670): Better handling rack exceptions - [@ericproulx](https://github.com/ericproulx).
* [#2671](https://github.com/ruby-grape/grape/pull/2671): Use ruby 3.1 shorthand kwargs syntax - [@ericproulx](https://github.com/ericproulx).
* [#2672](https://github.com/ruby-grape/grape/pull/2672): Minor ruby optimizations - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

#### Fixes
Expand Down
18 changes: 18 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ Upgrading Grape

### Upgrading to >= 3.2

#### Rack parameter parsing errors now raise `Grape::Exceptions::RequestError`

Rack errors raised during parameter parsing (malformed multipart, parameter type conflicts, encoding issues, etc.) are now wrapped in `Grape::Exceptions::RequestError` instead of their previous specific exception classes (`Grape::Exceptions::EmptyMessageBody`, `Grape::Exceptions::TooManyMultipartFiles`, `Grape::Exceptions::TooDeepParameters`, `Grape::Exceptions::ConflictingTypes`, `Grape::Exceptions::InvalidParameters`). Those classes have been removed.

If you rescue any of these specific exceptions, update your rescue clauses to use `Grape::Exceptions::RequestError`:

```ruby
# Before
rescue Grape::Exceptions::ConflictingTypes, Grape::Exceptions::TooDeepParameters => e
# ...

# After
rescue Grape::Exceptions::RequestError => e
# ...
```

The error message is now forwarded directly from Rack rather than translated through Grape's locale system. On Rack 3, all Rack bad-request errors share the `Rack::BadRequest` marker module and are covered by a single rescue.

#### `endpoint_run_validators.grape` notification no longer fired when there are no validators

`ActiveSupport::Notifications` subscribers listening to `endpoint_run_validators.grape` will no longer receive an event for endpoints that have no validators. If you rely on this notification to measure every request, subscribe to `endpoint_run.grape` instead, which always fires.
Expand Down
18 changes: 18 additions & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ module Grape
Rack::OPTIONS
].freeze

# Rack errors that should be rescued and wrapped as Grape::Exceptions::RequestError.
# Rack 3 introduced Rack::BadRequest as a marker module included by all bad request
# exception classes, allowing a single rescue entry to cover them all.
# On Rack 2, these errors are raised as individual exception classes.
RACK_ERRORS =
if defined?(Rack::BadRequest)
[EOFError, Rack::BadRequest]
else
[
EOFError,
Rack::Multipart::MultipartPartLimitError,
Rack::Multipart::MultipartTotalPartLimitError,
Rack::Utils::ParameterTypeError,
Rack::Utils::InvalidParameterError,
Rack::QueryParser::ParamsTooDeepError
]
end.freeze

def self.deprecator
@deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape')
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def initial_setup(base_instance_parent)
def override_all_methods!
(base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
define_singleton_method(method_override) do |*args, **kwargs, &block|
add_setup(method: method_override, args: args, kwargs: kwargs, block: block)
add_setup(method: method_override, args:, kwargs:, block:)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/api/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def collect_route_config_per_pattern(all_routes)
allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods
last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)

greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header: allow_header)
greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header:)
@router.associate_routes(greedy_route)
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/grape/declared_params_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def initialize(include_missing: true, evaluate_given: false, stringify: false, c
def call(passed_params, declared_params, route_params, renamed_params)
recursive_declared(
passed_params,
declared_params: declared_params,
route_params: route_params,
renamed_params: renamed_params
declared_params:,
route_params:,
renamed_params:
)
end

Expand Down
10 changes: 5 additions & 5 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ def error!(message, status = nil, additional_headers = nil, backtrace = nil, ori
status = self.status(status || inheritable_setting.namespace_inheritable[:default_error_status])
headers = additional_headers.present? ? header.merge(additional_headers) : header
throw :error,
message: message,
status: status,
headers: headers,
backtrace: backtrace,
original_exception: original_exception
message:,
status:,
headers:,
backtrace:,
original_exception:
end

# Redirect to a new url.
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/dsl/parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def optional(*attrs, **opts, &block)
# @param (see #requires)
# @option (see #requires)
def with(**opts, &)
new_group_attrs = [@group, opts].compact.reduce(&:deep_merge)
new_group_attrs = @group&.deep_merge(opts) || opts
new_group_scope(new_group_attrs, &)
end

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 @@ -136,8 +136,8 @@ def mount(mounts, *opts)
endpoints << Grape::Endpoint.new(
in_setting,
method: :any,
path: path,
app: app,
path:,
app:,
route_options: { anchor: false },
forward_match: !app.respond_to?(:inheritable_setting),
for: self
Expand Down Expand Up @@ -167,7 +167,7 @@ def route(methods, paths = ['/'], route_options = {}, &)

new_endpoint = Grape::Endpoint.new(
inheritable_setting,
method: method,
method:,
path: paths,
for: self,
route_options: all_route_options,
Expand Down
14 changes: 7 additions & 7 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def inspect
protected

def run
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env:) do
@request = Grape::Request.new(env, build_params_with: inheritable_setting.namespace_inheritable[:build_params_with])
begin
self.class.run_before_each(self)
Expand Down Expand Up @@ -229,7 +229,7 @@ def run_validators(request:)
def run_filters(filters, type = :other)
return unless filters

ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters:, type:) do
filters.each { |filter| instance_eval(&filter) }
end
end
Expand Down Expand Up @@ -316,15 +316,15 @@ def build_stack
stack.use Rack::Head
stack.use Rack::Lint if lint?
stack.use Grape::Middleware::Error,
format: format,
content_types: content_types,
format:,
content_types:,
default_status: inheritable_setting.namespace_inheritable[:default_error_status],
rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],
rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],
default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],
error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),
rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),
rescue_handlers: rescue_handlers,
rescue_handlers:,
base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),
all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],
grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]
Expand All @@ -340,9 +340,9 @@ def build_stack
end

stack.use Grape::Middleware::Formatter,
format: format,
format:,
default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,
content_types: content_types,
content_types:,
formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),
parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)

Expand Down
11 changes: 0 additions & 11 deletions lib/grape/exceptions/conflicting_types.rb

This file was deleted.

11 changes: 0 additions & 11 deletions lib/grape/exceptions/empty_message_body.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_accept_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class InvalidAcceptHeader < Base
def initialize(message, headers)
super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers: headers)
super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers:)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class InvalidFormatter < Base
def initialize(klass, to_format)
super(message: compose_message(:invalid_formatter, klass: klass, to_format: to_format))
super(message: compose_message(:invalid_formatter, klass:, to_format:))
end
end
end
Expand Down
11 changes: 0 additions & 11 deletions lib/grape/exceptions/invalid_parameters.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_version_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class InvalidVersionHeader < Base
def initialize(message, headers)
super(message: compose_message(:invalid_version_header, message: message), status: 406, headers: headers)
super(message: compose_message(:invalid_version_header, message: message), status: 406, headers:)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/invalid_versioner_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class InvalidVersionerOption < Base
def initialize(strategy)
super(message: compose_message(:invalid_versioner_option, strategy: strategy))
super(message: compose_message(:invalid_versioner_option, strategy:))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/method_not_allowed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class MethodNotAllowed < Base
def initialize(headers)
super(message: '405 Not Allowed', status: 405, headers: headers)
super(message: '405 Not Allowed', status: 405, headers:)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/missing_mime_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class MissingMimeType < Base
def initialize(new_format)
super(message: compose_message(:missing_mime_type, new_format: new_format))
super(message: compose_message(:missing_mime_type, new_format:))
end
end
end
Expand Down
11 changes: 11 additions & 0 deletions lib/grape/exceptions/request_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Grape
module Exceptions
class RequestError < Base
def initialize(status: 400)
super(message: $ERROR_INFO&.message, status:)
end
end
end
end
11 changes: 0 additions & 11 deletions lib/grape/exceptions/too_deep_parameters.rb

This file was deleted.

11 changes: 0 additions & 11 deletions lib/grape/exceptions/too_many_multipart_files.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_auth_strategy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class UnknownAuthStrategy < Base
def initialize(strategy:)
super(message: compose_message(:unknown_auth_strategy, strategy: strategy))
super(message: compose_message(:unknown_auth_strategy, strategy:))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_parameter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class UnknownParameter < Base
def initialize(param)
super(message: compose_message(:unknown_parameter, param: param))
super(message: compose_message(:unknown_parameter, param:))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_params_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class UnknownParamsBuilder < Base
def initialize(params_builder_type)
super(message: compose_message(:unknown_params_builder, params_builder_type: params_builder_type))
super(message: compose_message(:unknown_params_builder, params_builder_type:))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/unknown_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Grape
module Exceptions
class UnknownValidator < Base
def initialize(validator_type)
super(message: compose_message(:unknown_validator, validator_type: validator_type))
super(message: compose_message(:unknown_validator, validator_type:))
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/grape/exceptions/validation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(params:, message: nil, status: nil, headers: nil)
message = translate_message(message)
end

super(status: status, message: message, headers: headers)
super(status:, message:, headers:)
end

# Remove all the unnecessary stuff from Grape::Exceptions::Base like status
Expand Down
5 changes: 0 additions & 5 deletions lib/grape/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ en:
at_least_one: 'are missing, at least one parameter must be provided'
blank: 'is empty'
coerce: 'is invalid'
conflicting_types: 'query params contains conflicting types'
empty_message_body: 'empty message body supplied with %{body_format} content-type'
exactly_one: 'are missing, exactly one parameter must be provided'
except_values: 'has a value not allowed'
incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
Expand All @@ -20,7 +18,6 @@ en:
invalid_message_body:
problem: 'message body does not match declared format'
resolution: 'when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request''s ''body'' '
invalid_parameters: 'query params contains invalid format or byte sequence'
invalid_response: 'Invalid response'
invalid_version_header:
problem: 'invalid version header'
Expand Down Expand Up @@ -48,8 +45,6 @@ en:
presence: 'is missing'
regexp: 'is invalid'
same_as: 'is not the same as %{parameter}'
too_deep_parameters: 'query params are recursively nested over the specified limit (%{limit})'
too_many_multipart_files: 'the number of uploaded files exceeded the system''s configured limit (%{limit})'
unknown_auth_strategy: 'unknown auth strategy: %{strategy}'
unknown_options: 'unknown options: %{options}'
unknown_parameter: 'unknown parameter: %{param}'
Expand Down
6 changes: 2 additions & 4 deletions lib/grape/middleware/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@ def content_type

def query_params
rack_request.GET
rescue Rack::QueryParser::ParamsTooDeepError
raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
rescue Rack::Utils::ParameterTypeError
raise Grape::Exceptions::ConflictingTypes
rescue *Grape::RACK_ERRORS
raise Grape::Exceptions::RequestError
end

private
Expand Down
Loading
Loading