Skip to content

Commit 6203aaf

Browse files
committed
Extended rescue_from to take a block.
1 parent 7c4fe27 commit 6203aaf

File tree

5 files changed

+93
-10
lines changed

5 files changed

+93
-10
lines changed

README.markdown

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ The error format can be specified using `error_format`. Available formats are `:
9393
error_format :json
9494
end
9595

96+
You can rescue all exceptions with a code block. The `rack_response` wrapper automatically sets the default error code and content-type.
97+
98+
class Twitter::API < Grape::API
99+
rescue_from :all do |e|
100+
rack_response({ :message => "rescued from #{e.class.name}" })
101+
end
102+
end
103+
104+
You can also rescue specific exceptions with a code block and handle the Rack response at the lowest level.
105+
106+
class Twitter::API < Grape::API
107+
rescue_from :all do |e|
108+
Rack::Response.new([ e.message ], 500, { "Content-type" => "text/error" ).finish
109+
end
110+
end
111+
96112
## Note on Patches/Pull Requests
97113

98114
* Fork the project.

lib/grape/api.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,15 @@ def default_error_status(new_status = nil)
105105
# @overload rescue_from(*exception_classes, options = {})
106106
# @param [Array] exception_classes A list of classes that you want to rescue, or
107107
# the symbol :all to rescue from all exceptions.
108+
# @param [Block] block Execution block to handle the given exception.
108109
# @param [Hash] options Options for the rescue usage.
109110
# @option options [Boolean] :backtrace Include a backtrace in the rescue response.
110-
def rescue_from(*args)
111+
def rescue_from(*args, &block)
112+
if block_given?
113+
args.each do |arg|
114+
set(:rescue_handlers, { arg => block })
115+
end
116+
end
111117
set(:rescue_options, args.pop) if args.last.is_a?(Hash)
112118
set(:rescue_all, true) and return if args.include?(:all)
113119
set(:rescued_errors, args)
@@ -159,7 +165,7 @@ def http_basic(options = {}, &block)
159165

160166
def http_digest(options = {}, &block)
161167
options[:realm] ||= "API Authorization"
162-
options[:opaque] ||= "secret"
168+
options[:opaque] ||= "secret"
163169
auth :http_digest, options, &block
164170
end
165171

@@ -262,9 +268,10 @@ def build_endpoint(&block)
262268
:rescue_all => settings[:rescue_all],
263269
:rescued_errors => settings[:rescued_errors],
264270
:format => settings[:error_format] || :txt,
265-
:rescue_options => settings[:rescue_options]
266-
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
267-
b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
271+
:rescue_options => settings[:rescue_options],
272+
:rescue_handlers => settings[:rescue_handlers] || {}
273+
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
274+
b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
268275
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
269276
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
270277
b.use Grape::Middleware::Formatter, :default_format => default_format || :json

lib/grape/middleware/error.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ def default_options
1212
:default_message => "",
1313
:format => :txt,
1414
:formatters => {},
15-
1615
:rescue_all => false, # true to rescue all exceptions
17-
:rescue_options => {:backtrace => false}, # true to display backtrace
16+
:rescue_options => { :backtrace => false }, # true to display backtrace
17+
:rescue_handlers => {}, # rescue handler blocks
1818
:rescued_errors => []
1919
}
2020
end
@@ -45,20 +45,29 @@ def call!(env)
4545
})
4646
rescue Exception => e
4747
raise unless options[:rescue_all] || (options[:rescued_errors] || []).include?(e.class)
48-
error_response({ :message => e.message, :backtrace => e.backtrace })
48+
handler = options[:rescue_handlers][e.class] || options[:rescue_handlers][:all]
49+
handler.nil? ? handle_error(e) : self.instance_exec(e, &handler)
4950
end
5051

5152
end
5253

54+
def handle_error(e)
55+
error_response({ :message => e.message, :backtrace => e.backtrace })
56+
end
57+
5358
def error_response(error = {})
5459
status = error[:status] || options[:default_status]
5560
message = error[:message] || options[:default_message]
5661
headers = {'Content-Type' => content_type}
5762
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
5863
backtrace = error[:backtrace] || []
59-
Rack::Response.new([format_message(message, backtrace, status)], status, headers).finish
64+
rack_response(format_message(message, backtrace, status), status, headers)
6065
end
61-
66+
67+
def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
68+
Rack::Response.new([ message ], status, headers).finish
69+
end
70+
6271
def format_message(message, backtrace, status)
6372
formatter = formatter_for(options[:format])
6473
throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter

spec/grape/api_spec.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,4 +617,54 @@ def two
617617
last_response.status.should eql 403
618618
end
619619
end
620+
621+
describe ".rescue_from klass, block" do
622+
it 'should rescue Exception' do
623+
subject.rescue_from RuntimeError do |e|
624+
rack_response({ :message => "rescued from #{e.message}" }, 202)
625+
end
626+
subject.get '/exception' do
627+
raise "rain!"
628+
end
629+
get '/exception'
630+
last_response.status.should eql 202
631+
last_response.body.should == '{:message=>"rescued from rain!"}'
632+
end
633+
it 'should rescue an error via rescue_from :all' do
634+
class ConnectionError < RuntimeError; end
635+
subject.rescue_from :all do |e|
636+
rack_response({ :message => "rescued from #{e.class.name}" }, 500)
637+
end
638+
subject.get '/exception' do
639+
raise ConnectionError
640+
end
641+
get '/exception'
642+
last_response.status.should eql 500
643+
last_response.body.should == '{:message=>"rescued from ConnectionError"}'
644+
end
645+
it 'should rescue a specific error' do
646+
class ConnectionError < RuntimeError; end
647+
subject.rescue_from ConnectionError do |e|
648+
rack_response({ :message => "rescued from #{e.class.name}" }, 500)
649+
end
650+
subject.get '/exception' do
651+
raise ConnectionError
652+
end
653+
get '/exception'
654+
last_response.status.should eql 500
655+
last_response.body.should == '{:message=>"rescued from ConnectionError"}'
656+
end
657+
it 'should not rescue a different error' do
658+
class CommunicationError < RuntimeError; end
659+
subject.rescue_from RuntimeError do |e|
660+
rack_response({ :message => "rescued from #{e.class.name}" }, 500)
661+
end
662+
subject.get '/uncaught' do
663+
raise CommunicationError
664+
end
665+
lambda { get '/uncaught' }.should raise_error(CommunicationError)
666+
end
667+
end
668+
669+
620670
end

spec/grape/middleware/exception_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,5 +116,6 @@ def app
116116
get '/'
117117
last_response.status.should == 401
118118
end
119+
119120
end
120121
end

0 commit comments

Comments
 (0)