-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
error.rb
139 lines (114 loc) · 5.14 KB
/
error.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true
require 'grape/middleware/base'
require 'active_support/core_ext/string/output_safety'
module Grape
module Middleware
class Error < Base
def default_options
{
default_status: 500, # default status returned on error
default_message: '',
format: :txt,
helpers: nil,
formatters: {},
error_formatters: {},
rescue_all: false, # true to rescue all exceptions
rescue_grape_exceptions: false,
rescue_subclasses: true, # rescue subclasses of exceptions listed
rescue_options: {
backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
original_exception: false # true to display exception
},
rescue_handlers: {}, # rescue handler blocks
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
}
end
def initialize(app, *options)
super
self.class.send(:include, @options[:helpers]) if @options[:helpers]
end
def call!(env)
@env = env
begin
error_response(catch(:error) do
return @app.call(@env)
end)
rescue Exception => e # rubocop:disable Lint/RescueException
handler =
rescue_handler_for_base_only_class(e.class) ||
rescue_handler_for_class_or_its_ancestor(e.class) ||
rescue_handler_for_grape_exception(e.class) ||
rescue_handler_for_any_class(e.class) ||
raise
run_rescue_handler(handler, e)
end
end
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
rack_response(format_message(message, backtrace, original_exception), status, headers)
end
def default_rescue_handler(e)
error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
end
# TODO: This method is deprecated. Refactor out.
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] || error[:original_exception]&.backtrace || []
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
rack_response(format_message(message, backtrace, original_exception), status, headers)
end
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
Rack::Response.new([message], status, headers)
end
def format_message(message, backtrace, original_exception = nil)
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.",
backtrace: backtrace,
original_exception: original_exception unless formatter
formatter.call(message, backtrace, options, env, original_exception)
end
private
def rescue_handler_for_base_only_class(klass)
error, handler = options[:base_only_rescue_handlers].find { |err, _handler| klass == err }
return unless error
handler || :default_rescue_handler
end
def rescue_handler_for_class_or_its_ancestor(klass)
error, handler = options[:rescue_handlers].find { |err, _handler| klass <= err }
return unless error
handler || :default_rescue_handler
end
def rescue_handler_for_grape_exception(klass)
return unless klass <= Grape::Exceptions::Base
return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
:error_response
end
def rescue_handler_for_any_class(klass)
return unless klass <= StandardError
return unless options[:rescue_all] || options[:rescue_grape_exceptions]
options[:all_rescue_handler] || :default_rescue_handler
end
def run_rescue_handler(handler, error)
if handler.instance_of?(Symbol)
raise NoMethodError, "undefined method `#{handler}'" unless respond_to?(handler)
handler = public_method(handler)
end
response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
if response.is_a?(Rack::Response)
response
else
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
end
end
end
end
end