grape 2.0.0 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -1
- data/README.md +362 -316
- data/UPGRADING.md +197 -7
- data/grape.gemspec +5 -6
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- data/lib/grape/content_types.rb +0 -2
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +22 -20
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/inside_route.rb +46 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +14 -17
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +2 -4
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +3 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/base.rb +1 -3
- data/lib/grape/middleware/error.rb +55 -50
- data/lib/grape/middleware/formatter.rb +16 -13
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +4 -5
- data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
- data/lib/grape/middleware/versioner/header.rb +17 -163
- data/lib/grape/middleware/versioner/param.rb +2 -4
- data/lib/grape/middleware/versioner/path.rb +1 -3
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/path.rb +24 -29
- data/lib/grape/request.rb +4 -12
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +32 -37
- data/lib/grape/util/accept_header_handler.rb +105 -0
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/object.rb +45 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/attributes_iterator.rb +1 -0
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +22 -19
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validators/base.rb +1 -0
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +42 -0
- data/lib/grape/validations/validators/values_validator.rb +6 -1
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +30 -274
- metadata +30 -37
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
data/lib/grape/dsl/routing.rb
CHANGED
@@ -30,7 +30,7 @@ module Grape
|
|
30
30
|
if args.any?
|
31
31
|
options = args.extract_options!
|
32
32
|
options = options.reverse_merge(using: :path)
|
33
|
-
requested_versions = args.flatten
|
33
|
+
requested_versions = args.flatten.map(&:to_s)
|
34
34
|
|
35
35
|
raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
|
36
36
|
|
@@ -54,7 +54,7 @@ module Grape
|
|
54
54
|
|
55
55
|
# Define a root URL prefix for your entire API.
|
56
56
|
def prefix(prefix = nil)
|
57
|
-
namespace_inheritable(:root_prefix, prefix)
|
57
|
+
namespace_inheritable(:root_prefix, prefix&.to_s)
|
58
58
|
end
|
59
59
|
|
60
60
|
# Create a scope without affecting the URL.
|
@@ -85,8 +85,8 @@ module Grape
|
|
85
85
|
mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)
|
86
86
|
mounts.each_pair do |app, path|
|
87
87
|
if app.respond_to?(:mount_instance)
|
88
|
-
opts_with = opts.any? ? opts.
|
89
|
-
mount({ app.mount_instance(configuration: opts_with) => path })
|
88
|
+
opts_with = opts.any? ? opts.first[:with] : {}
|
89
|
+
mount({ app.mount_instance(configuration: opts_with) => path }, *opts)
|
90
90
|
next
|
91
91
|
end
|
92
92
|
in_setting = inheritable_setting
|
@@ -103,6 +103,15 @@ module Grape
|
|
103
103
|
change!
|
104
104
|
end
|
105
105
|
|
106
|
+
# When trying to mount multiple times the same endpoint, remove the previous ones
|
107
|
+
# from the list of endpoints if refresh_already_mounted parameter is true
|
108
|
+
refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false
|
109
|
+
if refresh_already_mounted && !endpoints.empty?
|
110
|
+
endpoints.delete_if do |endpoint|
|
111
|
+
endpoint.options[:app].to_s == app.to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
106
115
|
endpoints << Grape::Endpoint.new(
|
107
116
|
in_setting,
|
108
117
|
method: :any,
|
@@ -225,6 +234,13 @@ module Grape
|
|
225
234
|
def versions
|
226
235
|
@versions ||= []
|
227
236
|
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def refresh_mounted_api(mounts, *opts)
|
241
|
+
opts << { refresh_already_mounted: true }
|
242
|
+
mount(mounts, *opts)
|
243
|
+
end
|
228
244
|
end
|
229
245
|
end
|
230
246
|
end
|
@@ -38,6 +38,19 @@ module Grape
|
|
38
38
|
def params(&block)
|
39
39
|
Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
|
40
40
|
end
|
41
|
+
|
42
|
+
# Declare the contract to be used for the endpoint's parameters.
|
43
|
+
# @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
|
44
|
+
# The contract or schema to be used for validation. Optional.
|
45
|
+
# @yield a block yielding a new instance of Dry::Schema::Params
|
46
|
+
# subclass, allowing to define the schema inline. When the
|
47
|
+
# +contract+ parameter is a schema, it will be used as a parent. Optional.
|
48
|
+
def contract(contract = nil, &block)
|
49
|
+
raise ArgumentError, 'Either contract or block must be provided' unless contract || block
|
50
|
+
raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
|
51
|
+
|
52
|
+
Grape::Validations::ContractScope.new(self, contract, &block)
|
53
|
+
end
|
41
54
|
end
|
42
55
|
end
|
43
56
|
end
|
data/lib/grape/endpoint.rb
CHANGED
@@ -13,8 +13,8 @@ module Grape
|
|
13
13
|
attr_reader :env, :request, :headers, :params
|
14
14
|
|
15
15
|
class << self
|
16
|
-
def new(
|
17
|
-
self == Endpoint ? Class.new(Endpoint).new(
|
16
|
+
def new(...)
|
17
|
+
self == Endpoint ? Class.new(Endpoint).new(...) : super
|
18
18
|
end
|
19
19
|
|
20
20
|
def before_each(new_setup = false, &block)
|
@@ -55,7 +55,7 @@ module Grape
|
|
55
55
|
|
56
56
|
proc do |endpoint_instance|
|
57
57
|
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
|
58
|
-
method.
|
58
|
+
method.bind_call(endpoint_instance)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
@@ -151,7 +151,7 @@ module Grape
|
|
151
151
|
reset_routes!
|
152
152
|
routes.each do |route|
|
153
153
|
methods = [route.request_method]
|
154
|
-
methods <<
|
154
|
+
methods << Rack::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
|
155
155
|
methods.each do |method|
|
156
156
|
route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
|
157
157
|
router.append(route.apply(self))
|
@@ -190,10 +190,11 @@ module Grape
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def prepare_version
|
193
|
-
version = namespace_inheritable(:version)
|
193
|
+
version = namespace_inheritable(:version)
|
194
|
+
return unless version
|
194
195
|
return if version.empty?
|
195
196
|
|
196
|
-
version.length == 1 ? version.first
|
197
|
+
version.length == 1 ? version.first : version
|
197
198
|
end
|
198
199
|
|
199
200
|
def merge_route_options(**default)
|
@@ -206,7 +207,7 @@ module Grape
|
|
206
207
|
|
207
208
|
def prepare_path(path)
|
208
209
|
path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
|
209
|
-
Path.
|
210
|
+
Path.new(path, namespace, path_settings)
|
210
211
|
end
|
211
212
|
|
212
213
|
def namespace
|
@@ -230,15 +231,15 @@ module Grape
|
|
230
231
|
options[:app].endpoints if options[:app].respond_to?(:endpoints)
|
231
232
|
end
|
232
233
|
|
233
|
-
def equals?(
|
234
|
-
(options ==
|
234
|
+
def equals?(endpoint)
|
235
|
+
(options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
|
235
236
|
end
|
236
237
|
|
237
238
|
protected
|
238
239
|
|
239
240
|
def run
|
240
241
|
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
|
241
|
-
@header =
|
242
|
+
@header = Grape::Util::Header.new
|
242
243
|
@request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
|
243
244
|
@params = @request.params
|
244
245
|
@headers = @request.headers
|
@@ -401,15 +402,11 @@ module Grape
|
|
401
402
|
|
402
403
|
def options?
|
403
404
|
options[:options_route_enabled] &&
|
404
|
-
env[
|
405
|
+
env[Rack::REQUEST_METHOD] == Rack::OPTIONS
|
405
406
|
end
|
406
407
|
|
407
|
-
def
|
408
|
-
|
409
|
-
end
|
410
|
-
|
411
|
-
def respond_to_missing?(method_name, include_private = false)
|
412
|
-
super
|
408
|
+
def inspect
|
409
|
+
"#{self.class} in `#{route.origin}' endpoint"
|
413
410
|
end
|
414
411
|
end
|
415
412
|
end
|
@@ -11,11 +11,6 @@ module Grape
|
|
11
11
|
API_VENDOR = 'api.vendor'
|
12
12
|
API_FORMAT = 'api.format'
|
13
13
|
|
14
|
-
RACK_INPUT = 'rack.input'
|
15
|
-
RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
|
16
|
-
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
|
17
|
-
RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
|
18
|
-
|
19
14
|
GRAPE_REQUEST = 'grape.request'
|
20
15
|
GRAPE_REQUEST_HEADERS = 'grape.request.headers'
|
21
16
|
GRAPE_REQUEST_PARAMS = 'grape.request.params'
|
@@ -10,16 +10,17 @@ module Grape
|
|
10
10
|
message = present(message, env)
|
11
11
|
|
12
12
|
result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
13
|
+
Array.wrap(result).tap do |final_result|
|
14
|
+
rescue_options = options[:rescue_options] || {}
|
15
|
+
if rescue_options[:backtrace] && backtrace.present?
|
16
|
+
final_result << 'backtrace:'
|
17
|
+
final_result.concat(backtrace)
|
18
|
+
end
|
19
|
+
if rescue_options[:original_exception] && original_exception
|
20
|
+
final_result << 'original exception:'
|
21
|
+
final_result << original_exception.inspect
|
22
|
+
end
|
23
|
+
end.join("\r\n ")
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -41,15 +41,15 @@ module Grape
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def problem(key, **attributes)
|
44
|
-
translate_message("#{key}.problem"
|
44
|
+
translate_message(:"#{key}.problem", **attributes)
|
45
45
|
end
|
46
46
|
|
47
47
|
def summary(key, **attributes)
|
48
|
-
translate_message("#{key}.summary"
|
48
|
+
translate_message(:"#{key}.summary", **attributes)
|
49
49
|
end
|
50
50
|
|
51
51
|
def resolution(key, **attributes)
|
52
|
-
translate_message("#{key}.resolution"
|
52
|
+
translate_message(:"#{key}.resolution", **attributes)
|
53
53
|
end
|
54
54
|
|
55
55
|
def translate_attributes(keys, **options)
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/exceptions/base'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Exceptions
|
7
5
|
class ValidationErrors < Grape::Exceptions::Base
|
8
6
|
ERRORS_FORMAT_KEY = 'grape.errors.format'
|
9
|
-
DEFAULT_ERRORS_FORMAT = '
|
7
|
+
DEFAULT_ERRORS_FORMAT = '%<attributes>s %<message>s'
|
10
8
|
|
11
9
|
include Enumerable
|
12
10
|
|
@@ -14,7 +12,7 @@ module Grape
|
|
14
12
|
|
15
13
|
def initialize(errors: [], headers: {}, **_options)
|
16
14
|
@errors = errors.group_by(&:params)
|
17
|
-
super
|
15
|
+
super(message: full_messages.join(', '), status: 400, headers: headers)
|
18
16
|
end
|
19
17
|
|
20
18
|
def each
|
@@ -12,8 +12,12 @@ module Grape
|
|
12
12
|
|
13
13
|
def build_params
|
14
14
|
rack_params.deep_dup.tap do |params|
|
15
|
-
params.deep_merge!(grape_routing_args) if env.key?(Grape::Env::GRAPE_ROUTING_ARGS)
|
16
15
|
params.deep_symbolize_keys!
|
16
|
+
|
17
|
+
if env.key?(Grape::Env::GRAPE_ROUTING_ARGS)
|
18
|
+
grape_routing_args.deep_symbolize_keys!
|
19
|
+
params.deep_merge!(grape_routing_args)
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
data/lib/grape/http/headers.rb
CHANGED
@@ -1,46 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/util/lazy_object'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Http
|
7
5
|
module Headers
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
REQUEST_METHOD = 'REQUEST_METHOD'
|
12
|
-
QUERY_STRING = 'QUERY_STRING'
|
13
|
-
|
14
|
-
if Grape.lowercase_headers?
|
15
|
-
ALLOW = 'allow'
|
16
|
-
LOCATION = 'location'
|
17
|
-
TRANSFER_ENCODING = 'transfer-encoding'
|
18
|
-
X_CASCADE = 'x-cascade'
|
19
|
-
else
|
20
|
-
ALLOW = 'Allow'
|
21
|
-
LOCATION = 'Location'
|
22
|
-
TRANSFER_ENCODING = 'Transfer-Encoding'
|
23
|
-
X_CASCADE = 'X-Cascade'
|
24
|
-
end
|
25
|
-
|
26
|
-
GET = 'GET'
|
27
|
-
POST = 'POST'
|
28
|
-
PUT = 'PUT'
|
29
|
-
PATCH = 'PATCH'
|
30
|
-
DELETE = 'DELETE'
|
31
|
-
HEAD = 'HEAD'
|
32
|
-
OPTIONS = 'OPTIONS'
|
6
|
+
HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
|
7
|
+
HTTP_ACCEPT = 'HTTP_ACCEPT'
|
8
|
+
HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
|
33
9
|
|
34
|
-
|
35
|
-
|
10
|
+
ALLOW = 'Allow'
|
11
|
+
LOCATION = 'Location'
|
12
|
+
X_CASCADE = 'X-Cascade'
|
13
|
+
TRANSFER_ENCODING = 'Transfer-Encoding'
|
36
14
|
|
37
|
-
|
38
|
-
|
39
|
-
|
15
|
+
SUPPORTED_METHODS = [
|
16
|
+
Rack::GET,
|
17
|
+
Rack::POST,
|
18
|
+
Rack::PUT,
|
19
|
+
Rack::PATCH,
|
20
|
+
Rack::DELETE,
|
21
|
+
Rack::HEAD,
|
22
|
+
Rack::OPTIONS
|
23
|
+
].freeze
|
40
24
|
|
41
|
-
|
25
|
+
SUPPORTED_METHODS_WITHOUT_OPTIONS = (SUPPORTED_METHODS - [Rack::OPTIONS]).freeze
|
42
26
|
|
43
|
-
HTTP_HEADERS = Grape::Util::
|
27
|
+
HTTP_HEADERS = Grape::Util::Lazy::Object.new do
|
44
28
|
common_http_headers = %w[
|
45
29
|
Version
|
46
30
|
Host
|
data/lib/grape/locale/en.yml
CHANGED
@@ -10,6 +10,9 @@ en:
|
|
10
10
|
values: 'does not have a valid value'
|
11
11
|
except_values: 'has a value not allowed'
|
12
12
|
same_as: 'is not the same as %{parameter}'
|
13
|
+
length: 'is expected to have length within %{min} and %{max}'
|
14
|
+
length_min: 'is expected to have length greater than or equal to %{min}'
|
15
|
+
length_max: 'is expected to have length less than or equal to %{max}'
|
13
16
|
missing_vendor_option:
|
14
17
|
problem: 'missing :vendor option'
|
15
18
|
summary: 'when version using header, you must specify :vendor option'
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/dsl/headers'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Middleware
|
7
5
|
class Base
|
@@ -76,7 +74,7 @@ module Grape
|
|
76
74
|
end
|
77
75
|
|
78
76
|
def mime_types
|
79
|
-
@
|
77
|
+
@mime_types ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
|
80
78
|
types_without_params[v.split(';').first] = k
|
81
79
|
end
|
82
80
|
end
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/middleware/base'
|
4
|
-
require 'active_support/core_ext/string/output_safety'
|
5
|
-
|
6
3
|
module Grape
|
7
4
|
module Middleware
|
8
5
|
class Error < Base
|
@@ -34,66 +31,59 @@ module Grape
|
|
34
31
|
|
35
32
|
def call!(env)
|
36
33
|
@env = env
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end)
|
41
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
42
|
-
handler =
|
43
|
-
rescue_handler_for_base_only_class(e.class) ||
|
44
|
-
rescue_handler_for_class_or_its_ancestor(e.class) ||
|
45
|
-
rescue_handler_for_grape_exception(e.class) ||
|
46
|
-
rescue_handler_for_any_class(e.class) ||
|
47
|
-
raise
|
48
|
-
|
49
|
-
run_rescue_handler(handler, e)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
54
|
-
headers = headers.reverse_merge(Rack::CONTENT_TYPE => content_type)
|
55
|
-
rack_response(format_message(message, backtrace, original_exception), status, headers)
|
34
|
+
error_response(catch(:error) { return @app.call(@env) })
|
35
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
36
|
+
run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])
|
56
37
|
end
|
57
38
|
|
58
|
-
|
59
|
-
error_response(message: e.message, backtrace: e.backtrace, original_exception: e)
|
60
|
-
end
|
61
|
-
|
62
|
-
# TODO: This method is deprecated. Refactor out.
|
63
|
-
def error_response(error = {})
|
64
|
-
status = error[:status] || options[:default_status]
|
65
|
-
message = error[:message] || options[:default_message]
|
66
|
-
headers = { Rack::CONTENT_TYPE => content_type }
|
67
|
-
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
68
|
-
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
69
|
-
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
70
|
-
rack_response(format_message(message, backtrace, original_exception), status, headers)
|
71
|
-
end
|
39
|
+
private
|
72
40
|
|
73
|
-
def rack_response(
|
74
|
-
message =
|
75
|
-
Rack::Response.new(
|
41
|
+
def rack_response(status, headers, message)
|
42
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == TEXT_HTML
|
43
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))
|
76
44
|
end
|
77
45
|
|
78
46
|
def format_message(message, backtrace, original_exception = nil)
|
79
47
|
format = env[Grape::Env::API_FORMAT] || options[:format]
|
80
48
|
formatter = Grape::ErrorFormatter.formatter_for(format, **options)
|
49
|
+
return formatter.call(message, backtrace, options, env, original_exception) if formatter
|
50
|
+
|
81
51
|
throw :error,
|
82
52
|
status: 406,
|
83
53
|
message: "The requested format '#{format}' is not supported.",
|
84
54
|
backtrace: backtrace,
|
85
|
-
original_exception: original_exception
|
86
|
-
formatter.call(message, backtrace, options, env, original_exception)
|
55
|
+
original_exception: original_exception
|
87
56
|
end
|
88
57
|
|
89
|
-
|
58
|
+
def find_handler(klass)
|
59
|
+
rescue_handler_for_base_only_class(klass) ||
|
60
|
+
rescue_handler_for_class_or_its_ancestor(klass) ||
|
61
|
+
rescue_handler_for_grape_exception(klass) ||
|
62
|
+
rescue_handler_for_any_class(klass) ||
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
|
66
|
+
def error_response(error = {})
|
67
|
+
status = error[:status] || options[:default_status]
|
68
|
+
message = error[:message] || options[:default_message]
|
69
|
+
headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|
|
70
|
+
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
71
|
+
end
|
72
|
+
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
73
|
+
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
74
|
+
rack_response(status, headers, format_message(message, backtrace, original_exception))
|
75
|
+
end
|
76
|
+
|
77
|
+
def default_rescue_handler(exception)
|
78
|
+
error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)
|
79
|
+
end
|
90
80
|
|
91
81
|
def rescue_handler_for_base_only_class(klass)
|
92
82
|
error, handler = options[:base_only_rescue_handlers].find { |err, _handler| klass == err }
|
93
83
|
|
94
84
|
return unless error
|
95
85
|
|
96
|
-
handler || :default_rescue_handler
|
86
|
+
handler || method(:default_rescue_handler)
|
97
87
|
end
|
98
88
|
|
99
89
|
def rescue_handler_for_class_or_its_ancestor(klass)
|
@@ -101,39 +91,54 @@ module Grape
|
|
101
91
|
|
102
92
|
return unless error
|
103
93
|
|
104
|
-
handler || :default_rescue_handler
|
94
|
+
handler || method(:default_rescue_handler)
|
105
95
|
end
|
106
96
|
|
107
97
|
def rescue_handler_for_grape_exception(klass)
|
108
98
|
return unless klass <= Grape::Exceptions::Base
|
109
|
-
return :error_response if klass == Grape::Exceptions::InvalidVersionHeader
|
99
|
+
return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader
|
110
100
|
return unless options[:rescue_grape_exceptions] || !options[:rescue_all]
|
111
101
|
|
112
|
-
options[:grape_exceptions_rescue_handler] || :error_response
|
102
|
+
options[:grape_exceptions_rescue_handler] || method(:error_response)
|
113
103
|
end
|
114
104
|
|
115
105
|
def rescue_handler_for_any_class(klass)
|
116
106
|
return unless klass <= StandardError
|
117
107
|
return unless options[:rescue_all] || options[:rescue_grape_exceptions]
|
118
108
|
|
119
|
-
options[:all_rescue_handler] || :default_rescue_handler
|
109
|
+
options[:all_rescue_handler] || method(:default_rescue_handler)
|
120
110
|
end
|
121
111
|
|
122
|
-
def run_rescue_handler(handler, error)
|
112
|
+
def run_rescue_handler(handler, error, endpoint)
|
123
113
|
if handler.instance_of?(Symbol)
|
124
114
|
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
|
125
115
|
|
126
116
|
handler = public_method(handler)
|
127
117
|
end
|
128
118
|
|
129
|
-
response =
|
119
|
+
response = catch(:error) do
|
120
|
+
handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)
|
121
|
+
end
|
122
|
+
|
123
|
+
response = error!(response[:message], response[:status], response[:headers]) if error?(response)
|
130
124
|
|
131
125
|
if response.is_a?(Rack::Response)
|
132
126
|
response
|
133
127
|
else
|
134
|
-
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
|
128
|
+
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new, endpoint)
|
135
129
|
end
|
136
130
|
end
|
131
|
+
|
132
|
+
def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
|
133
|
+
rack_response(
|
134
|
+
status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),
|
135
|
+
format_message(message, backtrace, original_exception)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def error?(response)
|
140
|
+
response.is_a?(Hash) && response[:message] && response[:status] && response[:headers]
|
141
|
+
end
|
137
142
|
end
|
138
143
|
end
|
139
144
|
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'grape/middleware/base'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Middleware
|
7
5
|
class Formatter < Base
|
8
6
|
CHUNKED = 'chunked'
|
7
|
+
FORMAT = 'format'
|
9
8
|
|
10
9
|
def default_options
|
11
10
|
{
|
@@ -26,7 +25,7 @@ module Grape
|
|
26
25
|
status, headers, bodies = *@app_response
|
27
26
|
|
28
27
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
29
|
-
|
28
|
+
[status, headers, []]
|
30
29
|
else
|
31
30
|
build_formatted_response(status, headers, bodies)
|
32
31
|
end
|
@@ -82,14 +81,14 @@ module Grape
|
|
82
81
|
!request.parseable_data? &&
|
83
82
|
(request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
|
84
83
|
|
85
|
-
return unless (input = env[
|
84
|
+
return unless (input = env[Rack::RACK_INPUT])
|
86
85
|
|
87
|
-
input
|
86
|
+
rewind_input input
|
88
87
|
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
|
89
88
|
begin
|
90
89
|
read_rack_input(body) if body && !body.empty?
|
91
90
|
ensure
|
92
|
-
input
|
91
|
+
rewind_input input
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
@@ -103,12 +102,12 @@ module Grape
|
|
103
102
|
begin
|
104
103
|
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
|
105
104
|
if body.is_a?(Hash)
|
106
|
-
env[
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
env[
|
105
|
+
env[Rack::RACK_REQUEST_FORM_HASH] = if env.key?(Rack::RACK_REQUEST_FORM_HASH)
|
106
|
+
env[Rack::RACK_REQUEST_FORM_HASH].merge(body)
|
107
|
+
else
|
108
|
+
body
|
109
|
+
end
|
110
|
+
env[Rack::RACK_REQUEST_FORM_INPUT] = env[Rack::RACK_INPUT]
|
112
111
|
end
|
113
112
|
rescue Grape::Exceptions::Base => e
|
114
113
|
raise e
|
@@ -141,7 +140,7 @@ module Grape
|
|
141
140
|
end
|
142
141
|
|
143
142
|
def format_from_params
|
144
|
-
fmt = Rack::Utils.parse_nested_query(env[
|
143
|
+
fmt = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])[FORMAT]
|
145
144
|
# avoid symbol memory leak on an unknown format
|
146
145
|
return fmt.to_sym if content_type_for(fmt)
|
147
146
|
|
@@ -174,6 +173,10 @@ module Grape
|
|
174
173
|
.sort_by { |_, quality_preference| -(quality_preference ? quality_preference.to_f : 1.0) }
|
175
174
|
.flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
|
176
175
|
end
|
176
|
+
|
177
|
+
def rewind_input(input)
|
178
|
+
input.rewind if input.respond_to?(:rewind)
|
179
|
+
end
|
177
180
|
end
|
178
181
|
end
|
179
182
|
end
|