-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bubble up to the error_formatter the original exception and the backtrace #1652
Conversation
lib/grape/middleware/formatter.rb
Outdated
@@ -110,7 +110,7 @@ def read_rack_input(body) | |||
rescue Grape::Exceptions::Base => e | |||
raise e | |||
rescue StandardError => e | |||
throw :error, status: 400, message: e.message | |||
throw :error, status: 400, message: e.message, backtrace: e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a standard pattern somehow in Ruby? I mean I would expect backtrace
to be an actual backtrace and not the original exception. Is anyone doing it like this elsewhere?
Are there other places we throw :error
like this that also need fixing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a standard pattern somehow in Ruby? I mean I would expect backtrace to be an actual backtrace and not the original exception. Is anyone doing it like this elsewhere?
I am quite new in the Ruby world, so TBH I don't know if this is a standard pattern but I would say no. However the Grape library as a "contract" for the formatter: formatter.call(message, backtrace, options, env)
.
# lib/grape/middleware/error.rb:90
def error_response(error = {})
status = error[:status] || options[:default_status]
message = error[:message] || options[:default_message]
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
backtrace = error[:backtrace] || []
rack_response(format_message(message, backtrace), status, headers)
end
# lib/grape/middleware/error.rb:103
def format_message(message, backtrace)
format = env[Grape::Env::API_FORMAT] || options[:format]
formatter = Grape::ErrorFormatter.formatter_for(format, options)
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
formatter.call(message, backtrace, options, env)
end
This method is called here:
# lib/grape/middleware/error.rb:29
def call!(env)
@env = env
begin
error_response(catch(:error) do
return @app.call(@env)
end)
rescue StandardError => e
# ...
end
Are there other places we throw :error like this that also need fixing?
Probably yes, but I didn't investigate any other use cases. This one affected us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it works, backtrace is meant to be a backtrace. So I think either adding an original exception into the contract or passing the actual backtrace is the right thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do both, what would be your advice on how to test it?
Danger is right, this also needs tests. |
@dblock would you care to review the code again? I already did the changes you requested (added both backtrace and original exception) and added a test. I will update the changelog as soon as I have some feedback regarding the implementation. |
Thanks for hanging in here @dcsg. I am not loving this at all :( I think we're going the wrong way. The following passes all kinds of things here which are all properties of
I think what we want to write is this:
Does this look feasible? It might mean breaking some backwards compat which I am OK with, but maybe we don't have to? |
@dblock did the changes, what do you think about them? |
@@ -92,19 +95,24 @@ def error_response(error = {}) | |||
message = error[:message] || options[:default_message] | |||
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type } | |||
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash) | |||
backtrace = error[:backtrace] || [] | |||
rack_response(format_message(message, backtrace), status, headers) | |||
backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dblock here I could replace this with the following:
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
However, rubocop complains because of Ruby 2.1.*. Does Grape still support that Ruby version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grape supports whatever is in https://github.com/ruby-grape/grape/blob/master/.travis.yml, so 2.2+, so we can tame Rubocop accordingly. Since we don't use &.
anywhere else maybe just expanding it is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some minor comments left and I am OK merging this because it does what it does. However I'd like us to go further and get rid of passing backtrace through, maybe in a future PR?
Basically I want
throw :error, status: 400, message: e.message, backtrace: e.backtrace, exception: e
to become
throw :error, status: 400, original_exception: e
lib/grape/error_formatter/json.rb
Outdated
@@ -4,12 +4,15 @@ module Json | |||
extend Base | |||
|
|||
class << self | |||
def call(message, backtrace, options = {}, env = nil) | |||
def call(message, backtrace, options = {}, env = nil, original_exception = nil) | |||
result = wrap_message(present(message, env)) | |||
|
|||
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if (options[:rescue_options] || {})
is dup, so unwrap it.
rescue_options = options[:rescue_options]
if (rescue_options)
if ...
end
lib/grape/middleware/error.rb
Outdated
@@ -77,13 +80,13 @@ def exec_handler(e, &handler) | |||
end | |||
end | |||
|
|||
def error!(message, status = options[:default_status], headers = {}, backtrace = []) | |||
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here original_exception
should be defaulted to nil
.
@@ -92,19 +95,24 @@ def error_response(error = {}) | |||
message = error[:message] || options[:default_message] | |||
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type } | |||
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash) | |||
backtrace = error[:backtrace] || [] | |||
rack_response(format_message(message, backtrace), status, headers) | |||
backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine.
lib/grape/middleware/formatter.rb
Outdated
@@ -45,7 +45,7 @@ def build_formatted_response(status, headers, bodies) | |||
Rack::Response.new(bodymap, status, headers) | |||
end | |||
rescue Grape::Exceptions::InvalidFormatter => e | |||
throw :error, status: 500, message: e.message | |||
throw :error, status: 500, message: e.message, backtrace: e.backtrace, exception: e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sholdn't this be original_exception: e
?
CHANGELOG.md
Outdated
@@ -6,7 +6,7 @@ | |||
|
|||
#### Fixes | |||
|
|||
* Your contribution here. | |||
* [#1652](https://github.com/ruby-grape/grape/pull/1652): Bubble up to the error_formatter the root exception - [@dcsg](https://github.com/dcsg). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
root -> original
Doing this change it will break backwards compatibility. Do you want to do it now? |
@dblock did the changes. |
lib/grape/middleware/formatter.rb
Outdated
@@ -110,7 +110,7 @@ def read_rack_input(body) | |||
rescue Grape::Exceptions::Base => e | |||
raise e | |||
rescue StandardError => e | |||
throw :error, status: 400, message: e.message | |||
throw :error, status: 400, message: e.message, backtrace: e.backtrace, exception: e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still exception: ...
, which tells me there's no tests for it cause this doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I didn't find a way to test these use cases. Do you have any suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a trivial custom parser that raises an exception, it will throw on parser.call
and hit this code.
CHANGELOG.md
Outdated
@@ -6,7 +6,7 @@ | |||
|
|||
#### Fixes | |||
|
|||
* Your contribution here. | |||
* [#1652](https://github.com/ruby-grape/grape/pull/1652): Bubble up to the error_formatter the original exception - [@dcsg](https://github.com/dcsg). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh and this is a feature, not a fix, right? Should be above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO is both a new feature (the original_exception) and a fix (the backtrace).
e850d37
to
261de9e
Compare
add to run
|
@dblock added the missing test! |
6b08b4f
to
4521544
Compare
quite odd the tests that are failing. Any ideas? I didn't change at all anything related to those tests that are failing in the past 2 commits |
ef0c192
to
264aee1
Compare
Found the problem with the build, #1655. I'll rebase/merge this one next. |
You can rebase on master, otherwise I'll get to this soon. |
…rser is not able to parse a json input body.
Add backtrace in missing `throw :error` Add tests
264aee1
to
469b05d
Compare
@dblock rebase done |
Merged, thank you. |
Want to try to make the backward (in)compatible change to reduce the number of things we pass into the |
@dblock yes, I can do it |
BTW when do you expect to release a new tag with this fix? |
@dblock I guess it will be kind of hard or almost impossible to do the refactor you want. # lib/grape/dsl/inside_route.rb:103
# End the request and display an error to the
# end user with the specified message.
#
# @param message [String] The message to display.
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
def error!(message, status = nil, headers = nil)
self.status(status || namespace_inheritable(:default_error_status))
throw :error, message: message, status: self.status, headers: headers
end What do you think? |
We'll do a release soon. Give the refactor a shot and lets see if we can come up with something! |
Updates README according to ruby-grape#1652 Prevents `ArgumentError: wrong number of arguments (given 5, expected 4)` from happening when following the README.
When a custom parser is not able to parse the json input body, it raises an exception that is rescued in the
Grape::Middleware::Formatter:111
. However, thethrow
statement does not bubble up the root cause by not setting thebacktrace
. This makes it harder for theerror_formatter
to understand what caused the exception and to properly handle it.I tried to add tests to this but was not that easy, any suggestions?
Before the fix
grape/middleware/formatter.rb
:my_custom_error_formatter.rb
:After the fix
my_custom_error_formatter.rb
: