Skip to content

Commit 146934e

Browse files
author
Michael Bleigh
committed
Merge branch 'master' of https://github.com/dblock/grape
2 parents b7deeb0 + 09e7078 commit 146934e

File tree

7 files changed

+332
-11
lines changed

7 files changed

+332
-11
lines changed

README.markdown

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,29 @@ 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+
## Raising Errors
66+
67+
You can raise errors explicitly.
68+
69+
error!("Access Denied", 401)
70+
71+
You can also return JSON formatted objects explicitly by raising error! and passing a hash instead of a message.
72+
73+
error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
74+
75+
## Exception Handling
76+
77+
By default Grape does not catch all unexpected exceptions. This means that the web server will handle the error and render the default error page as a result. It is possible to trap all exceptions by setting `rescue_all_errors true` instead. You may change the error format to JSON by using `error_format :json` and set the default error status to 200 with `default_error_status 200`. You may also include the complete backtrace of the exception with `rescue_with_backtrace true` either as text (for the :txt format) or as a :backtrace field in the json (for the :json format).
78+
79+
class Twitter::API < Grape::API
80+
rescue_all_errors true
81+
rescue_with_backtrace true
82+
error_format :json
83+
default_error_status 200
84+
85+
# api methods
86+
end
87+
6588
## Note on Patches/Pull Requests
6689

6790
* Fork the project.

lib/grape/api.rb

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def set(key, value)
5050
def prefix(prefix = nil)
5151
prefix ? set(:root_prefix, prefix) : settings[:root_prefix]
5252
end
53-
53+
5454
# Specify an API version.
5555
#
5656
# @example API with legacy support.
@@ -79,6 +79,27 @@ def default_format(new_format = nil)
7979
new_format ? set(:default_format, new_format.to_sym) : settings[:default_format]
8080
end
8181

82+
# Specify the format for error messages.
83+
# May be `:json` or `:txt` (default).
84+
def error_format(new_format = nil)
85+
new_format ? set(:error_format, new_format.to_sym) : settings[:error_format]
86+
end
87+
88+
# Specify the default status code for errors.
89+
def default_error_status(new_status = nil)
90+
new_status ? set(:default_error_status, new_status) : settings[:default_error_status]
91+
end
92+
93+
# Specify whether to rescue all errors.
94+
def rescue_all_errors(new_value = true)
95+
set(:rescue_all_errors, new_value)
96+
end
97+
98+
# Specify whether to include error backtrace with errors.
99+
def rescue_with_backtrace(new_value = true)
100+
set(:rescue_with_backtrace, new_value)
101+
end
102+
82103
# Add helper methods that will be accessible from any
83104
# endpoint within this namespace (and child namespaces).
84105
#
@@ -217,7 +238,7 @@ def nest(*blocks, &block)
217238

218239
def build_endpoint(&block)
219240
b = Rack::Builder.new
220-
b.use Grape::Middleware::Error
241+
b.use Grape::Middleware::Error, :default_status => settings[:default_error_status] || 403, :rescue => settings[:rescue_all_errors], :format => settings[:error_format] || :txt, :backtrace => settings[:rescue_with_backtrace]
221242
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
222243
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
223244
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version

lib/grape/middleware/error.rb

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,84 @@
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+
:backtrace => false, # true to display backtrace
15+
}
16+
end
17+
18+
FORMATTERS = {
19+
:json => :format_json,
20+
:txt => :format_txt,
21+
}
22+
23+
def formatters
24+
FORMATTERS.merge(options[:formatters])
25+
end
26+
27+
def formatter_for(api_format)
28+
spec = formatters[api_format]
29+
case spec
30+
when nil
31+
lambda { |obj| obj }
32+
when Symbol
33+
method(spec)
34+
else
35+
spec
36+
end
37+
end
38+
39+
def format_json(message, backtrace)
40+
result = message.is_a?(Hash) ? message : { :error => message }
41+
if (options[:backtrace] && backtrace && ! backtrace.empty?)
42+
result = result.merge({ :backtrace => backtrace })
43+
end
44+
result.to_json
45+
end
46+
47+
def format_txt(message, backtrace)
48+
result = message.is_a?(Hash) ? message.to_json : message
49+
if options[:backtrace] && backtrace && ! backtrace.empty?
50+
result += "\r\n "
51+
result += backtrace.join("\r\n ")
52+
end
53+
result
54+
end
55+
656
def call!(env)
757
@env = env
8-
result = catch :error do
9-
@app.call(@env)
58+
59+
begin
60+
error_response(catch(:error){
61+
return @app.call(@env)
62+
})
63+
rescue Exception => e
64+
raise unless options[:rescue]
65+
error_response({ :message => e.message, :backtrace => e.backtrace })
1066
end
1167

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

1670
def error_response(error = {})
17-
Rack::Response.new([(error[:message] || options[:default_message])], error[:status] || 403, error[:headers] || {}).finish
71+
status = error[:status] || options[:default_status]
72+
message = error[:message] || options[:default_message]
73+
headers = error[:headers] || {}
74+
backtrace = error[:backtrace] || []
75+
Rack::Response.new([format_message(message, backtrace, status)], status, headers).finish
76+
end
77+
78+
def format_message(message, backtrace, status)
79+
formatter = formatter_for(options[:format])
80+
throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
81+
formatter.call(message, backtrace)
1882
end
83+
1984
end
2085
end
2186
end

spec/grape/api_spec.rb

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def app; subject end
2020
last_response.status.should eql 404
2121
end
2222
end
23-
23+
2424
describe '.version' do
2525
it 'should set the API version' do
2626
subject.version 'v1'
@@ -471,4 +471,101 @@ def two
471471
last_response.status.should eql 200
472472
end
473473
end
474+
475+
describe ".rescue_all_errors" do
476+
it 'should not rescue all errors when rescue_all_errors is false' do
477+
subject.rescue_all_errors false
478+
subject.get '/exception' do
479+
raise "rain!"
480+
end
481+
lambda { get '/exception' }.should raise_error
482+
end
483+
it 'should rescue all errors' do
484+
subject.rescue_all_errors true
485+
subject.get '/exception' do
486+
raise "rain!"
487+
end
488+
get '/exception'
489+
last_response.status.should eql 403
490+
end
491+
end
492+
493+
describe ".error_format" do
494+
it 'should rescue all errors and return :txt' do
495+
subject.rescue_all_errors true
496+
subject.error_format :txt
497+
subject.get '/exception' do
498+
raise "rain!"
499+
end
500+
get '/exception'
501+
last_response.body.should eql "rain!"
502+
end
503+
it 'should rescue all errros and return :txt with backtrace' do
504+
subject.rescue_all_errors true
505+
subject.error_format :txt
506+
subject.rescue_with_backtrace true
507+
subject.get '/exception' do
508+
raise "rain!"
509+
end
510+
get '/exception'
511+
last_response.body.start_with?("rain!\r\n").should be_true
512+
end
513+
it 'should rescue all errors and return :json' do
514+
subject.rescue_all_errors true
515+
subject.error_format :json
516+
subject.get '/exception' do
517+
raise "rain!"
518+
end
519+
get '/exception'
520+
last_response.body.should eql '{"error":"rain!"}'
521+
end
522+
it 'should rescue all errors and return :json with backtrace' do
523+
subject.rescue_all_errors true
524+
subject.error_format :json
525+
subject.rescue_with_backtrace true
526+
subject.get '/exception' do
527+
raise "rain!"
528+
end
529+
get '/exception'
530+
json = JSON.parse(last_response.body)
531+
json["error"].should eql 'rain!'
532+
json["backtrace"].length.should > 0
533+
end
534+
it 'should rescue error! and return txt' do
535+
subject.error_format :txt
536+
subject.get '/error' do
537+
error!("Access Denied", 401)
538+
end
539+
get '/error'
540+
last_response.body.should eql "Access Denied"
541+
end
542+
it 'should rescue error! and return json' do
543+
subject.error_format :json
544+
subject.get '/error' do
545+
error!("Access Denied", 401)
546+
end
547+
get '/error'
548+
last_response.body.should eql '{"error":"Access Denied"}'
549+
end
550+
end
551+
552+
describe ".default_error_status" do
553+
it 'should allow setting default_error_status' do
554+
subject.rescue_all_errors true
555+
subject.default_error_status 200
556+
subject.get '/exception' do
557+
raise "rain!"
558+
end
559+
get '/exception'
560+
last_response.status.should eql 200
561+
end
562+
it 'should have a default error status' do
563+
subject.rescue_all_errors true
564+
subject.get '/exception' do
565+
raise "rain!"
566+
end
567+
get '/exception'
568+
last_response.status.should eql 403
569+
end
570+
end
474571
end

spec/grape/endpoint_spec.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ def app; subject end
7373
end
7474

7575
it 'should accept an object and render it in format' do
76-
pending
77-
7876
subject.get '/hey' do
7977
error!({'dude' => 'rad'}, 403)
8078
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

0 commit comments

Comments
 (0)