Skip to content

Commit 42107da

Browse files
Mason McLeaddblock
authored andcommitted
adding new rescue_from option to rescue all and still use built in Grape::Exception handling (#1398)
1 parent c059f98 commit 42107da

File tree

7 files changed

+78
-3
lines changed

7 files changed

+78
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
* Your contribution here.
55

6+
* [#1398](https://github.com/ruby-grape/grape/pull/1398): Added rescue_from :grape_exceptions option to allow Grape to use the built in Grape::Exception handing and use rescue :all behavior for everything else - [@mmclead](https://github.com/mmclead).
7+
68
#### Features
79

810
* [#1393](https://github.com/ruby-grape/grape/pull/1393): Middleware can be inserted before or after default Grape middleware - [@ridiculous](https://github.com/ridiculous).

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,17 @@ class Twitter::API < Grape::API
18531853
end
18541854
```
18551855

1856+
Grape can also rescue from all exceptions and still use the built-in exception handing.
1857+
This will give the same behavior as `rescue_from :all` with the addition that Grape will use the exception handling defined by all Exception classes that inherit Grape::Exceptions::Base.
1858+
1859+
The intent of this setting is to provide a simple way to cover the most common exceptions and return any unexpected exceptions in the API format.
1860+
1861+
```ruby
1862+
class Twitter::API < Grape::API
1863+
rescue_from :grape_exceptions
1864+
end
1865+
```
1866+
18561867
You can also rescue specific exceptions.
18571868

18581869
```ruby

lib/grape/dsl/request_response.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,13 @@ def rescue_from(*args, &block)
110110
end
111111
handler ||= extract_with(options)
112112

113-
if args.include?(:all)
113+
case
114+
when args.include?(:all)
114115
namespace_inheritable(:rescue_all, true)
115116
namespace_inheritable :all_rescue_handler, handler
117+
when args.include?(:grape_exceptions)
118+
namespace_inheritable(:rescue_all, true)
119+
namespace_inheritable(:rescue_grape_exceptions, true)
116120
else
117121
handler_type =
118122
case options[:rescue_subclasses]

lib/grape/endpoint.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ def build_stack(helpers)
274274
content_types: namespace_stackable_with_hash(:content_types),
275275
default_status: namespace_inheritable(:default_error_status),
276276
rescue_all: namespace_inheritable(:rescue_all),
277+
rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
277278
default_error_formatter: namespace_inheritable(:default_error_formatter),
278279
error_formatters: namespace_stackable_with_hash(:error_formatters),
279280
rescue_options: namespace_stackable_with_hash(:rescue_options) || {},

lib/grape/middleware/error.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ def default_options
1212
formatters: {},
1313
error_formatters: {},
1414
rescue_all: false, # true to rescue all exceptions
15+
rescue_grape_exceptions: false,
1516
rescue_subclasses: true, # rescue subclasses of exceptions listed
16-
rescue_options: { backtrace: false }, # true to display backtrace
17+
rescue_options: { backtrace: false }, # true to display backtrace, true to let Grape handle Grape::Exceptions
1718
rescue_handlers: {}, # rescue handler blocks
1819
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
1920
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
@@ -34,7 +35,7 @@ def call!(env)
3435
end)
3536
rescue StandardError => e
3637
is_rescuable = rescuable?(e.class)
37-
if e.is_a?(Grape::Exceptions::Base) && !is_rescuable
38+
if e.is_a?(Grape::Exceptions::Base) && (!is_rescuable || rescuable_by_grape?(e.class))
3839
handler = ->(arg) { error_response(arg) }
3940
else
4041
raise unless is_rescuable
@@ -63,6 +64,11 @@ def rescuable?(klass)
6364
rescue_all? || rescue_class_or_its_ancestor?(klass) || rescue_with_base_only_handler?(klass)
6465
end
6566

67+
def rescuable_by_grape?(klass)
68+
return false if klass == Grape::Exceptions::InvalidVersionHeader
69+
options[:rescue_grape_exceptions]
70+
end
71+
6672
def exec_handler(e, &handler)
6773
if handler.lambda? && handler.arity == 0
6874
instance_exec(&handler)

spec/grape/dsl/request_response_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ def self.imbue(key, value)
143143
end
144144
end
145145

146+
describe ':grape_exceptions' do
147+
it 'sets rescue all to true' do
148+
expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
149+
expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
150+
subject.rescue_from :grape_exceptions
151+
end
152+
153+
it 'sets rescue_grape_exceptions to true' do
154+
expect(subject).to receive(:namespace_inheritable).with(:rescue_all, true)
155+
expect(subject).to receive(:namespace_inheritable).with(:rescue_grape_exceptions, true)
156+
subject.rescue_from :grape_exceptions
157+
end
158+
end
159+
146160
describe 'list of exceptions is passed' do
147161
it 'sets hash of exceptions as rescue handlers' do
148162
expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => nil)

spec/grape/exceptions/body_parse_errors_spec.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,43 @@ def app
5252
end
5353
end
5454

55+
context 'api with rescue_from :grape_exceptions handler' do
56+
subject { Class.new(Grape::API) }
57+
before do
58+
subject.rescue_from :all do |_e|
59+
rack_response 'message was processed', 400
60+
end
61+
subject.rescue_from :grape_exceptions
62+
63+
subject.params do
64+
requires :beer
65+
end
66+
subject.post '/beer' do
67+
'beer received'
68+
end
69+
end
70+
71+
def app
72+
subject
73+
end
74+
75+
context 'with content_type json' do
76+
it 'returns body parsing error message' do
77+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'
78+
expect(last_response.status).to eq 400
79+
expect(last_response.body).to include 'message body does not match declared format'
80+
end
81+
end
82+
83+
context 'with content_type xml' do
84+
it 'returns body parsing error message' do
85+
post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'
86+
expect(last_response.status).to eq 400
87+
expect(last_response.body).to include 'message body does not match declared format'
88+
end
89+
end
90+
end
91+
5592
context 'api without a rescue handler' do
5693
subject { Class.new(Grape::API) }
5794
before do

0 commit comments

Comments
 (0)