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