Skip to content

Commit d79760e

Browse files
committed
Fix Grape exception handling
Allow custom Grape exception handlers when the built-in exception handling is enabled
1 parent a068470 commit d79760e

File tree

3 files changed

+80
-53
lines changed

3 files changed

+80
-53
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* [#1750](https://github.com/ruby-grape/grape/pull/1750): Fix a circular dependency warning due to router being loaded by API - [@salasrod](https://github.com/salasrod).
1414
* [#1752](https://github.com/ruby-grape/grape/pull/1752): Fix `include_missing` behavior for aliased parameters - [@jonasoberschweiber](https://github.com/jonasoberschweiber).
1515
* [#1754](https://github.com/ruby-grape/grape/pull/1754): Allow rescue from non-`StandardError` exceptions to use default error handling - [@jelkster](https://github.com/jelkster).
16+
* [#1756](https://github.com/ruby-grape/grape/pull/1756): Allow custom Grape exception handlers when the built-in exception handling is enabled - [@soylent](https://github.com/soylent).
1617
* Your contribution here.
1718

1819
### 1.0.2 (1/10/2018)

lib/grape/middleware/error.rb

+45-53
Original file line numberDiff line numberDiff line change
@@ -36,52 +36,15 @@ def call!(env)
3636
error_response(catch(:error) do
3737
return @app.call(@env)
3838
end)
39-
rescue Exception => e # rubocop:disable Lint/RescueException
40-
is_rescuable = rescuable?(e.class)
41-
if e.is_a?(Grape::Exceptions::Base) && (!is_rescuable || rescuable_by_grape?(e.class))
42-
handler = ->(arg) { error_response(arg) }
43-
else
44-
raise unless is_rescuable
45-
handler = find_handler(e.class)
46-
end
47-
48-
handler.nil? ? handle_error(e) : exec_handler(e, &handler)
49-
end
50-
end
51-
52-
def find_handler(klass)
53-
handler = options[:rescue_handlers].find(-> { [] }) { |error, _| klass <= error }[1]
54-
handler ||= options[:base_only_rescue_handlers][klass]
55-
handler ||= options[:all_rescue_handler]
56-
57-
if handler.instance_of?(Symbol)
58-
raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
59-
handler = self.class.instance_method(handler).bind(self)
60-
end
61-
62-
handler
63-
end
64-
65-
def rescuable?(klass)
66-
return false if klass == Grape::Exceptions::InvalidVersionHeader
67-
68-
if klass <= StandardError
69-
rescue_all? || rescue_class_or_its_ancestor?(klass) || rescue_with_base_only_handler?(klass)
70-
else
71-
rescue_class_or_its_ancestor?(klass) || rescue_with_base_only_handler?(klass)
72-
end
73-
end
74-
75-
def rescuable_by_grape?(klass)
76-
return false if klass == Grape::Exceptions::InvalidVersionHeader
77-
options[:rescue_grape_exceptions]
78-
end
79-
80-
def exec_handler(e, &handler)
81-
if handler.lambda? && handler.arity.zero?
82-
instance_exec(&handler)
83-
else
84-
instance_exec(e, &handler)
39+
rescue Exception => error # rubocop:disable Lint/RescueException
40+
handler =
41+
rescue_handler_for_base_only_class(error.class) ||
42+
rescue_handler_for_class_or_its_ancestor(error.class) ||
43+
rescue_handler_for_grape_exception(error.class) ||
44+
rescue_handler_for_any_class(error.class) ||
45+
raise
46+
47+
run_rescue_handler(handler, error)
8548
end
8649
end
8750

@@ -90,7 +53,7 @@ def error!(message, status = options[:default_status], headers = {}, backtrace =
9053
rack_response(format_message(message, backtrace, original_exception), status, headers)
9154
end
9255

93-
def handle_error(e)
56+
def default_rescue_handler(e)
9457
error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
9558
end
9659

@@ -122,16 +85,45 @@ def format_message(message, backtrace, original_exception = nil)
12285

12386
private
12487

125-
def rescue_all?
126-
options[:rescue_all]
88+
def rescue_handler_for_base_only_class(klass)
89+
error, handler = options[:base_only_rescue_handlers].find { |err, _handler| klass == err }
90+
91+
return unless error
92+
93+
handler || :default_rescue_handler
94+
end
95+
96+
def rescue_handler_for_class_or_its_ancestor(klass)
97+
error, handler = options[:rescue_handlers].find { |err, _handler| klass <= err }
98+
99+
return unless error
100+
101+
handler || :default_rescue_handler
127102
end
128103

129-
def rescue_class_or_its_ancestor?(klass)
130-
(options[:rescue_handlers] || []).any? { |error, _handler| klass <= error }
104+
def rescue_handler_for_grape_exception(klass)
105+
return unless klass <= Grape::Exceptions::Base
106+
return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
107+
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
108+
109+
:error_response
131110
end
132111

133-
def rescue_with_base_only_handler?(klass)
134-
(options[:base_only_rescue_handlers] || []).include?(klass)
112+
def rescue_handler_for_any_class(klass)
113+
return unless klass <= StandardError
114+
return unless options[:rescue_all] || options[:rescue_grape_exceptions]
115+
116+
options[:all_rescue_handler] || :default_rescue_handler
117+
end
118+
119+
def run_rescue_handler(handler, error)
120+
if handler.instance_of?(Symbol)
121+
raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
122+
123+
handler = public_method(handler)
124+
end
125+
126+
handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
135127
end
136128
end
137129
end

spec/grape/api_spec.rb

+34
Original file line numberDiff line numberDiff line change
@@ -1946,6 +1946,40 @@ class ChildError < ParentError; end
19461946
end
19471947
end
19481948

1949+
describe '.rescue_from :grape_exceptions' do
1950+
before do
1951+
subject.rescue_from :grape_exceptions
1952+
end
1953+
1954+
let(:grape_exception) do
1955+
Grape::Exceptions::Base.new(status: 400, message: 'Grape Error')
1956+
end
1957+
1958+
it 'rescues grape exceptions' do
1959+
exception = grape_exception
1960+
subject.get('/grape_exception') { raise exception }
1961+
1962+
get '/grape_exception'
1963+
1964+
expect(last_response.status).to eq(exception.status)
1965+
expect(last_response.body).to eq(exception.message)
1966+
end
1967+
1968+
it 'rescues grape exceptions with a user-defined handler' do
1969+
subject.rescue_from grape_exception.class do |_error|
1970+
rack_response('Redefined Error', 403)
1971+
end
1972+
1973+
exception = grape_exception
1974+
subject.get('/grape_exception') { raise exception }
1975+
1976+
get '/grape_exception'
1977+
1978+
expect(last_response.status).to eq(403)
1979+
expect(last_response.body).to eq('Redefined Error')
1980+
end
1981+
end
1982+
19491983
describe '.error_format' do
19501984
it 'rescues all errors and return :txt' do
19511985
subject.rescue_from :all

0 commit comments

Comments
 (0)