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 @@ -11,6 +11,7 @@
* [#3008](https://github.com/ruby-grape/grape/pull/3008): Add the grape-on-rack app as a live-server memory profiling harness - [@ericproulx](https://github.com/ericproulx).
* [#3025](https://github.com/ruby-grape/grape/pull/3025): Add `:md` and `:markdown` formatters that render an object's `to_markdown`, for APIs that declare a `text/markdown` content type - [@ericproulx](https://github.com/ericproulx).
* [#3029](https://github.com/ruby-grape/grape/pull/3029): Add a built-in `:jsonl` (JSON Lines) format that streams an Enumerator body - [@ericproulx](https://github.com/ericproulx).
* [#3040](https://github.com/ruby-grape/grape/pull/3040): Add the `raise_rescue_handler_errors` setting to let an exception raised inside a `rescue_from` block propagate - [@dduugg](https://github.com/dduugg).
* Your contribution here.

#### Fixes
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ Currently the configurable settings are:
* `lint`: Checks every response against Rack's specification, defaults to `false`. See [Linting](#linting).
* `warn_on_helper_overrides`: Warns when a helper method masks a `Grape::Endpoint` instance method, defaults to `false`.
* `raise_rendering_errors`: Lets an error response that cannot be rendered propagate out of the middleware stack instead of being answered with a failsafe `500`, defaults to `false`. See [When the error response itself cannot be rendered](#when-the-error-response-itself-cannot-be-rendered).
* `raise_rescue_handler_errors`: Lets an exception raised inside a `rescue_from` block propagate out of the middleware stack instead of being redispatched, defaults to `false`. See [Re-raising from inside a `rescue_from` block](#re-raising-from-inside-a-rescue_from-block).

To change a setting value make sure that at some point during load time the following code runs

Expand Down Expand Up @@ -2971,6 +2972,16 @@ When this handler is registered the framework hands the original exception to yo

A second raise inside the redispatched handler is not redispatched again — it goes straight to the framework's generic 500. This bounds the chain at one redispatch and prevents loops.

To have an exception raised inside a `rescue_from` block propagate out of the middleware stack instead, so that Rack middleware above Grape can act on it:

```ruby
Grape.configure do |config|
config.raise_rescue_handler_errors = true
end
```

With this on the exception is re-raised untouched: it is not redispatched, so `rescue_from :internal_grape_exceptions` never sees it and neither `rack.exception` nor `grape.exception` is set. A block that calls `error!` still renders as usual.

You can also rescue all exceptions with a code block and handle the Rack response at the lowest level.

```ruby
Expand Down
3 changes: 3 additions & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ module Grape
# Let an error response that cannot be rendered propagate out of the
# middleware stack instead of being answered with a failsafe 500.
setting :raise_rendering_errors, default: false
# Let an exception raised inside a `rescue_from` block propagate out of the
# middleware stack instead of being redispatched.
setting :raise_rescue_handler_errors, default: false

# The HTTP QUERY method (RFC 10008): a safe, idempotent request whose content
# carries the query. Rack has no constant for it yet, hence the literal.
Expand Down
2 changes: 2 additions & 0 deletions lib/grape/middleware/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ def run_rescue_handler(handler, error, endpoint, redispatched: false)
rescue Grape::Exceptions::Halt => e
e.response
rescue StandardError => e
raise if Grape.config.raise_rescue_handler_errors

return redispatch(e, endpoint, redispatched)
end

Expand Down
69 changes: 69 additions & 0 deletions spec/grape/middleware/error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,75 @@ def initialize
expect(response.body).to eq('Invalid response')
end
end

context 'and Grape.config.raise_rescue_handler_errors is set' do
around do |example|
Grape.config.raise_rescue_handler_errors = true
example.run
ensure
Grape.config.raise_rescue_handler_errors = false
end

context 'and the block re-raises the exception it was handed' do
let(:app) do
Class.new(Grape::API) do
format :txt

rescue_from(:internal_grape_exceptions) { error!('internal', 503) }
rescue_from(:all) { |e| raise e }

get('/') { raise ArgumentError, 'oops' }
end
end

it 'lets it propagate past rescue_from :internal_grape_exceptions' do
expect { response }.to raise_error(ArgumentError, 'oops')
expect(last_request.env).not_to include(Grape::Env::GRAPE_EXCEPTION, Grape::Env::RACK_EXCEPTION)
end
end

context 'and the re-raised exception has a registered rescue_from' do
let(:app) do
Class.new(Grape::API) do
format :txt

custom_error_class = Class.new(StandardError)
const_set(:CustomError, custom_error_class)

rescue_from custom_error_class do |e|
error!("custom-handled: #{e.message}", 422)
end

rescue_from :all do |e|
raise custom_error_class, "wrapped(`#{e.message}`)"
end

get('/') { raise ArgumentError, 'oops' }
end
end

it 'lets it propagate instead of redispatching it' do
expect { response }.to raise_error(app::CustomError, 'wrapped(`oops`)')
end
end

context 'and the block calls error!' do
let(:app) do
Class.new(Grape::API) do
format :txt

rescue_from(:all) { |e| error!("handled: #{e.message}", 422) }

get('/') { raise ArgumentError, 'oops' }
end
end

it 'still renders the error' do
expect(response.status).to eq(422)
expect(response.body).to eq('handled: oops')
end
end
end
end

describe '#error!' do
Expand Down
Loading