grape 1.5.3 → 1.7.1
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 +92 -0
- data/CONTRIBUTING.md +32 -1
- data/README.md +176 -25
- data/UPGRADING.md +61 -4
- data/grape.gemspec +6 -6
- data/lib/grape/api/instance.rb +14 -18
- data/lib/grape/api.rb +17 -12
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dry_types.rb +12 -0
- data/lib/grape/dsl/api.rb +0 -2
- data/lib/grape/dsl/callbacks.rb +0 -2
- data/lib/grape/dsl/configuration.rb +0 -2
- data/lib/grape/dsl/desc.rb +4 -20
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +7 -7
- data/lib/grape/dsl/inside_route.rb +43 -30
- data/lib/grape/dsl/middleware.rb +4 -6
- data/lib/grape/dsl/parameters.rb +13 -10
- data/lib/grape/dsl/request_response.rb +9 -8
- data/lib/grape/dsl/routing.rb +6 -4
- data/lib/grape/dsl/settings.rb +5 -7
- data/lib/grape/dsl/validations.rb +0 -15
- data/lib/grape/endpoint.rb +22 -37
- data/lib/grape/error_formatter/json.rb +9 -7
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/base.rb +3 -2
- data/lib/grape/exceptions/missing_group_type.rb +8 -1
- data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
- data/lib/grape/exceptions/validation.rb +1 -6
- 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 +9 -8
- data/lib/grape/middleware/auth/dsl.rb +7 -2
- data/lib/grape/middleware/base.rb +3 -1
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +3 -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/path.rb +1 -0
- data/lib/grape/request.rb +4 -1
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/cache.rb +1 -1
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/json.rb +2 -0
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/attributes_doc.rb +58 -0
- data/lib/grape/validations/params_scope.rb +138 -79
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
- data/lib/grape/validations/types/invalid_value.rb +0 -7
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +16 -8
- data/lib/grape/validations/types/set_coercer.rb +0 -2
- data/lib/grape/validations/types.rb +98 -30
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +82 -70
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/default_validator.rb +51 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +88 -0
- data/lib/grape/validations.rb +16 -6
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +77 -29
- data/spec/grape/api/custom_validations_spec.rb +116 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -5
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
- data/spec/grape/api/documentation_spec.rb +59 -0
- data/spec/grape/api/inherited_helpers_spec.rb +0 -2
- data/spec/grape/api/instance_spec.rb +0 -1
- data/spec/grape/api/invalid_format_spec.rb +2 -2
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/nested_helpers_spec.rb +0 -2
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/parameters_modification_spec.rb +0 -2
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
- data/spec/grape/api/recognize_path_spec.rb +1 -3
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
- data/spec/grape/api/shared_helpers_spec.rb +0 -2
- data/spec/grape/api_remount_spec.rb +16 -16
- data/spec/grape/api_spec.rb +462 -251
- data/spec/grape/config_spec.rb +0 -2
- data/spec/grape/dsl/callbacks_spec.rb +2 -3
- data/spec/grape/dsl/desc_spec.rb +2 -2
- data/spec/grape/dsl/headers_spec.rb +39 -11
- data/spec/grape/dsl/helpers_spec.rb +3 -4
- data/spec/grape/dsl/inside_route_spec.rb +16 -16
- data/spec/grape/dsl/logger_spec.rb +15 -19
- data/spec/grape/dsl/middleware_spec.rb +2 -3
- data/spec/grape/dsl/parameters_spec.rb +2 -2
- data/spec/grape/dsl/request_response_spec.rb +7 -8
- data/spec/grape/dsl/routing_spec.rb +11 -10
- data/spec/grape/dsl/settings_spec.rb +0 -2
- data/spec/grape/dsl/validations_spec.rb +0 -17
- data/spec/grape/endpoint/declared_spec.rb +261 -16
- data/spec/grape/endpoint_spec.rb +88 -59
- data/spec/grape/entity_spec.rb +22 -23
- data/spec/grape/exceptions/base_spec.rb +16 -2
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +64 -24
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
- data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
- data/spec/grape/exceptions/missing_option_spec.rb +1 -3
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
- data/spec/grape/exceptions/validation_spec.rb +5 -5
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
- data/spec/grape/integration/rack_spec.rb +6 -7
- data/spec/grape/loading_spec.rb +8 -10
- data/spec/grape/middleware/auth/base_spec.rb +0 -1
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
- data/spec/grape/middleware/base_spec.rb +28 -19
- data/spec/grape/middleware/error_spec.rb +8 -3
- data/spec/grape/middleware/exception_spec.rb +111 -163
- data/spec/grape/middleware/formatter_spec.rb +33 -14
- data/spec/grape/middleware/globals_spec.rb +7 -6
- data/spec/grape/middleware/stack_spec.rb +14 -14
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
- data/spec/grape/middleware/versioner/header_spec.rb +30 -15
- data/spec/grape/middleware/versioner/param_spec.rb +7 -3
- data/spec/grape/middleware/versioner/path_spec.rb +5 -3
- data/spec/grape/middleware/versioner_spec.rb +1 -3
- data/spec/grape/named_api_spec.rb +0 -2
- data/spec/grape/parser_spec.rb +4 -2
- data/spec/grape/path_spec.rb +52 -54
- data/spec/grape/presenters/presenter_spec.rb +7 -8
- data/spec/grape/request_spec.rb +6 -6
- data/spec/grape/util/inheritable_setting_spec.rb +7 -8
- data/spec/grape/util/inheritable_values_spec.rb +3 -3
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
- data/spec/grape/util/stackable_values_spec.rb +7 -6
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
- data/spec/grape/validations/attributes_doc_spec.rb +153 -0
- data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
- data/spec/grape/validations/params_scope_spec.rb +361 -96
- data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
- data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
- data/spec/grape/validations/types_spec.rb +36 -10
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
- data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
- data/spec/grape/validations/validators/coerce_spec.rb +23 -24
- data/spec/grape/validations/validators/default_spec.rb +72 -80
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
- data/spec/grape/validations/validators/except_values_spec.rb +3 -5
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
- data/spec/grape/validations/validators/presence_spec.rb +16 -3
- data/spec/grape/validations/validators/regexp_spec.rb +25 -33
- data/spec/grape/validations/validators/same_as_spec.rb +14 -22
- data/spec/grape/validations/validators/values_spec.rb +201 -179
- data/spec/grape/validations_spec.rb +171 -79
- data/spec/integration/eager_load/eager_load_spec.rb +2 -2
- data/spec/integration/multi_json/json_spec.rb +1 -3
- data/spec/integration/multi_xml/xml_spec.rb +1 -3
- data/spec/shared/versioning_examples.rb +12 -9
- data/spec/spec_helper.rb +21 -6
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- metadata +41 -29
- data/lib/grape/validations/validators/all_or_none.rb +0 -15
- data/lib/grape/validations/validators/allow_blank.rb +0 -18
- data/lib/grape/validations/validators/as.rb +0 -16
- data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
- data/lib/grape/validations/validators/coerce.rb +0 -91
- data/lib/grape/validations/validators/default.rb +0 -48
- data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
- data/lib/grape/validations/validators/except_values.rb +0 -22
- data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
- data/lib/grape/validations/validators/presence.rb +0 -12
- data/lib/grape/validations/validators/regexp.rb +0 -13
- data/lib/grape/validations/validators/same_as.rb +0 -26
- data/lib/grape/validations/validators/values.rb +0 -83
- data/spec/grape/dsl/configuration_spec.rb +0 -16
- data/spec/grape/validations/attributes_iterator_spec.rb +0 -6
- data/spec/support/eager_load.rb +0 -19
data/lib/grape/dsl/settings.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'active_support/concern'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module DSL
|
7
5
|
# Keeps track of settings (implemented as key-value pairs, grouped by
|
@@ -103,12 +101,14 @@ module Grape
|
|
103
101
|
def namespace_stackable_with_hash(key)
|
104
102
|
settings = get_or_set :namespace_stackable, key, nil
|
105
103
|
return if settings.blank?
|
104
|
+
|
106
105
|
settings.each_with_object({}) { |value, result| result.deep_merge!(value) }
|
107
106
|
end
|
108
107
|
|
109
108
|
def namespace_reverse_stackable_with_hash(key)
|
110
109
|
settings = get_or_set :namespace_reverse_stackable, key, nil
|
111
110
|
return if settings.blank?
|
111
|
+
|
112
112
|
result = {}
|
113
113
|
settings.each do |setting|
|
114
114
|
setting.each do |field, value|
|
@@ -154,10 +154,10 @@ module Grape
|
|
154
154
|
|
155
155
|
# Execute the block within a context where our inheritable settings are forked
|
156
156
|
# to a new copy (see #namespace_start).
|
157
|
-
def within_namespace(&
|
157
|
+
def within_namespace(&block)
|
158
158
|
namespace_start
|
159
159
|
|
160
|
-
result = yield if
|
160
|
+
result = yield if block
|
161
161
|
|
162
162
|
namespace_end
|
163
163
|
reset_validations!
|
@@ -175,9 +175,7 @@ module Grape
|
|
175
175
|
# +inheritable_setting+, however, it doesn't contain any user-defined settings.
|
176
176
|
# Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+
|
177
177
|
# in the chain for every endpoint.
|
178
|
-
if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
|
179
|
-
setting.inherit_from superclass.inheritable_setting
|
180
|
-
end
|
178
|
+
setting.inherit_from superclass.inheritable_setting if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance
|
181
179
|
end
|
182
180
|
end
|
183
181
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'active_support/concern'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module DSL
|
7
5
|
module Validations
|
@@ -32,7 +30,6 @@ module Grape
|
|
32
30
|
unset_namespace_stackable :declared_params
|
33
31
|
unset_namespace_stackable :validations
|
34
32
|
unset_namespace_stackable :params
|
35
|
-
unset_description_field :params
|
36
33
|
end
|
37
34
|
|
38
35
|
# Opens a root-level ParamsScope, defining parameter coercions and
|
@@ -41,18 +38,6 @@ module Grape
|
|
41
38
|
def params(&block)
|
42
39
|
Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
|
43
40
|
end
|
44
|
-
|
45
|
-
def document_attribute(names, opts)
|
46
|
-
setting = description_field(:params)
|
47
|
-
setting ||= description_field(:params, {})
|
48
|
-
Array(names).each do |name|
|
49
|
-
full_name = name[:full_name].to_s
|
50
|
-
setting[full_name] ||= {}
|
51
|
-
setting[full_name].merge!(opts)
|
52
|
-
|
53
|
-
namespace_stackable(:params, full_name => opts)
|
54
|
-
end
|
55
|
-
end
|
56
41
|
end
|
57
42
|
end
|
58
43
|
end
|
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)
|
@@ -106,7 +105,7 @@ module Grape
|
|
106
105
|
@body = nil
|
107
106
|
@proc = nil
|
108
107
|
|
109
|
-
return unless
|
108
|
+
return unless block
|
110
109
|
|
111
110
|
@source = block
|
112
111
|
@block = self.class.generate_api_method(method_name, &block)
|
@@ -118,11 +117,9 @@ module Grape
|
|
118
117
|
inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
|
119
118
|
parent_declared_params = namespace_stackable[:declared_params]
|
120
119
|
|
121
|
-
if parent_declared_params
|
122
|
-
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten)
|
123
|
-
end
|
120
|
+
inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
|
124
121
|
|
125
|
-
endpoints
|
122
|
+
endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
|
126
123
|
end
|
127
124
|
|
128
125
|
def require_option(options, key)
|
@@ -142,7 +139,7 @@ module Grape
|
|
142
139
|
end
|
143
140
|
|
144
141
|
def reset_routes!
|
145
|
-
endpoints
|
142
|
+
endpoints&.each(&:reset_routes!)
|
146
143
|
@namespace = nil
|
147
144
|
@routes = nil
|
148
145
|
end
|
@@ -154,13 +151,9 @@ module Grape
|
|
154
151
|
reset_routes!
|
155
152
|
routes.each do |route|
|
156
153
|
methods = [route.request_method]
|
157
|
-
if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
|
158
|
-
methods << Grape::Http::Headers::HEAD
|
159
|
-
end
|
154
|
+
methods << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
|
160
155
|
methods.each do |method|
|
161
|
-
unless route.request_method == method
|
162
|
-
route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h)
|
163
|
-
end
|
156
|
+
route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
|
164
157
|
router.append(route.apply(self))
|
165
158
|
end
|
166
159
|
end
|
@@ -200,6 +193,7 @@ module Grape
|
|
200
193
|
def prepare_version
|
201
194
|
version = namespace_inheritable(:version) || []
|
202
195
|
return if version.empty?
|
196
|
+
|
203
197
|
version.length == 1 ? version.first.to_s : version
|
204
198
|
end
|
205
199
|
|
@@ -234,7 +228,7 @@ module Grape
|
|
234
228
|
# Return the collection of endpoints within this endpoint.
|
235
229
|
# This is the case when an Grape::API mounts another Grape::API.
|
236
230
|
def endpoints
|
237
|
-
options[:app].endpoints if options[:app]
|
231
|
+
options[:app].endpoints if options[:app].respond_to?(:endpoints)
|
238
232
|
end
|
239
233
|
|
240
234
|
def equals?(e)
|
@@ -256,13 +250,13 @@ module Grape
|
|
256
250
|
|
257
251
|
if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
|
258
252
|
raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
|
253
|
+
|
259
254
|
header 'Allow', allowed_methods
|
260
255
|
response_object = ''
|
261
256
|
status 204
|
262
257
|
else
|
263
258
|
run_filters before_validations, :before_validation
|
264
259
|
run_validators validations, request
|
265
|
-
remove_renamed_params
|
266
260
|
run_filters after_validations, :after_validation
|
267
261
|
response_object = execute
|
268
262
|
end
|
@@ -305,7 +299,7 @@ module Grape
|
|
305
299
|
|
306
300
|
if namespace_inheritable(:version)
|
307
301
|
stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
|
308
|
-
versions: namespace_inheritable(:version)
|
302
|
+
versions: namespace_inheritable(:version)&.flatten,
|
309
303
|
version_options: namespace_inheritable(:version_options),
|
310
304
|
prefix: namespace_inheritable(:root_prefix),
|
311
305
|
mount_path: namespace_stackable(:mount_path).first
|
@@ -328,17 +322,10 @@ module Grape
|
|
328
322
|
Module.new { helpers.each { |mod_to_include| include mod_to_include } }
|
329
323
|
end
|
330
324
|
|
331
|
-
|
332
|
-
return unless route_setting(:renamed_params)
|
333
|
-
route_setting(:renamed_params).flat_map(&:keys).each do |renamed_param|
|
334
|
-
@params.delete(renamed_param)
|
335
|
-
end
|
336
|
-
end
|
337
|
-
|
338
|
-
private :build_stack, :build_helpers, :remove_renamed_params
|
325
|
+
private :build_stack, :build_helpers
|
339
326
|
|
340
327
|
def execute
|
341
|
-
@block
|
328
|
+
@block&.call(self)
|
342
329
|
end
|
343
330
|
|
344
331
|
def helpers
|
@@ -365,15 +352,13 @@ module Grape
|
|
365
352
|
|
366
353
|
ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
|
367
354
|
validators.each do |validator|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
break if validator.fail_fast?
|
376
|
-
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?
|
377
362
|
end
|
378
363
|
end
|
379
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
|
|
@@ -25,9 +21,15 @@ module Grape
|
|
25
21
|
if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
|
26
22
|
message
|
27
23
|
else
|
28
|
-
{ error: message }
|
24
|
+
{ error: ensure_utf8(message) }
|
29
25
|
end
|
30
26
|
end
|
27
|
+
|
28
|
+
def ensure_utf8(message)
|
29
|
+
return message unless message.respond_to? :encode
|
30
|
+
|
31
|
+
message.encode('UTF-8', invalid: :replace, undef: :replace)
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -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
|
@@ -7,11 +7,12 @@ module Grape
|
|
7
7
|
BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
|
8
8
|
FALLBACK_LOCALE = :en
|
9
9
|
|
10
|
-
attr_reader :status, :
|
10
|
+
attr_reader :status, :headers
|
11
11
|
|
12
12
|
def initialize(status: nil, message: nil, headers: nil, **_options)
|
13
|
+
super(message)
|
14
|
+
|
13
15
|
@status = status
|
14
|
-
@message = message
|
15
16
|
@headers = headers
|
16
17
|
end
|
17
18
|
|
@@ -2,10 +2,17 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Exceptions
|
5
|
-
class
|
5
|
+
class MissingGroupType < Base
|
6
6
|
def initialize
|
7
7
|
super(message: compose_message(:missing_group_type))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
Grape::Exceptions::MissingGroupTypeError = Class.new(Grape::Exceptions::MissingGroupType) do
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
warn '[DEPRECATION] `Grape::Exceptions::MissingGroupTypeError` is deprecated. Use `Grape::Exceptions::MissingGroupType` instead.'
|
17
|
+
end
|
18
|
+
end
|
@@ -2,10 +2,17 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Exceptions
|
5
|
-
class
|
5
|
+
class UnsupportedGroupType < Base
|
6
6
|
def initialize
|
7
7
|
super(message: compose_message(:unsupported_group_type))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
Grape::Exceptions::UnsupportedGroupTypeError = Class.new(Grape::Exceptions::UnsupportedGroupType) do
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
warn '[DEPRECATION] `Grape::Exceptions::UnsupportedGroupTypeError` is deprecated. Use `Grape::Exceptions::UnsupportedGroupType` instead.'
|
17
|
+
end
|
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
|
@@ -22,10 +21,6 @@ module Grape
|
|
22
21
|
def as_json(*_args)
|
23
22
|
to_s
|
24
23
|
end
|
25
|
-
|
26
|
-
def to_s
|
27
|
-
message
|
28
|
-
end
|
29
24
|
end
|
30
25
|
end
|
31
26
|
end
|
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
@@ -11,8 +11,8 @@ en:
|
|
11
11
|
except_values: 'has a value not allowed'
|
12
12
|
same_as: 'is not the same as %{parameter}'
|
13
13
|
missing_vendor_option:
|
14
|
-
problem: 'missing :vendor option
|
15
|
-
summary: 'when version using header, you must specify :vendor option
|
14
|
+
problem: 'missing :vendor option'
|
15
|
+
summary: 'when version using header, you must specify :vendor option'
|
16
16
|
resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
|
17
17
|
missing_mime_type:
|
18
18
|
problem: 'missing mime type for %{new_format}'
|
@@ -21,12 +21,12 @@ en:
|
|
21
21
|
or add your own with content_type :%{new_format}, 'application/%{new_format}'
|
22
22
|
"
|
23
23
|
invalid_with_option_for_represent:
|
24
|
-
problem: '
|
24
|
+
problem: 'you must specify an entity class in the :with option'
|
25
25
|
resolution: 'eg: represent User, :with => Entity::User'
|
26
|
-
missing_option: '
|
26
|
+
missing_option: 'you must specify :%{option} options'
|
27
27
|
invalid_formatter: 'cannot convert %{klass} to %{to_format}'
|
28
28
|
invalid_versioner_option:
|
29
|
-
problem: '
|
29
|
+
problem: 'unknown :using for versioner: %{strategy}'
|
30
30
|
resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
|
31
31
|
unknown_validator: 'unknown validator: %{validator_type}'
|
32
32
|
unknown_options: 'unknown options: %{options}'
|
@@ -44,11 +44,12 @@ 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: '
|
47
|
+
empty_message_body: 'empty message body supplied with %{body_format} content-type'
|
48
|
+
too_many_multipart_files: "the number of uploaded files exceeded the system's configured limit (%{limit})"
|
48
49
|
invalid_accept_header:
|
49
|
-
problem: '
|
50
|
+
problem: 'invalid accept header'
|
50
51
|
resolution: '%{message}'
|
51
52
|
invalid_version_header:
|
52
|
-
problem: '
|
53
|
+
problem: 'invalid version header'
|
53
54
|
resolution: '%{message}'
|
54
55
|
invalid_response: 'Invalid response'
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rack/auth/basic'
|
4
|
-
require 'active_support/concern'
|
5
4
|
|
6
5
|
module Grape
|
7
6
|
module Middleware
|
@@ -32,7 +31,13 @@ module Grape
|
|
32
31
|
|
33
32
|
def http_digest(options = {}, &block)
|
34
33
|
options[:realm] ||= 'API Authorization'
|
35
|
-
|
34
|
+
|
35
|
+
if options[:realm].respond_to?(:values_at)
|
36
|
+
options[:realm][:opaque] ||= 'secret'
|
37
|
+
else
|
38
|
+
options[:opaque] ||= 'secret'
|
39
|
+
end
|
40
|
+
|
36
41
|
auth :http_digest, options, &block
|
37
42
|
end
|
38
43
|
end
|
@@ -59,7 +59,8 @@ module Grape
|
|
59
59
|
|
60
60
|
def response
|
61
61
|
return @app_response if @app_response.is_a?(Rack::Response)
|
62
|
-
|
62
|
+
|
63
|
+
@app_response = Rack::Response.new(@app_response[2], @app_response[0], @app_response[1])
|
63
64
|
end
|
64
65
|
|
65
66
|
def content_type_for(format)
|
@@ -84,6 +85,7 @@ module Grape
|
|
84
85
|
|
85
86
|
def merge_headers(response)
|
86
87
|
return unless headers.is_a?(Hash)
|
88
|
+
|
87
89
|
case response
|
88
90
|
when Rack::Response then response.headers.merge!(headers)
|
89
91
|
when Array then response[1].merge!(headers)
|
@@ -72,7 +72,7 @@ module Grape
|
|
72
72
|
|
73
73
|
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
|
74
74
|
message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
75
|
-
Rack::Response.new([message], status, headers)
|
75
|
+
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
|
76
76
|
end
|
77
77
|
|
78
78
|
def format_message(message, backtrace, original_exception = nil)
|
@@ -121,7 +121,7 @@ module Grape
|
|
121
121
|
|
122
122
|
def run_rescue_handler(handler, error)
|
123
123
|
if handler.instance_of?(Symbol)
|
124
|
-
raise NoMethodError, "undefined method
|
124
|
+
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
|
125
125
|
|
126
126
|
handler = public_method(handler)
|
127
127
|
end
|
@@ -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)
|
@@ -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
|
|
@@ -45,8 +45,8 @@ module Grape
|
|
45
45
|
@others = []
|
46
46
|
end
|
47
47
|
|
48
|
-
def each
|
49
|
-
@middlewares.each
|
48
|
+
def each(&block)
|
49
|
+
@middlewares.each(&block)
|
50
50
|
end
|
51
51
|
|
52
52
|
def size
|
@@ -95,7 +95,7 @@ module Grape
|
|
95
95
|
|
96
96
|
# @return [Rack::Builder] the builder object with our middlewares applied
|
97
97
|
def build(builder = Rack::Builder.new)
|
98
|
-
others.shift(others.size).each(
|
98
|
+
others.shift(others.size).each { |m| merge_with(m) }
|
99
99
|
middlewares.each do |m|
|
100
100
|
m.use_in(builder)
|
101
101
|
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
|