-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy patherror.rb
148 lines (121 loc) · 5.59 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
140
141
142
143
144
145
146
147
148
# frozen_string_literal: true
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.include(@options[:helpers]) if @options[:helpers]
end
def call!(env)
@env = env
error_response(catch(:error) { return @app.call(@env) })
rescue Exception => e # rubocop:disable Lint/RescueException
run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
end
private
def rack_response(status, headers, message)
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(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[:error_formatters], options[:default_error_formatter])
return formatter.call(message, backtrace, options, env, original_exception) if formatter
throw :error,
status: 406,
message: "The requested format '#{format}' is not supported.",
backtrace: backtrace,
original_exception: original_exception
end
def find_handler(klass)
rescue_handler_for_base_only_class(klass) ||
rescue_handler_for_class_or_its_ancestor(klass) ||
rescue_handler_for_grape_exception(klass) ||
rescue_handler_for_any_class(klass) ||
raise
end
def error_response(error = {})
status = error[:status] || options[:default_status]
env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called
message = error[:message] || options[:default_message]
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
end
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
rack_response(status, headers, format_message(message, backtrace, original_exception))
end
def default_rescue_handler(exception)
error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
end
def rescue_handler_for_base_only_class(klass)
error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }
return unless error
handler || method(: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 || method(:default_rescue_handler)
end
def rescue_handler_for_grape_exception(klass)
return unless klass <= Grape::Exceptions::Base
return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
options[:grape_exceptions_rescue_handler] || method(: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] || method(:default_rescue_handler)
end
def run_rescue_handler(handler, error, endpoint)
if handler.instance_of?(Symbol)
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
handler = public_method(handler)
end
response = catch(:error) do
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
end
if error?(response)
error_response(response)
elsif response.is_a?(Rack::Response)
response
else
run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)
end
end
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route
rack_response(
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
format_message(message, backtrace, original_exception)
)
end
def error?(response)
return false unless response.is_a?(Hash)
response.key?(:message) && response.key?(:status) && response.key?(:headers)
end
end
end
end