Ruby Grape rescue_from :all not rescuing all errors - ruby

My Grape app has several error handlers, including lastly:
rescue_from :all, backtrace: true do |e|
message = { errors: { all: e.message } }
rack_response(format_message(message, e.backtrace), 500 )
end
But this is not rescuing at least errors that Grape processes with
throw :error
internally. How do I rescue those errors? The particular errors noted are "The requested format 'txt' is not supported" and "Not Found: some_path". These errors occur when the format extension is missing or only a '.' is supplied, respectively.

You don't rescue the thrown conditions. They will go straight to the error handler, because rescue is for raised errors, not thrown conditions. throw does not create exactly the same objects as raise, and cannot be processed in the same way.
You could however, format the error message using an error_formatter:
module CustomErrorFormatter
def self.call message, backtrace, options, env
{ errors: { all: message.to_s } }.to_json
end
end
And in the main app:
error_formatter :json, CustomErrorFormatter

Related

How to use check something before an error is raised

I have the following ruby code
class Gateway
...
def post
begin
...
raise ClientError if state == :open
rescue ClientError => e
Log.add("error")
raise
end
end
end
On RSpec, how can I check that when ClientError is raised Log.add is called?
I have tried different things but I always get the error raised.
Thanks
You can probably do something like this (the initialize step might need to look bit different, depending on how you need to set the state to :open):
describe 'Gateway#post' do
let(:gateway) { Gateway.new(state: :open) }
before { allow(Log).to receive(:add) }
it 'raises an excpetion' do
expect { gateway.post }.to raise_error(ClientError)
expect(Log).to have_received(:add).with('error')
end
end
Something like this should work:
describe '#post' do
context 'with state :open' do
let(:gateway) { Gateway.new(state: :open) }
it 'logs the error' do
expect(Log).to receive(:add).with('error')
gateway.post rescue nil
end
it 're-raises the error' do
expect { gateway.post }.to raise_error(ClientError)
end
end
end
In the first example, rescue nil ensures that your spec is not failing because of the raised error (it silently rescues it). The second example checks that the error is being re-raised.

Can you rescue from specific errors with messages in ruby?

I'm trying to understand how errors propogate between classes in Ruby. I have this so far:
class User
def charge
puts "charging order soon"
raise RuntimeError.new("This is a runtime error")
rescue ArgumentError
puts "should never gets here"
end
end
class Runner
def run
begin
User.new.charge
rescue RuntimeError => e
puts e.message
end
end
end
Runner.new.run
When I run this, I get this which seems right:
$ ruby errors.rb
charging order soon
This is a runtime error
Inside runner, can I rescue from the RuntimeError with a specific message? If I have multiple RuntimeErrors being raised around my application, is there any way for the Runner's rescue clause to be raised only for RuntimeErrors with specific messages?
See https://stackoverflow.com/a/23771227/2981429
If you call raise inside a rescue block, the last raised exception will be re-raised.
In your exception block, you can check the message and choose to re-raise or not:
begin
User.new.charge
rescue RuntimeError => e
case e.message
when "This is a runtime error"
# put your handler code here
else
raise # re-raise the last exception
end
end
However if it's your goal to solely rescue errors that you yourself raise manually, then it's probably easier to define a custom error class instead:
class MyError < StandardError; end
Then instead of raise RuntimeError.new("message") use raise MyError.new("message"), and rescue it normally:
begin
User.new.charge
rescue MyError => e
# handler
end
This way you don't have to worry about your rescues interfering with the built-in exceptions.

Sinatra Error Handling in Ruby

I have a simple Sinatra rest and I am having trouble trapping an error. I also admit I am fairly new to Ruby and Sinatra.
When I raise and error in the post endpoint I want to report the incoming document. I need to either 1) handle the error within the post result (where I have access to #incoming) or 2) pass the incoming document to the error and report it there.
What is a better option, option 1 or option 2?
If I stay with option 1, how do I prevent error from picking up the error (as it seems to be doing now)
If I go to option 2, how do I pass incoming to error?
Below is a sample of my code:
post ('/result') do
begin
#incoming = JSON.parse(request.body.read)
//do something that causes an error
rescue
e = env['sinatra.error']
url = request.url
ip = request.ip
#actlogpassblock = { :message => e.message,
:path => url,
:ip => ip,
:timestamp => Time.new,
:type => "500",
:sub => "RES",
:payload => #incoming
}
action_log.insert(#actlogpassblock)
status 500
end
end
error do
status 500
e = env['sinatra.error']
url = request.url
ip = request.ip
backtrace = "Application error\n#{e}\n#{e.backtrace.join("\n")}"
#actlogpassblock = { :message => e.message,
:path => url,
:ip => ip,
:timestamp => Time.new,
:type => "500",
:backtrace => backtrace
}
action_log.insert(#actlogpassblock)
{:result => 'Ah Shucks! Something went wrong'}.to_json
end
If I stay with option 1, how do I prevent error from picking up the
error (as it seems to be doing now)
According to the docs:
The error handler is invoked any time an exception is raised from a
route block...
However, that only applies to uncaught exceptions. Try this:
require 'sinatra'
set :show_exceptions, false
get '/' do
begin
raise ZeroDivisionError
rescue ZeroDivisionError
"rescue clause"
end
end
error do
"sinatra error handler"
end
Then try this:
get '/' do
raise ZeroDivisionError
end
error do
"sinatra error handler"
end
Also, you can tailor the error handler to only catch certain exceptions thereby avoiding some exceptions, e.g.
error IndexError do ...
or
error MyCustomException do ...
or
error 400..510 do
But for the catch all version:
error do
you can't stop that from executing when an uncaught exception occurs in a route block...unless:
The error handlers will only be invoked, however, if both the Sinatra
:raise_errors and :show_exceptions configuration options have been set
to false...
:raise_errors defaults to true in the "test" environment and to false
on other environments.
:show_exceptions value defaults to true in the
"development" environment and to false on other environments
The author of Sintra has said: "This [behavior] is
intentional. The idea is that error blocks will hide the issue and you
usually don't want to do this in development mode.
https://github.com/sul-dlss/sdr-services-app/blob/master/Sinatra-error-handling.md
If I go to option 2, how do I pass incoming to error?
An #variable that is created inside a route block can be seen inside an error block. Try this:
require 'sinatra'
set :show_exceptions, false
get '/' do
#incoming = "hello world" #create #variable
raise ZeroDivisionError
end
error ZeroDivisionError do
#incoming #retrieve #variable
end
After entering the url http://localhost:4567 in your browser, you should see "hello world" on the returned web page.
The reason that works is because an #variable attaches itself to whatever object is self at the time the #variable is created; likewise when an #variable is summoned, it is retrieved from whatever object is self at that time. When Sinatra executes either the route block or the error block it sets self to the same object.
Option 2 seems nice because it separates the error handling code from the application code.

Reraise (same exception) after catching an exception in Ruby

I am trying to improve my Ruby skills by catching exceptions. I want to know if it is common to reraise the same kind of exception when you have several method calls. So, would the following code make sense? Is it ok to reraise the same kind of exception, or should I not catch it on the process method?
class Logo
def process
begin
#processed_logo = LogoProcessor::create_image(self.src)
rescue CustomException
raise CustomException
end
end
end
module LogoProcessor
def self.create_image
raise CustomException if some_condition
end
end
Sometimes we just want to know an error happened, without having to actually handle the error.
It is often the case that the one responsible for handling errors is user of the object: the caller. What if we are interested in the error, but don't want to assume that responsibility? We rescue the error, do whatever we need to do and then propagate the signal up the stack as if nothing had happened.
For example, what if we wanted to log the error message and then let the caller deal with it?
begin
this_will_fail!
rescue Failure => error
log.error error.message
raise
end
Calling raise without any arguments will raise the last error. In our case, we are re-raising error.
In the example you presented in your question, re-raising the error is simply not necessary. You could simply let it propagate up the stack naturally. The only difference in your example is you're creating a new error object and raising it instead of re-raising the last one.
This will raise the same type of error as the original, but you can customize the message.
rescue StandardError => e
raise e.class, "Message: #{e.message}"
I had the same question as in the comment thread here, i.e. What if the line before (re)raise fails?
My understanding was limited by the missing knowledge that the global variable of $! is "kinda garbage collected" // "scoped to its functional context", which the below example demonstrates:
def func
begin
raise StandardError, 'func!'
rescue StandardError => err
puts "$! = #{$!.inspect}"
end
end
begin
raise StandardError, 'oh no!'
rescue StandardError => err
func
puts "$! = #{$!.inspect}"
raise
end
The output of the above is:
$! = #<StandardError: func!>
$! = #<StandardError: oh no!>
StandardError: oh no!
from (pry):47:in `__pry__'
This behavior is different than how Python's (re)raise works.
The documentation for Exception states:
When an exception has been raised but not yet handled (in rescue,
ensure, at_exit and END blocks), two global variables are set:
$! contains the current exception.
$# contains its backtrace.
So these variables aren't true global variables, they are only defined inside the block that's handling the error.
begin
raise
rescue
p $! # StandardError
end
p $! # nil
A slightly better way to do the same thing as FreePender is to use the exception method from the Exception class, which is the ancestor class to any error classes, like StandardError, so that the method is available to any error classes.
Here the method's documentation that you can find on ApiDock:
With no argument, or if the argument is the same as the receiver, return the receiver. Otherwise, create a new exception object of the same class as the receiver, but with a message equal to string.to_str.
Now let's see how it works:
begin
this_will_fail!
rescue Failure => error
raise error.exception("Message: #{error.message}")
end
Adding to above answers here:
In some applications you may need to log the error twice.
For example exception need to be notified to monitoring tools like
Nagios/Newrelic/Cloudwatch.
At the same time you may have your own kibana backed summary logging
tool, internally for your reference.
In those cases you might want to log & handle the errors multiple times.
Example:
begin
begin
nil.to_sym
rescue => e
puts "inner block error message: #{e.message}"
puts "inner block backtrace: #{e.backtrace.join("\n")}"
raise e
end
rescue => e
puts "outer block error message: #{e.message}"
puts "outer block backtrace: #{e.backtrace.join("\n")}"
end
I am using puts here, for your the ease of verifying this code in
rails console, in actual production you may need to use rails logger

rspec: raise_error usage to match error message

I raised an error using raise(ConfigurationError.new(msg))
I tried to test this with rspec:
expect {
Base.configuration.username
}.to raise_error(ConfigurationError, message)
But this doesn't work. How can I test this? The goal is to match message.
You can match error message with regex:
it { expect{ Foo.bar }.to raise_error(NoMethodError, /private/) }
This will check if NoMethodError raised with private method message not undefined method.
Will be useful because NoMethodError.new didn't pass tests even with same error message.
Make sure you are using rspec > 2.14.0 and take a look at this commit:
https://github.com/rspec/rspec-expectations/commit/7f02b503d5ae48d1141b6465acd0a7a4e1bb84dd
it "passes if an error instance is expected" do
s = StandardError.new
expect {raise s}.to raise_error(s)
end
An alternative solution is to use a block where you assert on various properties of |error|. Using the names in the original question:
expect { Base.configuration.username }
.to(raise_error do |error|
expect(error).to be_a(ConfigurationError)
expect(error.message).to eq 'My configuration message'
end)

Resources