Skip to content

Commit 3c0be5e

Browse files
dcsgdblock
authored andcommitted
Bubble up to the error_formatter the original exception and the backtrace (#1652)
1 parent d337d7f commit 3c0be5e

File tree

10 files changed

+122
-29
lines changed

10 files changed

+122
-29
lines changed

.rubocop_todo.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2017-06-17 22:15:11 +0300 using RuboCop version 0.47.0.
3+
# on 2017-07-11 00:59:31 +0100 using RuboCop version 0.47.0.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
@@ -44,6 +44,11 @@ Metrics/ModuleLength:
4444
Metrics/PerceivedComplexity:
4545
Max: 14
4646

47+
# Offense count: 2
48+
Style/BlockDelimiters:
49+
Exclude:
50+
- 'spec/grape/middleware/formatter_spec.rb'
51+
4752
# Offense count: 2
4853
Style/IdenticalConditionalBranches:
4954
Exclude:

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
#### Features
44

55
* Your contribution here.
6+
* [#1652](https://github.com/ruby-grape/grape/pull/1652): Add the original exception to the error_formatter the original exception - [@dcsg](https://github.com/dcsg).
67

78
#### Fixes
89

9-
* Your contribution here.
10+
* [#1652](https://github.com/ruby-grape/grape/pull/1652): Fix missing backtrace that was not being bubble up to the error_formatter - [@dcsg](https://github.com/dcsg).
1011

1112
### 1.0.0 (7/3/2017)
1213

lib/grape/error_formatter/json.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ module Json
44
extend Base
55

66
class << self
7-
def call(message, backtrace, options = {}, env = nil)
7+
def call(message, backtrace, options = {}, env = nil, original_exception = nil)
88
result = wrap_message(present(message, env))
99

10-
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
10+
rescue_options = options[:rescue_options] || {}
11+
if rescue_options[:backtrace] && backtrace && !backtrace.empty?
1112
result = result.merge(backtrace: backtrace)
1213
end
14+
if rescue_options[:original_exception] && original_exception
15+
result = result.merge(original_exception: original_exception.inspect)
16+
end
1317
::Grape::Json.dump(result)
1418
end
1519

lib/grape/error_formatter/txt.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ module Txt
44
extend Base
55

66
class << self
7-
def call(message, backtrace, options = {}, env = nil)
7+
def call(message, backtrace, options = {}, env = nil, original_exception = nil)
88
message = present(message, env)
99

1010
result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message
11-
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
12-
result += "\r\n "
11+
rescue_options = options[:rescue_options] || {}
12+
if rescue_options[:backtrace] && backtrace && !backtrace.empty?
13+
result += "\r\n backtrace:"
1314
result += backtrace.join("\r\n ")
1415
end
16+
if rescue_options[:original_exception] && original_exception
17+
result += "\r\n original exception:"
18+
result += "\r\n #{original_exception.inspect}"
19+
end
1520
result
1621
end
1722
end

lib/grape/error_formatter/xml.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ module Xml
44
extend Base
55

66
class << self
7-
def call(message, backtrace, options = {}, env = nil)
7+
def call(message, backtrace, options = {}, env = nil, original_exception = nil)
88
message = present(message, env)
99

1010
result = message.is_a?(Hash) ? message : { message: message }
11-
if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
11+
rescue_options = options[:rescue_options] || {}
12+
if rescue_options[:backtrace] && backtrace && !backtrace.empty?
1213
result = result.merge(backtrace: backtrace)
1314
end
15+
if rescue_options[:original_exception] && original_exception
16+
result = result.merge(original_exception: original_exception.inspect)
17+
end
1418
result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
1519
end
1620
end

lib/grape/middleware/error.rb

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ def default_options
1414
rescue_all: false, # true to rescue all exceptions
1515
rescue_grape_exceptions: false,
1616
rescue_subclasses: true, # rescue subclasses of exceptions listed
17-
rescue_options: { backtrace: false }, # true to display backtrace, true to let Grape handle Grape::Exceptions
17+
rescue_options: {
18+
backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
19+
original_exception: false, # true to display exception
20+
},
1821
rescue_handlers: {}, # rescue handler blocks
1922
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
2023
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
@@ -77,13 +80,13 @@ def exec_handler(e, &handler)
7780
end
7881
end
7982

80-
def error!(message, status = options[:default_status], headers = {}, backtrace = [])
83+
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
8184
headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
82-
rack_response(format_message(message, backtrace), status, headers)
85+
rack_response(format_message(message, backtrace, original_exception), status, headers)
8386
end
8487

8588
def handle_error(e)
86-
error_response(message: e.message, backtrace: e.backtrace)
89+
error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
8790
end
8891

8992
# TODO: This method is deprecated. Refactor out.
@@ -92,19 +95,24 @@ def error_response(error = {})
9295
message = error[:message] || options[:default_message]
9396
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
9497
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
95-
backtrace = error[:backtrace] || []
96-
rack_response(format_message(message, backtrace), status, headers)
98+
backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || []
99+
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
100+
rack_response(format_message(message, backtrace, original_exception), status, headers)
97101
end
98102

99103
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
100104
Rack::Response.new([message], status, headers).finish
101105
end
102106

103-
def format_message(message, backtrace)
107+
def format_message(message, backtrace, original_exception = nil)
104108
format = env[Grape::Env::API_FORMAT] || options[:format]
105109
formatter = Grape::ErrorFormatter.formatter_for(format, options)
106-
throw :error, status: 406, message: "The requested format '#{format}' is not supported." unless formatter
107-
formatter.call(message, backtrace, options, env)
110+
throw :error,
111+
status: 406,
112+
message: "The requested format '#{format}' is not supported.",
113+
backtrace: backtrace,
114+
original_exception: original_exception unless formatter
115+
formatter.call(message, backtrace, options, env, original_exception)
108116
end
109117

110118
private

lib/grape/middleware/formatter.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def build_formatted_response(status, headers, bodies)
4545
Rack::Response.new(bodymap, status, headers)
4646
end
4747
rescue Grape::Exceptions::InvalidFormatter => e
48-
throw :error, status: 500, message: e.message
48+
throw :error, status: 500, message: e.message, backtrace: e.backtrace, original_exception: e
4949
end
5050

5151
def fetch_formatter(headers, options)
@@ -110,7 +110,7 @@ def read_rack_input(body)
110110
rescue Grape::Exceptions::Base => e
111111
raise e
112112
rescue StandardError => e
113-
throw :error, status: 400, message: e.message
113+
throw :error, status: 400, message: e.message, backtrace: e.backtrace, original_exception: e
114114
end
115115
else
116116
env[Grape::Env::API_REQUEST_BODY] = body

spec/grape/api_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,7 +1996,7 @@ class ChildError < ParentError; end
19961996
before :each do
19971997
module ApiSpec
19981998
class CustomErrorFormatter
1999-
def self.call(message, _backtrace, _options, _env)
1999+
def self.call(message, _backtrace, _options, _env, _original_exception)
20002000
"message: #{message} @backtrace"
20012001
end
20022002
end
@@ -2018,7 +2018,7 @@ def self.call(message, _backtrace, _options, _env)
20182018
before :each do
20192019
module ApiSpec
20202020
class CustomErrorFormatter
2021-
def self.call(message, _backtrace, _option, _env)
2021+
def self.call(message, _backtrace, _option, _env, _original_exception)
20222022
"message: #{message} @backtrace"
20232023
end
20242024
end

spec/grape/middleware/exception_spec.rb

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,14 @@ def call(_env)
158158
it 'is possible to specify a custom formatter' do
159159
@app ||= Rack::Builder.app do
160160
use Spec::Support::EndpointFaker
161-
use Grape::Middleware::Error, rescue_all: true,
162-
format: :custom,
163-
error_formatters: {
164-
custom: lambda do |message, _backtrace, _options, _env|
165-
{ custom_formatter: message }.inspect
166-
end
167-
}
161+
use Grape::Middleware::Error,
162+
rescue_all: true,
163+
format: :custom,
164+
error_formatters: {
165+
custom: lambda do |message, _backtrace, _options, _env, _original_exception|
166+
{ custom_formatter: message }.inspect
167+
end
168+
}
168169
run ExceptionSpec::ExceptionApp
169170
end
170171
get '/'
@@ -192,5 +193,46 @@ def call(_env)
192193
expect(last_response.status).to eq(400)
193194
expect(last_response.body).to eq('failed validation')
194195
end
196+
197+
context 'with rescue_options :backtrace and :exception set to true' do
198+
it 'is possible to return the backtrace and the original exception in json format' do
199+
@app ||= Rack::Builder.app do
200+
use Spec::Support::EndpointFaker
201+
use Grape::Middleware::Error,
202+
rescue_all: true,
203+
format: :json,
204+
rescue_options: { backtrace: true, original_exception: true }
205+
run ExceptionSpec::ExceptionApp
206+
end
207+
get '/'
208+
expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original_exception', 'RuntimeError')
209+
end
210+
211+
it 'is possible to return the backtrace and the original exception in xml format' do
212+
@app ||= Rack::Builder.app do
213+
use Spec::Support::EndpointFaker
214+
use Grape::Middleware::Error,
215+
rescue_all: true,
216+
format: :xml,
217+
rescue_options: { backtrace: true, original_exception: true }
218+
run ExceptionSpec::ExceptionApp
219+
end
220+
get '/'
221+
expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original-exception', 'RuntimeError')
222+
end
223+
224+
it 'is possible to return the backtrace and the original exception in txt format' do
225+
@app ||= Rack::Builder.app do
226+
use Spec::Support::EndpointFaker
227+
use Grape::Middleware::Error,
228+
rescue_all: true,
229+
format: :txt,
230+
rescue_options: { backtrace: true, original_exception: true }
231+
run ExceptionSpec::ExceptionApp
232+
end
233+
get '/'
234+
expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original exception', 'RuntimeError')
235+
end
236+
end
195237
end
196238
end

spec/grape/middleware/formatter_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,4 +324,28 @@ def self.call(_, _)
324324
expect(bodies.body.first).to eq({ message: 'invalid' }.to_json)
325325
end
326326
end
327+
328+
context 'custom parser raises exception and rescue options are enabled for backtrace and original_exception' do
329+
it 'adds the backtrace and original_exception to the error output' do
330+
subject = Grape::Middleware::Formatter.new(
331+
app,
332+
rescue_options: { backtrace: true, original_exception: true },
333+
parsers: { json: ->(_object, _env) { raise StandardError, 'fail' } }
334+
)
335+
io = StringIO.new('{invalid}')
336+
error = catch(:error) {
337+
subject.call(
338+
'PATH_INFO' => '/info',
339+
'REQUEST_METHOD' => 'POST',
340+
'CONTENT_TYPE' => 'application/json',
341+
'rack.input' => io,
342+
'CONTENT_LENGTH' => io.length
343+
)
344+
}
345+
346+
expect(error[:message]).to eq 'fail'
347+
expect(error[:backtrace].size).to be >= 1
348+
expect(error[:original_exception].class).to eq StandardError
349+
end
350+
end
327351
end

0 commit comments

Comments
 (0)