Skip to content

Commit e3f5257

Browse files
author
dB
committed
It's now possible to catch all exceptions thrown within APIs and to specify the error format.
1 parent 41dfcb2 commit e3f5257

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

README.markdown

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,31 @@ And would respond to the following routes:
6262

6363
Serialization takes place automatically. For more detailed usage information, please visit the [Grape Wiki](http://github.com/intridea/grape/wiki).
6464

65+
## Error Handling
66+
67+
The default behavior of Grape is to rescue all exceptions and to return a 403 status code with the exception's message in the response body.
68+
69+
It's possible to disable this behavior and pass exceptions further up the stack. This usually means displaying the web server's error page as a result.
70+
71+
class Twitter::API < Grape::API
72+
use Grape::Middleware::Error, :rescue => false
73+
...
74+
end
75+
76+
It's also common to change the error format to JSON.
77+
78+
class Twitter::API < Grape::API
79+
use Grape::Middleware::Error, :format => :json
80+
...
81+
end
82+
83+
Finally, you can specify your own error formatter. The following example returns a custom error message in the JSON format.
84+
85+
class Twitter::API < Grape::API
86+
use Grape::Middleware::Error, :format => :custom, :formatters = { lambda { |message| { :custom => "the error message was: #{message}" } }
87+
...
88+
end
89+
6590
## Note on Patches/Pull Requests
6691

6792
* Fork the project.

lib/grape/middleware/error.rb

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,73 @@
33
module Grape
44
module Middleware
55
class Error < Base
6+
7+
def default_options
8+
{
9+
:default_status => 403, # default status returned on error
10+
:rescue => true, # true to rescue all exceptions
11+
:default_message => "",
12+
:format => :txt,
13+
:formatters => {},
14+
}
15+
end
16+
17+
FORMATTERS = {
18+
:json => :format_json,
19+
:txt => :format_txt,
20+
}
21+
22+
def formatters
23+
FORMATTERS.merge(options[:formatters])
24+
end
25+
26+
def formatter_for(api_format)
27+
spec = formatters[api_format]
28+
case spec
29+
when nil
30+
lambda { |obj| obj }
31+
when Symbol
32+
method(spec)
33+
else
34+
spec
35+
end
36+
end
37+
38+
def format_json(message)
39+
{ :error => message }
40+
end
41+
42+
def format_txt(message)
43+
message
44+
end
45+
646
def call!(env)
747
@env = env
8-
result = catch :error do
9-
@app.call(@env)
48+
49+
begin
50+
error_response(catch(:error){
51+
return @app.call(@env)
52+
})
53+
rescue Exception => e
54+
raise unless options[:rescue]
55+
error_response({ :message => e.message })
1056
end
1157

12-
result ||= {}
13-
result.is_a?(Hash) ? error_response(result) : result
1458
end
1559

1660
def error_response(error = {})
17-
Rack::Response.new([(error[:message] || options[:default_message])], error[:status] || 403, error[:headers] || {}).finish
61+
status = error[:status] || options[:default_status]
62+
message = error[:message] || options[:default_message]
63+
headers = error[:headers] || {}
64+
Rack::Response.new([format_message(message, status)], status, headers).finish
65+
end
66+
67+
def format_message(message, status)
68+
formatter = formatter_for(options[:format])
69+
throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
70+
formatter.call(message)
1871
end
72+
1973
end
2074
end
2175
end

spec/grape/middleware/error_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ def app
4646
context 'with formatting' do
4747

4848
end
49-
end
49+
end
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
require 'spec_helper'
2+
3+
describe Grape::Middleware::Error do
4+
class ExceptionApp
5+
class << self
6+
def call(env)
7+
raise "rain!"
8+
end
9+
end
10+
end
11+
12+
def app
13+
@app
14+
end
15+
16+
it 'should set the message appropriately' do
17+
@app ||= Rack::Builder.app do
18+
use Grape::Middleware::Error
19+
run ExceptionApp
20+
end
21+
get '/'
22+
last_response.body.should == "rain!"
23+
end
24+
25+
it 'should default to a 403 status' do
26+
@app ||= Rack::Builder.app do
27+
use Grape::Middleware::Error
28+
run ExceptionApp
29+
end
30+
get '/'
31+
last_response.status.should == 403
32+
end
33+
34+
it 'should be possible to specify a different default status code' do
35+
@app ||= Rack::Builder.app do
36+
use Grape::Middleware::Error, :default_status => 500
37+
run ExceptionApp
38+
end
39+
get '/'
40+
last_response.status.should == 500
41+
end
42+
43+
it 'should be possible to disable exception trapping' do
44+
@app ||= Rack::Builder.app do
45+
use Grape::Middleware::Error, :rescue => false
46+
run ExceptionApp
47+
end
48+
lambda { get '/' }.should raise_error
49+
end
50+
51+
it 'should be possible to return errors in json format' do
52+
@app ||= Rack::Builder.app do
53+
use Grape::Middleware::Error, :format => :json
54+
run ExceptionApp
55+
end
56+
get '/'
57+
last_response.body.should == '{:error=>"rain!"}'
58+
end
59+
60+
it 'should be possible to specify a custom formatter' do
61+
@app ||= Rack::Builder.app do
62+
use Grape::Middleware::Error,
63+
:format => :custom,
64+
:formatters => {
65+
:custom => lambda { |message| { :custom_formatter => message } }
66+
}
67+
run ExceptionApp
68+
end
69+
get '/'
70+
last_response.body.should == '{:custom_formatter=>"rain!"}'
71+
end
72+
73+
end

0 commit comments

Comments
 (0)