grape 2.0.0 → 2.2.0
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 +96 -1
- data/README.md +364 -317
- data/UPGRADING.md +205 -7
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- data/lib/grape/content_types.rb +13 -10
- 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/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +51 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +43 -35
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/error_formatter.rb +13 -25
- 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/formatter.rb +15 -25
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +4 -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 +14 -15
- data/lib/grape/middleware/error.rb +61 -54
- data/lib/grape/middleware/formatter.rb +18 -15
- 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 +8 -33
- data/lib/grape/middleware/versioner/header.rb +62 -123
- data/lib/grape/middleware/versioner/param.rb +5 -23
- data/lib/grape/middleware/versioner/path.rb +11 -33
- data/lib/grape/middleware/versioner.rb +5 -14
- data/lib/grape/middleware/versioner_helpers.rb +75 -0
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/parser.rb +8 -24
- 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/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 +49 -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 +31 -38
- 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/util/registrable.rb +0 -15
@@ -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 DSL
|
7
5
|
module InsideRoute
|
@@ -31,11 +29,17 @@ module Grape
|
|
31
29
|
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
|
32
30
|
declared_params ||= optioned_declared_params(**options)
|
33
31
|
|
34
|
-
if passed_params.is_a?(Array)
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
res = if passed_params.is_a?(Array)
|
33
|
+
declared_array(passed_params, options, declared_params, params_nested_path)
|
34
|
+
else
|
35
|
+
declared_hash(passed_params, options, declared_params, params_nested_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
if (key_maps = namespace_stackable(:contract_key_map))
|
39
|
+
key_maps.each { |key_map| key_map.write(passed_params, res) }
|
38
40
|
end
|
41
|
+
|
42
|
+
res
|
39
43
|
end
|
40
44
|
|
41
45
|
private
|
@@ -99,7 +103,7 @@ module Grape
|
|
99
103
|
|
100
104
|
route_options_params = options[:route_options][:params] || {}
|
101
105
|
type = route_options_params.dig(key, :type)
|
102
|
-
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?(key) }
|
106
|
+
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
|
103
107
|
|
104
108
|
if type == 'Hash' && !has_children
|
105
109
|
{}
|
@@ -159,12 +163,37 @@ module Grape
|
|
159
163
|
# end user with the specified message.
|
160
164
|
#
|
161
165
|
# @param message [String] The message to display.
|
162
|
-
# @param status [Integer]
|
166
|
+
# @param status [Integer] The HTTP Status Code. Defaults to default_error_status, 500 if not set.
|
163
167
|
# @param additional_headers [Hash] Addtional headers for the response.
|
164
|
-
|
165
|
-
|
168
|
+
# @param backtrace [Array<String>] The backtrace of the exception that caused the error.
|
169
|
+
# @param original_exception [Exception] The original exception that caused the error.
|
170
|
+
def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
|
171
|
+
status = self.status(status || namespace_inheritable(:default_error_status))
|
166
172
|
headers = additional_headers.present? ? header.merge(additional_headers) : header
|
167
|
-
throw :error,
|
173
|
+
throw :error,
|
174
|
+
message: message,
|
175
|
+
status: status,
|
176
|
+
headers: headers,
|
177
|
+
backtrace: backtrace,
|
178
|
+
original_exception: original_exception
|
179
|
+
end
|
180
|
+
|
181
|
+
# Creates a Rack response based on the provided message, status, and headers.
|
182
|
+
# The content type in the headers is set to the default content type unless provided.
|
183
|
+
# The message is HTML-escaped if the content type is 'text/html'.
|
184
|
+
#
|
185
|
+
# @param message [String] The content of the response.
|
186
|
+
# @param status [Integer] The HTTP status code.
|
187
|
+
# @params headers [Hash] (optional) Headers for the response
|
188
|
+
# (default: {Rack::CONTENT_TYPE => content_type}).
|
189
|
+
#
|
190
|
+
# Returns:
|
191
|
+
# A Rack::Response object containing the specified message, status, and headers.
|
192
|
+
#
|
193
|
+
def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
|
194
|
+
Grape.deprecator.warn('The rack_response method has been deprecated, use error! instead.')
|
195
|
+
message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
|
196
|
+
Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), headers)
|
168
197
|
end
|
169
198
|
|
170
199
|
# Redirect to a new url.
|
@@ -178,7 +207,7 @@ module Grape
|
|
178
207
|
if permanent
|
179
208
|
status 301
|
180
209
|
body_message ||= "This resource has been moved permanently to #{url}."
|
181
|
-
elsif
|
210
|
+
elsif http_version == 'HTTP/1.1' && !request.get?
|
182
211
|
status 303
|
183
212
|
body_message ||= "An alternate resource is located at #{url}."
|
184
213
|
else
|
@@ -204,10 +233,9 @@ module Grape
|
|
204
233
|
when nil
|
205
234
|
return @status if instance_variable_defined?(:@status) && @status
|
206
235
|
|
207
|
-
|
208
|
-
when Grape::Http::Headers::POST
|
236
|
+
if request.post?
|
209
237
|
201
|
210
|
-
|
238
|
+
elsif request.delete?
|
211
239
|
if instance_variable_defined?(:@body) && @body.present?
|
212
240
|
200
|
213
241
|
else
|
@@ -436,6 +464,14 @@ module Grape
|
|
436
464
|
embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
|
437
465
|
entity_class.represent(object, **embeds.merge(options))
|
438
466
|
end
|
467
|
+
|
468
|
+
def http_version
|
469
|
+
env['HTTP_VERSION'] || env[Rack::SERVER_PROTOCOL]
|
470
|
+
end
|
471
|
+
|
472
|
+
def context
|
473
|
+
self
|
474
|
+
end
|
439
475
|
end
|
440
476
|
end
|
441
477
|
end
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -130,7 +130,7 @@ module Grape
|
|
130
130
|
|
131
131
|
opts = attrs.extract_options!.clone
|
132
132
|
opts[:presence] = { value: true, message: opts[:message] }
|
133
|
-
opts = @group.
|
133
|
+
opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
|
134
134
|
|
135
135
|
if opts[:using]
|
136
136
|
require_required_and_optional_fields(attrs.first, opts)
|
@@ -149,7 +149,7 @@ module Grape
|
|
149
149
|
|
150
150
|
opts = attrs.extract_options!.clone
|
151
151
|
type = opts[:type]
|
152
|
-
opts = @group.
|
152
|
+
opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
|
153
153
|
|
154
154
|
# check type for optional parameter group
|
155
155
|
if attrs && block
|
@@ -170,7 +170,8 @@ module Grape
|
|
170
170
|
# @param (see #requires)
|
171
171
|
# @option (see #requires)
|
172
172
|
def with(*attrs, &block)
|
173
|
-
|
173
|
+
new_group_attrs = [@group, attrs.clone.first].compact.reduce(&:deep_merge)
|
174
|
+
new_group_scope([new_group_attrs], &block)
|
174
175
|
end
|
175
176
|
|
176
177
|
# Disallow the given parameters to be present in the same request.
|
@@ -230,7 +231,7 @@ module Grape
|
|
230
231
|
|
231
232
|
alias group requires
|
232
233
|
|
233
|
-
class EmptyOptionalValue; end
|
234
|
+
class EmptyOptionalValue; end # rubocop:disable Lint/EmptyClass
|
234
235
|
|
235
236
|
def map_params(params, element, is_array = false)
|
236
237
|
if params.is_a?(Array)
|
@@ -17,18 +17,16 @@ module Grape
|
|
17
17
|
# Specify the format for the API's serializers.
|
18
18
|
# May be `:json`, `:xml`, `:txt`, etc.
|
19
19
|
def format(new_format = nil)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
namespace_inheritable(:format)
|
31
|
-
end
|
20
|
+
return namespace_inheritable(:format) unless new_format
|
21
|
+
|
22
|
+
symbolic_new_format = new_format.to_sym
|
23
|
+
namespace_inheritable(:format, symbolic_new_format)
|
24
|
+
namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(symbolic_new_format))
|
25
|
+
|
26
|
+
content_type = content_types[symbolic_new_format]
|
27
|
+
raise Grape::Exceptions::MissingMimeType.new(new_format) unless content_type
|
28
|
+
|
29
|
+
namespace_stackable(:content_types, symbolic_new_format => content_type)
|
32
30
|
end
|
33
31
|
|
34
32
|
# Specify a custom formatter for a content-type.
|
@@ -43,12 +41,10 @@ module Grape
|
|
43
41
|
|
44
42
|
# Specify a default error formatter.
|
45
43
|
def default_error_formatter(new_formatter_name = nil)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
namespace_inheritable(:default_error_formatter)
|
51
|
-
end
|
44
|
+
return namespace_inheritable(:default_error_formatter) unless new_formatter_name
|
45
|
+
|
46
|
+
new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name)
|
47
|
+
namespace_inheritable(:default_error_formatter, new_formatter)
|
52
48
|
end
|
53
49
|
|
54
50
|
def error_formatter(format, options)
|
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
|
@@ -114,10 +114,10 @@ module Grape
|
|
114
114
|
# Update our settings from a given set of stackable parameters. Used when
|
115
115
|
# the endpoint's API is mounted under another one.
|
116
116
|
def inherit_settings(namespace_stackable)
|
117
|
-
|
117
|
+
parent_validations = namespace_stackable[:validations]
|
118
|
+
inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
|
118
119
|
parent_declared_params = namespace_stackable[:declared_params]
|
119
|
-
|
120
|
-
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
|
120
|
+
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?
|
121
121
|
|
122
122
|
endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
|
123
123
|
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,10 @@ module Grape
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def prepare_version
|
193
|
-
version = namespace_inheritable(:version)
|
194
|
-
return if version.
|
193
|
+
version = namespace_inheritable(:version)
|
194
|
+
return if version.blank?
|
195
195
|
|
196
|
-
version.length == 1 ? version.first
|
196
|
+
version.length == 1 ? version.first : version
|
197
197
|
end
|
198
198
|
|
199
199
|
def merge_route_options(**default)
|
@@ -205,8 +205,10 @@ module Grape
|
|
205
205
|
end
|
206
206
|
|
207
207
|
def prepare_path(path)
|
208
|
-
|
209
|
-
|
208
|
+
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
|
209
|
+
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
|
210
|
+
path_settings = namespace_stackable_hash.merge!(namespace_inheritable_hash)
|
211
|
+
Path.new(path, namespace, path_settings)
|
210
212
|
end
|
211
213
|
|
212
214
|
def namespace
|
@@ -230,15 +232,24 @@ module Grape
|
|
230
232
|
options[:app].endpoints if options[:app].respond_to?(:endpoints)
|
231
233
|
end
|
232
234
|
|
233
|
-
def equals?(
|
234
|
-
(options ==
|
235
|
+
def equals?(endpoint)
|
236
|
+
(options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
|
237
|
+
end
|
238
|
+
|
239
|
+
# The purpose of this override is solely for stripping internals when an error occurs while calling
|
240
|
+
# an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
|
241
|
+
# Otherwise, it calls super.
|
242
|
+
def inspect
|
243
|
+
return super unless env
|
244
|
+
|
245
|
+
"#{self.class} in '#{route.origin}' endpoint"
|
235
246
|
end
|
236
247
|
|
237
248
|
protected
|
238
249
|
|
239
250
|
def run
|
240
251
|
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
|
241
|
-
@header =
|
252
|
+
@header = Grape::Util::Header.new
|
242
253
|
@request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
|
243
254
|
@params = @request.params
|
244
255
|
@headers = @request.headers
|
@@ -279,36 +290,39 @@ module Grape
|
|
279
290
|
def build_stack(helpers)
|
280
291
|
stack = Grape::Middleware::Stack.new
|
281
292
|
|
293
|
+
content_types = namespace_stackable_with_hash(:content_types)
|
294
|
+
format = namespace_inheritable(:format)
|
295
|
+
|
282
296
|
stack.use Rack::Head
|
283
297
|
stack.use Class.new(Grape::Middleware::Error),
|
284
298
|
helpers: helpers,
|
285
|
-
format:
|
286
|
-
content_types:
|
299
|
+
format: format,
|
300
|
+
content_types: content_types,
|
287
301
|
default_status: namespace_inheritable(:default_error_status),
|
288
302
|
rescue_all: namespace_inheritable(:rescue_all),
|
289
303
|
rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
|
290
304
|
default_error_formatter: namespace_inheritable(:default_error_formatter),
|
291
305
|
error_formatters: namespace_stackable_with_hash(:error_formatters),
|
292
|
-
rescue_options: namespace_stackable_with_hash(:rescue_options)
|
293
|
-
rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers)
|
294
|
-
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers)
|
306
|
+
rescue_options: namespace_stackable_with_hash(:rescue_options),
|
307
|
+
rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
|
308
|
+
base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
|
295
309
|
all_rescue_handler: namespace_inheritable(:all_rescue_handler),
|
296
310
|
grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
|
297
311
|
|
298
312
|
stack.concat namespace_stackable(:middleware)
|
299
313
|
|
300
|
-
if namespace_inheritable(:version)
|
314
|
+
if namespace_inheritable(:version).present?
|
301
315
|
stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
|
302
|
-
versions: namespace_inheritable(:version)
|
316
|
+
versions: namespace_inheritable(:version).flatten,
|
303
317
|
version_options: namespace_inheritable(:version_options),
|
304
318
|
prefix: namespace_inheritable(:root_prefix),
|
305
319
|
mount_path: namespace_stackable(:mount_path).first
|
306
320
|
end
|
307
321
|
|
308
322
|
stack.use Grape::Middleware::Formatter,
|
309
|
-
format:
|
323
|
+
format: format,
|
310
324
|
default_format: namespace_inheritable(:default_format) || :txt,
|
311
|
-
content_types:
|
325
|
+
content_types: content_types,
|
312
326
|
formatters: namespace_stackable_with_hash(:formatters),
|
313
327
|
parsers: namespace_stackable_with_hash(:parsers)
|
314
328
|
|
@@ -319,7 +333,9 @@ module Grape
|
|
319
333
|
|
320
334
|
def build_helpers
|
321
335
|
helpers = namespace_stackable(:helpers)
|
322
|
-
|
336
|
+
return if helpers.empty?
|
337
|
+
|
338
|
+
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
|
323
339
|
end
|
324
340
|
|
325
341
|
private :build_stack, :build_helpers
|
@@ -338,7 +354,7 @@ module Grape
|
|
338
354
|
@lazy_initialize_lock.synchronize do
|
339
355
|
return true if @lazy_initialized
|
340
356
|
|
341
|
-
@helpers = build_helpers
|
357
|
+
@helpers = build_helpers&.tap { |mod| self.class.include mod }
|
342
358
|
@app = options[:app] || build_stack(@helpers)
|
343
359
|
|
344
360
|
@lazy_initialized = true
|
@@ -401,15 +417,7 @@ module Grape
|
|
401
417
|
|
402
418
|
def options?
|
403
419
|
options[:options_route_enabled] &&
|
404
|
-
env[
|
405
|
-
end
|
406
|
-
|
407
|
-
def method_missing(name, *_args)
|
408
|
-
raise NoMethodError.new("undefined method `#{name}' for #{self.class} in `#{route.origin}' endpoint")
|
409
|
-
end
|
410
|
-
|
411
|
-
def respond_to_missing?(method_name, include_private = false)
|
412
|
-
super
|
420
|
+
env[Rack::REQUEST_METHOD] == Rack::OPTIONS
|
413
421
|
end
|
414
422
|
end
|
415
423
|
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'
|
@@ -9,17 +9,18 @@ module Grape
|
|
9
9
|
def call(message, backtrace, options = {}, env = nil, original_exception = nil)
|
10
10
|
result = wrap_message(present(message, env))
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
|
12
|
+
result = merge_rescue_options(result, backtrace, options, original_exception) if result.is_a?(Hash)
|
13
|
+
|
15
14
|
::Grape::Json.dump(result)
|
16
15
|
end
|
17
16
|
|
18
17
|
private
|
19
18
|
|
20
19
|
def wrap_message(message)
|
21
|
-
if message.is_a?(
|
20
|
+
if message.is_a?(Hash)
|
22
21
|
message
|
22
|
+
elsif message.is_a?(Exceptions::ValidationErrors)
|
23
|
+
message.as_json
|
23
24
|
else
|
24
25
|
{ error: ensure_utf8(message) }
|
25
26
|
end
|
@@ -30,6 +31,14 @@ module Grape
|
|
30
31
|
|
31
32
|
message.encode('UTF-8', invalid: :replace, undef: :replace)
|
32
33
|
end
|
34
|
+
|
35
|
+
def merge_rescue_options(result, backtrace, options, original_exception)
|
36
|
+
rescue_options = options[:rescue_options] || {}
|
37
|
+
result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
|
38
|
+
result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
33
42
|
end
|
34
43
|
end
|
35
44
|
end
|
@@ -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
|
@@ -2,34 +2,22 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module ErrorFormatter
|
5
|
-
|
5
|
+
module_function
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
xml: Grape::ErrorFormatter::Xml
|
15
|
-
}
|
16
|
-
end
|
7
|
+
DEFAULTS = {
|
8
|
+
serializable_hash: Grape::ErrorFormatter::Json,
|
9
|
+
json: Grape::ErrorFormatter::Json,
|
10
|
+
jsonapi: Grape::ErrorFormatter::Json,
|
11
|
+
txt: Grape::ErrorFormatter::Txt,
|
12
|
+
xml: Grape::ErrorFormatter::Xml
|
13
|
+
}.freeze
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
|
16
|
+
select_formatter(error_formatters, format) || default_error_formatter || DEFAULTS[:txt]
|
17
|
+
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
case spec
|
25
|
-
when nil
|
26
|
-
options[:default_error_formatter] || Grape::ErrorFormatter::Txt
|
27
|
-
when Symbol
|
28
|
-
method(spec)
|
29
|
-
else
|
30
|
-
spec
|
31
|
-
end
|
32
|
-
end
|
19
|
+
def select_formatter(error_formatters, format)
|
20
|
+
error_formatters&.key?(format) ? error_formatters[format] : DEFAULTS[format]
|
33
21
|
end
|
34
22
|
end
|
35
23
|
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
|