grape 1.3.3 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|