Permalink
Browse files

Refactor rescues into rescue_from for a better API.

  • Loading branch information...
1 parent 146934e commit 927b4c438abf5f07297e1dcb0113136bfa0ad486 Michael Bleigh committed May 27, 2011
Showing with 126 additions and 91 deletions.
  1. +2 −0 Gemfile.lock
  2. +1 −2 Rakefile
  3. +1 −0 grape.gemspec
  4. +26 −9 lib/grape/api.rb
  5. +10 −7 lib/grape/middleware/error.rb
  6. +24 −13 spec/grape/api_spec.rb
  7. +62 −60 spec/grape/middleware/exception_spec.rb
View
@@ -25,6 +25,7 @@ GEM
rack (>= 1.0.0)
rack-test (0.5.4)
rack (>= 1.0)
+ rake (0.9.0)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
@@ -46,5 +47,6 @@ DEPENDENCIES
json_pure
maruku
rack-test
+ rake
rspec (~> 2.6.0)
yard
View
@@ -2,8 +2,7 @@ require 'rubygems'
require 'bundler'
Bundler.setup :default, :test, :development
-require 'mg'
-MG.new('grape.gemspec')
+Bundler::GemHelper.install_tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
View
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'multi_json'
s.add_runtime_dependency 'multi_xml'
+ s.add_development_dependency 'rake'
s.add_development_dependency 'maruku'
s.add_development_dependency 'yard'
s.add_development_dependency 'rack-test'
View
@@ -90,14 +90,26 @@ def default_error_status(new_status = nil)
new_status ? set(:default_error_status, new_status) : settings[:default_error_status]
end
- # Specify whether to rescue all errors.
- def rescue_all_errors(new_value = true)
- set(:rescue_all_errors, new_value)
- end
-
- # Specify whether to include error backtrace with errors.
- def rescue_with_backtrace(new_value = true)
- set(:rescue_with_backtrace, new_value)
+ # Allows you to rescue certain exceptions that occur to return
+ # a grape error rather than raising all the way to the
+ # server level.
+ #
+ # @example Rescue from custom exceptions
+ # class ExampleAPI < Grape::API
+ # class CustomError < StandardError; end
+ #
+ # rescue_from CustomError
+ # end
+ #
+ # @overload rescue_from(*exception_classes, options = {})
+ # @param [Array] exception_classes A list of classes that you want to rescue, or
+ # the symbol :all to rescue from all exceptions.
+ # @param [Hash] options Options for the rescue usage.
+ # @option options [Boolean] :backtrace Include a backtrace in the rescue response.
+ def rescue_from(*args)
+ set(:rescue_options, args.pop) if args.last.is_a?(Hash)
+ set(:rescue_all, true) and return if args.include?(:all)
+ set(:rescued_errors, args)
end
# Add helper methods that will be accessible from any
@@ -238,7 +250,12 @@ def nest(*blocks, &block)
def build_endpoint(&block)
b = Rack::Builder.new
- 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]
+ b.use Grape::Middleware::Error,
+ :default_status => settings[:default_error_status] || 403,
+ :rescue_all => settings[:rescue_all],
+ :rescued_errors => settings[:rescued_errors],
+ :format => settings[:error_format] || :txt,
+ :rescue_options => settings[:rescue_options]
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
@@ -1,4 +1,5 @@
require 'grape/middleware/base'
+require 'multi_json'
module Grape
module Middleware
@@ -7,11 +8,13 @@ class Error < Base
def default_options
{
:default_status => 403, # default status returned on error
- :rescue => true, # true to rescue all exceptions
:default_message => "",
:format => :txt,
:formatters => {},
- :backtrace => false, # true to display backtrace
+
+ :rescue_all => false, # true to rescue all exceptions
+ :rescue_options => {:backtrace => false}, # true to display backtrace
+ :rescued_errors => []
}
end
@@ -38,15 +41,15 @@ def formatter_for(api_format)
def format_json(message, backtrace)
result = message.is_a?(Hash) ? message : { :error => message }
- if (options[:backtrace] && backtrace && ! backtrace.empty?)
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
result = result.merge({ :backtrace => backtrace })
end
- result.to_json
+ MultiJson.encode(result)
end
def format_txt(message, backtrace)
- result = message.is_a?(Hash) ? message.to_json : message
- if options[:backtrace] && backtrace && ! backtrace.empty?
+ result = message.is_a?(Hash) ? MultiJson.encode(message) : message
+ if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
result += "\r\n "
result += backtrace.join("\r\n ")
end
@@ -61,7 +64,7 @@ def call!(env)
return @app.call(@env)
})
rescue Exception => e
- raise unless options[:rescue]
+ raise unless options[:rescue_all] || (options[:rescued_errors] || []).include?(e.class)
error_response({ :message => e.message, :backtrace => e.backtrace })
end
View
@@ -472,46 +472,58 @@ def two
end
end
- describe ".rescue_all_errors" do
- it 'should not rescue all errors when rescue_all_errors is false' do
- subject.rescue_all_errors false
+ describe ".rescue_from" do
+ it 'should not rescue errors when rescue_from is not set' do
subject.get '/exception' do
raise "rain!"
end
lambda { get '/exception' }.should raise_error
end
- it 'should rescue all errors' do
- subject.rescue_all_errors true
+
+ it 'should rescue all errors if rescue_from :all is called' do
+ subject.rescue_from :all
subject.get '/exception' do
raise "rain!"
end
get '/exception'
last_response.status.should eql 403
end
+
+ it 'should rescue only certain errors if rescue_from is called with specific errors' do
+ subject.rescue_from ArgumentError
+ subject.get('/rescued'){ raise ArgumentError }
+ subject.get('/unrescued'){ raise "beefcake" }
+
+ get '/rescued'
+ last_response.status.should eql 403
+
+ lambda{ get '/unrescued' }.should raise_error
+ end
end
describe ".error_format" do
it 'should rescue all errors and return :txt' do
- subject.rescue_all_errors true
+ subject.rescue_from :all
subject.error_format :txt
subject.get '/exception' do
raise "rain!"
end
get '/exception'
last_response.body.should eql "rain!"
end
+
it 'should rescue all errros and return :txt with backtrace' do
- subject.rescue_all_errors true
+ subject.rescue_from :all, :backtrace => true
subject.error_format :txt
- subject.rescue_with_backtrace true
subject.get '/exception' do
raise "rain!"
end
get '/exception'
last_response.body.start_with?("rain!\r\n").should be_true
end
+
it 'should rescue all errors and return :json' do
- subject.rescue_all_errors true
+ subject.rescue_from :all
subject.error_format :json
subject.get '/exception' do
raise "rain!"
@@ -520,9 +532,8 @@ def two
last_response.body.should eql '{"error":"rain!"}'
end
it 'should rescue all errors and return :json with backtrace' do
- subject.rescue_all_errors true
+ subject.rescue_from :all, :backtrace => true
subject.error_format :json
- subject.rescue_with_backtrace true
subject.get '/exception' do
raise "rain!"
end
@@ -551,7 +562,7 @@ def two
describe ".default_error_status" do
it 'should allow setting default_error_status' do
- subject.rescue_all_errors true
+ subject.rescue_from :all
subject.default_error_status 200
subject.get '/exception' do
raise "rain!"
@@ -560,7 +571,7 @@ def two
last_response.status.should eql 200
end
it 'should have a default error status' do
- subject.rescue_all_errors true
+ subject.rescue_from :all
subject.get '/exception' do
raise "rain!"
end
@@ -38,80 +38,82 @@ def call(env)
def app
@app
end
-
- it 'should set the message appropriately' do
+
+ it 'should not trap errors by default' do
@app ||= Rack::Builder.app do
use Grape::Middleware::Error
run ExceptionApp
end
- get '/'
- last_response.body.should == "rain!"
+ lambda { get '/' }.should raise_error
end
- it 'should default to a 403 status' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error
- run ExceptionApp
+ context 'with rescue_all set to true' do
+ it 'should set the message appropriately' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error, :rescue_all => true
+ run ExceptionApp
+ end
+ get '/'
+ last_response.body.should == "rain!"
end
- get '/'
- last_response.status.should == 403
- end
- it 'should be possible to specify a different default status code' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error, :default_status => 500
- run ExceptionApp
- end
- get '/'
- last_response.status.should == 500
- end
-
- it 'should be possible to disable exception trapping' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error, :rescue => false
- run ExceptionApp
+ it 'should default to a 403 status' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error, :rescue_all => true
+ run ExceptionApp
+ end
+ get '/'
+ last_response.status.should == 403
end
- lambda { get '/' }.should raise_error
- end
- it 'should be possible to return errors in json format' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error, :format => :json
- run ExceptionApp
+ it 'should be possible to specify a different default status code' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error, :rescue_all => true, :default_status => 500
+ run ExceptionApp
+ end
+ get '/'
+ last_response.status.should == 500
end
- get '/'
- last_response.body.should == '{"error":"rain!"}'
- end
-
- it 'should be possible to return hash errors in json format' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error, :format => :json
- run ErrorHashApp
+
+ it 'should be possible to return errors in json format' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error, :rescue_all => true, :format => :json
+ run ExceptionApp
+ end
+ get '/'
+ last_response.body.should == '{"error":"rain!"}'
end
- get '/'
- last_response.body.should == '{"error":"rain!","detail":"missing widget"}'
- end
- it 'should be possible to specify a custom formatter' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error,
- :format => :custom,
- :formatters => {
- :custom => lambda { |message, backtrace| { :custom_formatter => message } }
- }
- run ExceptionApp
+ it 'should be possible to return hash errors in json format' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error, :rescue_all => true, :format => :json
+ run ErrorHashApp
+ end
+ get '/'
+ last_response.body.should == '{"error":"rain!","detail":"missing widget"}'
end
- get '/'
- last_response.body.should == '{:custom_formatter=>"rain!"}'
- end
-
- it 'should not trap regular error! codes' do
- @app ||= Rack::Builder.app do
- use Grape::Middleware::Error
- run AccessDeniedApp
+
+ it 'should be possible to specify a custom formatter' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error,
+ :rescue_all => true,
+ :format => :custom,
+ :formatters => {
+ :custom => lambda { |message, backtrace| { :custom_formatter => message } }
+ }
+ run ExceptionApp
+ end
+ get '/'
+ last_response.body.should == '{:custom_formatter=>"rain!"}'
end
- get '/'
- last_response.status.should == 401
+
+ it 'should not trap regular error! codes' do
+ @app ||= Rack::Builder.app do
+ use Grape::Middleware::Error
+ run AccessDeniedApp
+ end
+ get '/'
+ last_response.status.should == 401
+ end
end
-
end

0 comments on commit 927b4c4

Please sign in to comment.