grape 1.3.3 → 1.6.2
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 +111 -2
- data/CONTRIBUTING.md +2 -1
- data/README.md +135 -23
- data/UPGRADING.md +237 -46
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +34 -42
- data/lib/grape/api.rb +21 -16
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +8 -5
- data/lib/grape/dsl/inside_route.rb +72 -53
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +8 -9
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +29 -42
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/auth/dsl.rb +7 -1
- data/lib/grape/middleware/base.rb +6 -3
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +7 -7
- data/lib/grape/middleware/stack.rb +10 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +4 -1
- data/lib/grape/router/attribute_translator.rb +3 -3
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +31 -30
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +2 -2
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +4 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +97 -62
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +4 -5
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +8 -5
- data/lib/grape/validations/validators/allow_blank.rb +9 -7
- data/lib/grape/validations/validators/as.rb +6 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
- data/lib/grape/validations/validators/base.rb +74 -69
- data/lib/grape/validations/validators/coerce.rb +63 -76
- data/lib/grape/validations/validators/default.rb +36 -34
- data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
- data/lib/grape/validations/validators/except_values.rb +13 -11
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -19
- data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
- data/lib/grape/validations/validators/presence.rb +7 -4
- data/lib/grape/validations/validators/regexp.rb +8 -5
- data/lib/grape/validations/validators/same_as.rb +18 -15
- data/lib/grape/validations/validators/values.rb +61 -56
- data/lib/grape/validations.rb +6 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +7 -3
- data/spec/grape/api/custom_validations_spec.rb +77 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -3
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
- data/spec/grape/api/invalid_format_spec.rb +2 -0
- data/spec/grape/api/recognize_path_spec.rb +1 -1
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
- data/spec/grape/api_remount_spec.rb +25 -19
- data/spec/grape/api_spec.rb +576 -211
- data/spec/grape/dsl/callbacks_spec.rb +2 -1
- data/spec/grape/dsl/headers_spec.rb +39 -9
- data/spec/grape/dsl/helpers_spec.rb +3 -2
- data/spec/grape/dsl/inside_route_spec.rb +185 -34
- data/spec/grape/dsl/logger_spec.rb +16 -18
- data/spec/grape/dsl/middleware_spec.rb +2 -1
- data/spec/grape/dsl/parameters_spec.rb +2 -0
- data/spec/grape/dsl/request_response_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +10 -7
- data/spec/grape/endpoint/declared_spec.rb +848 -0
- data/spec/grape/endpoint_spec.rb +77 -589
- data/spec/grape/entity_spec.rb +29 -23
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
- data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
- data/spec/grape/exceptions/validation_spec.rb +5 -3
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
- data/spec/grape/integration/rack_sendfile_spec.rb +13 -9
- data/spec/grape/loading_spec.rb +8 -8
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
- data/spec/grape/middleware/auth/strategies_spec.rb +61 -21
- data/spec/grape/middleware/base_spec.rb +24 -15
- data/spec/grape/middleware/error_spec.rb +3 -3
- data/spec/grape/middleware/exception_spec.rb +111 -161
- data/spec/grape/middleware/formatter_spec.rb +28 -7
- data/spec/grape/middleware/globals_spec.rb +7 -4
- data/spec/grape/middleware/stack_spec.rb +15 -12
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
- data/spec/grape/middleware/versioner/header_spec.rb +14 -13
- data/spec/grape/middleware/versioner/param_spec.rb +7 -1
- data/spec/grape/middleware/versioner/path_spec.rb +5 -1
- data/spec/grape/middleware/versioner_spec.rb +1 -1
- data/spec/grape/parser_spec.rb +4 -0
- data/spec/grape/path_spec.rb +52 -52
- data/spec/grape/presenters/presenter_spec.rb +7 -6
- data/spec/grape/request_spec.rb +6 -4
- data/spec/grape/util/inheritable_setting_spec.rb +7 -7
- data/spec/grape/util/inheritable_values_spec.rb +3 -2
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
- data/spec/grape/util/stackable_values_spec.rb +7 -5
- data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +14 -3
- data/spec/grape/validations/params_scope_spec.rb +72 -10
- data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
- data/spec/grape/validations/types_spec.rb +8 -8
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
- data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
- data/spec/grape/validations/validators/coerce_spec.rb +248 -33
- data/spec/grape/validations/validators/default_spec.rb +121 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
- data/spec/grape/validations/validators/except_values_spec.rb +4 -3
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
- data/spec/grape/validations/validators/presence_spec.rb +16 -1
- data/spec/grape/validations/validators/regexp_spec.rb +25 -31
- data/spec/grape/validations/validators/same_as_spec.rb +14 -20
- data/spec/grape/validations/validators/values_spec.rb +183 -178
- data/spec/grape/validations_spec.rb +342 -29
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/integration/multi_json/json_spec.rb +1 -1
- data/spec/integration/multi_xml/xml_spec.rb +1 -1
- data/spec/shared/versioning_examples.rb +32 -29
- data/spec/spec_helper.rb +12 -12
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +110 -102
data/lib/grape/endpoint.rb
CHANGED
@@ -20,7 +20,8 @@ module Grape
|
|
20
20
|
def before_each(new_setup = false, &block)
|
21
21
|
@before_each ||= []
|
22
22
|
if new_setup == false
|
23
|
-
return @before_each unless
|
23
|
+
return @before_each unless block
|
24
|
+
|
24
25
|
@before_each << block
|
25
26
|
else
|
26
27
|
@before_each = [new_setup]
|
@@ -46,9 +47,7 @@ module Grape
|
|
46
47
|
# @return [Proc]
|
47
48
|
# @raise [NameError] an instance method with the same name already exists
|
48
49
|
def generate_api_method(method_name, &block)
|
49
|
-
if method_defined?(method_name)
|
50
|
-
raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
|
51
|
-
end
|
50
|
+
raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") if method_defined?(method_name)
|
52
51
|
|
53
52
|
define_method(method_name, &block)
|
54
53
|
method = instance_method(method_name)
|
@@ -80,7 +79,10 @@ module Grape
|
|
80
79
|
|
81
80
|
self.inheritable_setting = new_settings.point_in_time_copy
|
82
81
|
|
83
|
-
|
82
|
+
# now +namespace_stackable(:declared_params)+ contains all params defined for
|
83
|
+
# this endpoint and its parents, but later it will be cleaned up,
|
84
|
+
# see +reset_validations!+ in lib/grape/dsl/validations.rb
|
85
|
+
route_setting(:declared_params, namespace_stackable(:declared_params).flatten)
|
84
86
|
route_setting(:saved_validations, namespace_stackable(:validations))
|
85
87
|
|
86
88
|
namespace_stackable(:representations, []) unless namespace_stackable(:representations)
|
@@ -99,11 +101,11 @@ module Grape
|
|
99
101
|
@block = nil
|
100
102
|
|
101
103
|
@status = nil
|
102
|
-
@
|
104
|
+
@stream = nil
|
103
105
|
@body = nil
|
104
106
|
@proc = nil
|
105
107
|
|
106
|
-
return unless
|
108
|
+
return unless block
|
107
109
|
|
108
110
|
@source = block
|
109
111
|
@block = self.class.generate_api_method(method_name, &block)
|
@@ -115,12 +117,9 @@ module Grape
|
|
115
117
|
inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
|
116
118
|
parent_declared_params = namespace_stackable[:declared_params]
|
117
119
|
|
118
|
-
if parent_declared_params
|
119
|
-
inheritable_setting.route[:declared_params] ||= []
|
120
|
-
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
|
121
|
-
end
|
120
|
+
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
|
122
121
|
|
123
|
-
endpoints
|
122
|
+
endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
|
124
123
|
end
|
125
124
|
|
126
125
|
def require_option(options, key)
|
@@ -140,7 +139,7 @@ module Grape
|
|
140
139
|
end
|
141
140
|
|
142
141
|
def reset_routes!
|
143
|
-
endpoints
|
142
|
+
endpoints&.each(&:reset_routes!)
|
144
143
|
@namespace = nil
|
145
144
|
@routes = nil
|
146
145
|
end
|
@@ -152,13 +151,9 @@ module Grape
|
|
152
151
|
reset_routes!
|
153
152
|
routes.each do |route|
|
154
153
|
methods = [route.request_method]
|
155
|
-
if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
|
156
|
-
methods << Grape::Http::Headers::HEAD
|
157
|
-
end
|
154
|
+
methods << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
|
158
155
|
methods.each do |method|
|
159
|
-
unless route.request_method == method
|
160
|
-
route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h)
|
161
|
-
end
|
156
|
+
route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
|
162
157
|
router.append(route.apply(self))
|
163
158
|
end
|
164
159
|
end
|
@@ -190,7 +185,7 @@ module Grape
|
|
190
185
|
requirements: prepare_routes_requirements,
|
191
186
|
prefix: namespace_inheritable(:root_prefix),
|
192
187
|
anchor: options[:route_options].fetch(:anchor, true),
|
193
|
-
settings: inheritable_setting.route.except(:
|
188
|
+
settings: inheritable_setting.route.except(:declared_params, :saved_validations),
|
194
189
|
forward_match: options[:forward_match]
|
195
190
|
}
|
196
191
|
end
|
@@ -198,6 +193,7 @@ module Grape
|
|
198
193
|
def prepare_version
|
199
194
|
version = namespace_inheritable(:version) || []
|
200
195
|
return if version.empty?
|
196
|
+
|
201
197
|
version.length == 1 ? version.first.to_s : version
|
202
198
|
end
|
203
199
|
|
@@ -232,7 +228,7 @@ module Grape
|
|
232
228
|
# Return the collection of endpoints within this endpoint.
|
233
229
|
# This is the case when an Grape::API mounts another Grape::API.
|
234
230
|
def endpoints
|
235
|
-
options[:app].endpoints if options[:app]
|
231
|
+
options[:app].endpoints if options[:app].respond_to?(:endpoints)
|
236
232
|
end
|
237
233
|
|
238
234
|
def equals?(e)
|
@@ -253,14 +249,14 @@ module Grape
|
|
253
249
|
run_filters befores, :before
|
254
250
|
|
255
251
|
if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
|
256
|
-
raise Grape::Exceptions::MethodNotAllowed
|
252
|
+
raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
|
253
|
+
|
257
254
|
header 'Allow', allowed_methods
|
258
255
|
response_object = ''
|
259
256
|
status 204
|
260
257
|
else
|
261
258
|
run_filters before_validations, :before_validation
|
262
259
|
run_validators validations, request
|
263
|
-
remove_renamed_params
|
264
260
|
run_filters after_validations, :after_validation
|
265
261
|
response_object = execute
|
266
262
|
end
|
@@ -271,8 +267,8 @@ module Grape
|
|
271
267
|
# status verifies body presence when DELETE
|
272
268
|
@body ||= response_object
|
273
269
|
|
274
|
-
# The body commonly is an Array of Strings, the application instance itself, or a
|
275
|
-
response_object =
|
270
|
+
# The body commonly is an Array of Strings, the application instance itself, or a Stream-like object
|
271
|
+
response_object = stream || [body]
|
276
272
|
|
277
273
|
[status, header, response_object]
|
278
274
|
ensure
|
@@ -326,14 +322,7 @@ module Grape
|
|
326
322
|
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
|
327
323
|
end
|
328
324
|
|
329
|
-
|
330
|
-
return unless route_setting(:renamed_params)
|
331
|
-
route_setting(:renamed_params).flat_map(&:keys).each do |renamed_param|
|
332
|
-
@params.delete(renamed_param)
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
private :build_stack, :build_helpers, :remove_renamed_params
|
325
|
+
private :build_stack, :build_helpers
|
337
326
|
|
338
327
|
def execute
|
339
328
|
@block ? @block.call(self) : nil
|
@@ -363,15 +352,13 @@ module Grape
|
|
363
352
|
|
364
353
|
ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
|
365
354
|
validators.each do |validator|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
break if validator.fail_fast?
|
374
|
-
end
|
355
|
+
validator.validate(request)
|
356
|
+
rescue Grape::Exceptions::Validation => e
|
357
|
+
validation_errors << e
|
358
|
+
break if validator.fail_fast?
|
359
|
+
rescue Grape::Exceptions::ValidationArrayErrors => e
|
360
|
+
validation_errors.concat e.errors
|
361
|
+
break if validator.fail_fast?
|
375
362
|
end
|
376
363
|
end
|
377
364
|
|
@@ -10,12 +10,8 @@ module Grape
|
|
10
10
|
result = wrap_message(present(message, env))
|
11
11
|
|
12
12
|
rescue_options = options[:rescue_options] || {}
|
13
|
-
if rescue_options[:backtrace] && backtrace && !backtrace.empty?
|
14
|
-
|
15
|
-
end
|
16
|
-
if rescue_options[:original_exception] && original_exception
|
17
|
-
result = result.merge(original_exception: original_exception.inspect)
|
18
|
-
end
|
13
|
+
result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
|
14
|
+
result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
|
19
15
|
::Grape::Json.dump(result)
|
20
16
|
end
|
21
17
|
|
@@ -11,12 +11,8 @@ module Grape
|
|
11
11
|
|
12
12
|
result = message.is_a?(Hash) ? message : { message: message }
|
13
13
|
rescue_options = options[:rescue_options] || {}
|
14
|
-
if rescue_options[:backtrace] && backtrace && !backtrace.empty?
|
15
|
-
|
16
|
-
end
|
17
|
-
if rescue_options[:original_exception] && original_exception
|
18
|
-
result = result.merge(original_exception: original_exception.inspect)
|
19
|
-
end
|
14
|
+
result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
|
15
|
+
result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
|
20
16
|
result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
|
21
17
|
end
|
22
18
|
end
|
@@ -5,8 +5,7 @@ require 'grape/exceptions/base'
|
|
5
5
|
module Grape
|
6
6
|
module Exceptions
|
7
7
|
class Validation < Grape::Exceptions::Base
|
8
|
-
attr_accessor :params
|
9
|
-
attr_accessor :message_key
|
8
|
+
attr_accessor :params, :message_key
|
10
9
|
|
11
10
|
def initialize(params:, message: nil, **args)
|
12
11
|
@params = params
|
@@ -17,7 +16,7 @@ module Grape
|
|
17
16
|
super(**args)
|
18
17
|
end
|
19
18
|
|
20
|
-
#
|
19
|
+
# Remove all the unnecessary stuff from Grape::Exceptions::Base like status
|
21
20
|
# and headers when converting a validation error to json or string
|
22
21
|
def as_json(*_args)
|
23
22
|
to_s
|
data/lib/grape/formatter/json.rb
CHANGED
@@ -8,13 +8,14 @@ module Grape
|
|
8
8
|
return object if object.is_a?(String)
|
9
9
|
return ::Grape::Json.dump(serialize(object)) if serializable?(object)
|
10
10
|
return object.to_json if object.respond_to?(:to_json)
|
11
|
+
|
11
12
|
::Grape::Json.dump(object)
|
12
13
|
end
|
13
14
|
|
14
15
|
private
|
15
16
|
|
16
17
|
def serializable?(object)
|
17
|
-
object.respond_to?(:serializable_hash) || object.is_a?(Array) && object.all? { |o| o.respond_to? :serializable_hash } || object.is_a?(Hash)
|
18
|
+
object.respond_to?(:serializable_hash) || (object.is_a?(Array) && object.all? { |o| o.respond_to? :serializable_hash }) || object.is_a?(Hash)
|
18
19
|
end
|
19
20
|
|
20
21
|
def serialize(object)
|
data/lib/grape/formatter/xml.rb
CHANGED
data/lib/grape/locale/en.yml
CHANGED
@@ -44,6 +44,7 @@ en:
|
|
44
44
|
"when specifying %{body_format} as content-type, you must pass valid
|
45
45
|
%{body_format} in the request's 'body'
|
46
46
|
"
|
47
|
+
empty_message_body: 'Empty message body supplied with %{body_format} content-type'
|
47
48
|
invalid_accept_header:
|
48
49
|
problem: 'Invalid accept header'
|
49
50
|
resolution: '%{message}'
|
@@ -51,4 +52,3 @@ en:
|
|
51
52
|
problem: 'Invalid version header'
|
52
53
|
resolution: '%{message}'
|
53
54
|
invalid_response: 'Invalid response'
|
54
|
-
|
@@ -10,9 +10,9 @@ module Grape
|
|
10
10
|
|
11
11
|
attr_accessor :options, :app, :env
|
12
12
|
|
13
|
-
def initialize(app,
|
13
|
+
def initialize(app, *options)
|
14
14
|
@app = app
|
15
|
-
@options = options
|
15
|
+
@options = options.shift
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(env)
|
@@ -23,7 +23,7 @@ module Grape
|
|
23
23
|
self.env = env
|
24
24
|
|
25
25
|
if options.key?(:type)
|
26
|
-
auth_proc
|
26
|
+
auth_proc = options[:proc]
|
27
27
|
auth_proc_context = context
|
28
28
|
|
29
29
|
strategy_info = Grape::Middleware::Auth::Strategies[options[:type]]
|
@@ -32,7 +32,13 @@ module Grape
|
|
32
32
|
|
33
33
|
def http_digest(options = {}, &block)
|
34
34
|
options[:realm] ||= 'API Authorization'
|
35
|
-
|
35
|
+
|
36
|
+
if options[:realm].respond_to?(:values_at)
|
37
|
+
options[:realm][:opaque] ||= 'secret'
|
38
|
+
else
|
39
|
+
options[:opaque] ||= 'secret'
|
40
|
+
end
|
41
|
+
|
36
42
|
auth :http_digest, options, &block
|
37
43
|
end
|
38
44
|
end
|
@@ -8,15 +8,16 @@ module Grape
|
|
8
8
|
include Helpers
|
9
9
|
|
10
10
|
attr_reader :app, :env, :options
|
11
|
+
|
11
12
|
TEXT_HTML = 'text/html'
|
12
13
|
|
13
14
|
include Grape::DSL::Headers
|
14
15
|
|
15
16
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
16
17
|
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
17
|
-
def initialize(app,
|
18
|
+
def initialize(app, *options)
|
18
19
|
@app = app
|
19
|
-
@options = default_options.merge(
|
20
|
+
@options = options.any? ? default_options.merge(options.shift) : default_options
|
20
21
|
@app_response = nil
|
21
22
|
end
|
22
23
|
|
@@ -58,7 +59,8 @@ module Grape
|
|
58
59
|
|
59
60
|
def response
|
60
61
|
return @app_response if @app_response.is_a?(Rack::Response)
|
61
|
-
|
62
|
+
|
63
|
+
@app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
|
62
64
|
end
|
63
65
|
|
64
66
|
def content_type_for(format)
|
@@ -83,6 +85,7 @@ module Grape
|
|
83
85
|
|
84
86
|
def merge_headers(response)
|
85
87
|
return unless headers.is_a?(Hash)
|
88
|
+
|
86
89
|
case response
|
87
90
|
when Rack::Response then response.headers.merge!(headers)
|
88
91
|
when Array then response[1].merge!(headers)
|
@@ -19,15 +19,15 @@ module Grape
|
|
19
19
|
rescue_subclasses: true, # rescue subclasses of exceptions listed
|
20
20
|
rescue_options: {
|
21
21
|
backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions
|
22
|
-
original_exception: false
|
22
|
+
original_exception: false # true to display exception
|
23
23
|
},
|
24
24
|
rescue_handlers: {}, # rescue handler blocks
|
25
25
|
base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class
|
26
|
-
all_rescue_handler: nil
|
26
|
+
all_rescue_handler: nil # rescue handler block to rescue from all exceptions
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
-
def initialize(app,
|
30
|
+
def initialize(app, *options)
|
31
31
|
super
|
32
32
|
self.class.send(:include, @options[:helpers]) if @options[:helpers]
|
33
33
|
end
|
@@ -38,15 +38,15 @@ module Grape
|
|
38
38
|
error_response(catch(:error) do
|
39
39
|
return @app.call(@env)
|
40
40
|
end)
|
41
|
-
rescue Exception =>
|
41
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
42
42
|
handler =
|
43
|
-
rescue_handler_for_base_only_class(
|
44
|
-
rescue_handler_for_class_or_its_ancestor(
|
45
|
-
rescue_handler_for_grape_exception(
|
46
|
-
rescue_handler_for_any_class(
|
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
47
|
raise
|
48
48
|
|
49
|
-
run_rescue_handler(handler,
|
49
|
+
run_rescue_handler(handler, e)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -65,15 +65,13 @@ module Grape
|
|
65
65
|
message = error[:message] || options[:default_message]
|
66
66
|
headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }
|
67
67
|
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
68
|
-
backtrace = error[:backtrace] || error[:original_exception]
|
68
|
+
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
69
69
|
original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil
|
70
70
|
rack_response(format_message(message, backtrace, original_exception), status, headers)
|
71
71
|
end
|
72
72
|
|
73
73
|
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
|
74
|
-
if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
75
|
-
message = ERB::Util.html_escape(message)
|
76
|
-
end
|
74
|
+
message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
77
75
|
Rack::Response.new([message], status, headers)
|
78
76
|
end
|
79
77
|
|
@@ -22,6 +22,7 @@ module Grape
|
|
22
22
|
|
23
23
|
def after
|
24
24
|
return unless @app_response
|
25
|
+
|
25
26
|
status, headers, bodies = *@app_response
|
26
27
|
|
27
28
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
@@ -36,9 +37,9 @@ module Grape
|
|
36
37
|
def build_formatted_response(status, headers, bodies)
|
37
38
|
headers = ensure_content_type(headers)
|
38
39
|
|
39
|
-
if bodies.is_a?(Grape::
|
40
|
-
Grape::
|
41
|
-
resp.body = bodies.
|
40
|
+
if bodies.is_a?(Grape::ServeStream::StreamResponse)
|
41
|
+
Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|
|
42
|
+
resp.body = bodies.stream
|
42
43
|
end
|
43
44
|
else
|
44
45
|
# Allow content-type to be explicitly overwritten
|
@@ -79,7 +80,7 @@ module Grape
|
|
79
80
|
(request.post? || request.put? || request.patch? || request.delete?) &&
|
80
81
|
(!request.form_data? || !request.media_type) &&
|
81
82
|
!request.parseable_data? &&
|
82
|
-
(request.content_length.to_i
|
83
|
+
(request.content_length.to_i.positive? || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
|
83
84
|
|
84
85
|
return unless (input = env[Grape::Env::RACK_INPUT])
|
85
86
|
|
@@ -96,9 +97,7 @@ module Grape
|
|
96
97
|
def read_rack_input(body)
|
97
98
|
fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
|
98
99
|
|
99
|
-
unless content_type_for(fmt)
|
100
|
-
throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported."
|
101
|
-
end
|
100
|
+
throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." unless content_type_for(fmt)
|
102
101
|
parser = Grape::Parser.parser_for fmt, **options
|
103
102
|
if parser
|
104
103
|
begin
|
@@ -145,6 +144,7 @@ module Grape
|
|
145
144
|
fmt = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
|
146
145
|
# avoid symbol memory leak on an unknown format
|
147
146
|
return fmt.to_sym if content_type_for(fmt)
|
147
|
+
|
148
148
|
fmt
|
149
149
|
end
|
150
150
|
|
@@ -30,6 +30,10 @@ module Grape
|
|
30
30
|
def inspect
|
31
31
|
klass.to_s
|
32
32
|
end
|
33
|
+
|
34
|
+
def use_in(builder)
|
35
|
+
builder.use(@klass, *@args, &@block)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
include Enumerable
|
@@ -41,8 +45,8 @@ module Grape
|
|
41
45
|
@others = []
|
42
46
|
end
|
43
47
|
|
44
|
-
def each
|
45
|
-
@middlewares.each
|
48
|
+
def each(&block)
|
49
|
+
@middlewares.each(&block)
|
46
50
|
end
|
47
51
|
|
48
52
|
def size
|
@@ -62,6 +66,7 @@ module Grape
|
|
62
66
|
middleware = self.class::Middleware.new(*args, &block)
|
63
67
|
middlewares.insert(index, middleware)
|
64
68
|
end
|
69
|
+
ruby2_keywords :insert if respond_to?(:ruby2_keywords, true)
|
65
70
|
|
66
71
|
alias insert_before insert
|
67
72
|
|
@@ -69,11 +74,13 @@ module Grape
|
|
69
74
|
index = assert_index(index, :after)
|
70
75
|
insert(index + 1, *args, &block)
|
71
76
|
end
|
77
|
+
ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true)
|
72
78
|
|
73
79
|
def use(*args, &block)
|
74
80
|
middleware = self.class::Middleware.new(*args, &block)
|
75
81
|
middlewares.push(middleware)
|
76
82
|
end
|
83
|
+
ruby2_keywords :use if respond_to?(:ruby2_keywords, true)
|
77
84
|
|
78
85
|
def merge_with(middleware_specs)
|
79
86
|
middleware_specs.each do |operation, *args|
|
@@ -90,7 +97,7 @@ module Grape
|
|
90
97
|
def build(builder = Rack::Builder.new)
|
91
98
|
others.shift(others.size).each(&method(:merge_with))
|
92
99
|
middlewares.each do |m|
|
93
|
-
m.
|
100
|
+
m.use_in(builder)
|
94
101
|
end
|
95
102
|
builder
|
96
103
|
end
|
@@ -22,11 +22,9 @@ module Grape
|
|
22
22
|
def before
|
23
23
|
potential_version = (env[Grape::Http::Headers::HTTP_ACCEPT_VERSION] || '').strip
|
24
24
|
|
25
|
-
if strict?
|
25
|
+
if strict? && potential_version.empty?
|
26
26
|
# If no Accept-Version header:
|
27
|
-
|
28
|
-
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
|
29
|
-
end
|
27
|
+
throw :error, status: 406, headers: error_headers, message: 'Accept-Version header must be set.'
|
30
28
|
end
|
31
29
|
|
32
30
|
return if potential_version.empty?
|
@@ -51,7 +49,7 @@ module Grape
|
|
51
49
|
# of routes (see Grape::Router) for more information). To prevent
|
52
50
|
# this behavior, and not add the `X-Cascade` header, one can set the `:cascade` option to `false`.
|
53
51
|
def cascade?
|
54
|
-
if options[:version_options]
|
52
|
+
if options[:version_options]&.key?(:cascade)
|
55
53
|
options[:version_options][:cascade]
|
56
54
|
else
|
57
55
|
true
|
@@ -26,10 +26,10 @@ module Grape
|
|
26
26
|
# route.
|
27
27
|
class Header < Base
|
28
28
|
VENDOR_VERSION_HEADER_REGEX =
|
29
|
-
/\Avnd\.([a-z0-9.\-_
|
29
|
+
/\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze
|
30
30
|
|
31
|
-
HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_
|
32
|
-
HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_
|
31
|
+
HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#{Regexp.last_match(0)}\^]+/.freeze
|
32
|
+
HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#{Regexp.last_match(0)}\^]+?)(?:-([a-z0-9*.]+))+/.freeze
|
33
33
|
|
34
34
|
def before
|
35
35
|
strict_header_checks if strict?
|
@@ -52,12 +52,14 @@ module Grape
|
|
52
52
|
|
53
53
|
def strict_accept_header_presence_check
|
54
54
|
return unless header.qvalues.empty?
|
55
|
+
|
55
56
|
fail_with_invalid_accept_header!('Accept header must be set.')
|
56
57
|
end
|
57
58
|
|
58
59
|
def strict_version_vendor_accept_header_presence_check
|
59
60
|
return unless versions.present?
|
60
61
|
return if an_accept_header_with_version_and_vendor_is_present?
|
62
|
+
|
61
63
|
fail_with_invalid_accept_header!('API vendor or version not found.')
|
62
64
|
end
|
63
65
|
|
@@ -160,7 +162,7 @@ module Grape
|
|
160
162
|
# information). To prevent # this behavior, and not add the `X-Cascade`
|
161
163
|
# header, one can set the `:cascade` option to `false`.
|
162
164
|
def cascade?
|
163
|
-
if version_options
|
165
|
+
if version_options&.key?(:cascade)
|
164
166
|
version_options[:cascade]
|
165
167
|
else
|
166
168
|
true
|
@@ -32,6 +32,7 @@ module Grape
|
|
32
32
|
def before
|
33
33
|
potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
|
34
34
|
return if potential_version.nil?
|
35
|
+
|
35
36
|
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
36
37
|
env[Grape::Env::API_VERSION] = potential_version
|
37
38
|
env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'English'
|
3
4
|
module Rack
|
4
5
|
module Accept
|
5
6
|
module Header
|
6
|
-
ALLOWED_CHARACTERS = %r{^([a-z*]+)
|
7
|
+
ALLOWED_CHARACTERS = %r{^([a-z*]+)/([a-z0-9*&\^\-_#{$ERROR_INFO}.+]+)(?:;([a-z0-9=;]+))?$}.freeze
|
7
8
|
class << self
|
8
9
|
# Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
|
9
10
|
def parse_media_type(media_type)
|
@@ -37,6 +37,7 @@ module Grape
|
|
37
37
|
pieces = path.split('/')
|
38
38
|
potential_version = pieces[1]
|
39
39
|
return unless potential_version&.match?(options[:pattern])
|
40
|
+
|
40
41
|
throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
41
42
|
env[Grape::Env::API_VERSION] = potential_version
|
42
43
|
end
|
@@ -45,6 +46,7 @@ module Grape
|
|
45
46
|
|
46
47
|
def mounted_path?(path)
|
47
48
|
return false unless mount_path && path.start_with?(mount_path)
|
49
|
+
|
48
50
|
rest = path.slice(mount_path.length..-1)
|
49
51
|
rest.start_with?('/') || rest.empty?
|
50
52
|
end
|
data/lib/grape/parser/json.rb
CHANGED
@@ -8,7 +8,7 @@ module Grape
|
|
8
8
|
::Grape::Json.load(object)
|
9
9
|
rescue ::Grape::Json::ParseError
|
10
10
|
# handle JSON parsing errors via the rescue handlers or provide error message
|
11
|
-
raise Grape::Exceptions::InvalidMessageBody
|
11
|
+
raise Grape::Exceptions::InvalidMessageBody.new('application/json')
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
data/lib/grape/parser/xml.rb
CHANGED
@@ -8,7 +8,7 @@ module Grape
|
|
8
8
|
::Grape::Xml.parse(object)
|
9
9
|
rescue ::Grape::Xml::ParseError
|
10
10
|
# handle XML parsing errors via the rescue handlers or provide error message
|
11
|
-
raise Grape::Exceptions::InvalidMessageBody
|
11
|
+
raise Grape::Exceptions::InvalidMessageBody.new('application/xml')
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|