grape 2.4.0 → 4.0.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 +351 -0
- data/CONTRIBUTING.md +18 -10
- data/README.md +457 -228
- data/UPGRADING.md +1240 -1
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +65 -116
- data/lib/grape/api.rb +53 -62
- data/lib/grape/content_types.rb +58 -3
- data/lib/grape/cookies.rb +2 -6
- data/lib/grape/declared_params_handler.rb +116 -0
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/desc.rb +24 -67
- data/lib/grape/dsl/entity.rb +106 -0
- data/lib/grape/dsl/helpers.rb +80 -65
- data/lib/grape/dsl/inside_route.rb +56 -296
- data/lib/grape/dsl/logger.rb +4 -9
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +86 -93
- data/lib/grape/dsl/request_response.rb +139 -139
- data/lib/grape/dsl/rescue_options.rb +25 -0
- data/lib/grape/dsl/routing.rb +328 -203
- data/lib/grape/dsl/settings.rb +29 -135
- data/lib/grape/dsl/validations.rb +38 -44
- data/lib/grape/dsl/version_options.rb +23 -0
- data/lib/grape/endpoint/options.rb +25 -0
- data/lib/grape/endpoint.rb +285 -245
- data/lib/grape/env.rb +8 -3
- data/lib/grape/error_formatter/base.rb +27 -21
- data/lib/grape/error_formatter/json.rb +6 -0
- data/lib/grape/error_formatter/txt.rb +10 -10
- data/lib/grape/error_formatter.rb +6 -2
- data/lib/grape/exceptions/base.rb +20 -44
- data/lib/grape/exceptions/error_response.rb +48 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/internal_server_error.rb +16 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/request_error.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
- data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/exceptions/validation.rb +25 -7
- data/lib/grape/exceptions/validation_array_errors.rb +4 -0
- data/lib/grape/exceptions/validation_errors.rb +21 -23
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +6 -9
- data/lib/grape/json.rb +38 -2
- data/lib/grape/locale/en.yml +3 -5
- data/lib/grape/middleware/auth/base.rb +4 -3
- data/lib/grape/middleware/auth/dsl.rb +9 -23
- data/lib/grape/middleware/base.rb +41 -36
- data/lib/grape/middleware/error.rb +271 -73
- data/lib/grape/middleware/formatter.rb +152 -63
- data/lib/grape/middleware/precomputed_content_types.rb +51 -0
- data/lib/grape/middleware/stack.rb +20 -11
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
- data/lib/grape/middleware/versioner/base.rb +40 -48
- data/lib/grape/middleware/versioner/header.rb +5 -23
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +54 -7
- data/lib/grape/mountable.rb +22 -0
- data/lib/grape/namespace.rb +19 -11
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/path.rb +8 -72
- data/lib/grape/precompiled_json.rb +50 -0
- data/lib/grape/request.rb +57 -20
- data/lib/grape/router/base_route.rb +53 -7
- data/lib/grape/router/greedy_route.rb +16 -6
- data/lib/grape/router/mustermann_pattern.rb +44 -0
- data/lib/grape/router/pattern/path.rb +78 -0
- data/lib/grape/router/pattern.rb +65 -37
- data/lib/grape/router/route.rb +66 -18
- data/lib/grape/router.rb +126 -115
- data/lib/grape/serve_stream/file_body.rb +7 -0
- data/lib/grape/serve_stream/sendfile_response.rb +3 -5
- data/lib/grape/serve_stream/stream_response.rb +7 -0
- data/lib/grape/testing.rb +33 -0
- data/lib/grape/util/api_description.rb +67 -0
- data/lib/grape/util/cache.rb +22 -2
- data/lib/grape/util/deep_freeze.rb +34 -0
- data/lib/grape/util/freeze_on_new.rb +20 -0
- data/lib/grape/util/inheritable_setting.rb +777 -39
- data/lib/grape/util/lazy/base.rb +16 -0
- data/lib/grape/util/lazy/block.rb +2 -9
- data/lib/grape/util/lazy/value.rb +2 -9
- data/lib/grape/util/lazy/value_enumerable.rb +13 -16
- data/lib/grape/util/media_type.rb +13 -9
- data/lib/grape/util/path_normalizer.rb +37 -0
- data/lib/grape/util/registry.rb +13 -3
- data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
- data/lib/grape/util/stackable_values.rb +39 -10
- data/lib/grape/util/translation.rb +42 -0
- data/lib/grape/validations/attributes_iterator.rb +61 -29
- data/lib/grape/validations/coerce_options.rb +21 -0
- data/lib/grape/validations/contract_scope.rb +8 -13
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/oneof_collector.rb +35 -0
- data/lib/grape/validations/param_scope_tracker.rb +62 -0
- data/lib/grape/validations/params_documentation.rb +52 -0
- data/lib/grape/validations/params_scope.rb +217 -304
- data/lib/grape/validations/shared_options.rb +19 -0
- data/lib/grape/validations/single_attribute_iterator.rb +6 -4
- data/lib/grape/validations/types/array_coercer.rb +7 -10
- data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
- data/lib/grape/validations/types/dry_type_coercer.rb +10 -15
- data/lib/grape/validations/types/json.rb +1 -3
- data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
- data/lib/grape/validations/types/primitive_coercer.rb +14 -33
- data/lib/grape/validations/types/set_coercer.rb +1 -1
- data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
- data/lib/grape/validations/types.rb +29 -51
- data/lib/grape/validations/validations_spec.rb +164 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
- data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
- data/lib/grape/validations/validators/base.rb +118 -34
- data/lib/grape/validations/validators/coerce_validator.rb +25 -37
- data/lib/grape/validations/validators/contract_scope_validator.rb +10 -5
- data/lib/grape/validations/validators/default_validator.rb +13 -20
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
- data/lib/grape/validations/validators/except_values_validator.rb +14 -4
- data/lib/grape/validations/validators/length_validator.rb +23 -22
- data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
- data/lib/grape/validations/validators/oneof_validator.rb +51 -0
- data/lib/grape/validations/validators/presence_validator.rb +4 -2
- data/lib/grape/validations/validators/regexp_validator.rb +11 -3
- data/lib/grape/validations/validators/same_as_validator.rb +7 -15
- data/lib/grape/validations/validators/values_validator.rb +34 -26
- data/lib/grape/version.rb +1 -2
- data/lib/grape/xml.rb +8 -1
- data/lib/grape.rb +58 -31
- metadata +62 -39
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -17
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/exceptions/conflicting_types.rb +0 -11
- data/lib/grape/exceptions/empty_message_body.rb +0 -11
- data/lib/grape/exceptions/invalid_parameters.rb +0 -11
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
- data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
- data/lib/grape/extensions/hash.rb +0 -27
- data/lib/grape/extensions/hashie/mash.rb +0 -24
- data/lib/grape/middleware/globals.rb +0 -14
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/base_inheritable.rb +0 -43
- data/lib/grape/util/inheritable_values.rb +0 -29
- data/lib/grape/util/reverse_stackable_values.rb +0 -15
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
- data/lib/grape/validations/validator_factory.rb +0 -15
data/lib/grape/dsl/middleware.rb
CHANGED
|
@@ -3,51 +3,33 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module Middleware
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
arr = [:use, middleware_class, *args]
|
|
19
|
-
arr << block if block
|
|
20
|
-
|
|
21
|
-
namespace_stackable(:middleware, arr)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def insert(*args, &block)
|
|
25
|
-
arr = [:insert, *args]
|
|
26
|
-
arr << block if block
|
|
27
|
-
|
|
28
|
-
namespace_stackable(:middleware, arr)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def insert_before(*args, &block)
|
|
32
|
-
arr = [:insert_before, *args]
|
|
33
|
-
arr << block if block
|
|
34
|
-
|
|
35
|
-
namespace_stackable(:middleware, arr)
|
|
36
|
-
end
|
|
6
|
+
# Apply a custom middleware to the API. Applies
|
|
7
|
+
# to the current namespace and any children, but
|
|
8
|
+
# not parents.
|
|
9
|
+
#
|
|
10
|
+
# @param middleware_class [Class] The class of the middleware you'd like
|
|
11
|
+
# to inject.
|
|
12
|
+
def use(middleware_class, *args, &block)
|
|
13
|
+
arr = [:use, middleware_class, *args]
|
|
14
|
+
arr << block if block
|
|
15
|
+
|
|
16
|
+
inheritable_setting.add_middleware(arr)
|
|
17
|
+
end
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
%i[insert insert_before insert_after].each do |method_name|
|
|
20
|
+
define_method method_name do |*args, &block|
|
|
21
|
+
arr = [method_name, *args]
|
|
40
22
|
arr << block if block
|
|
41
23
|
|
|
42
|
-
|
|
24
|
+
inheritable_setting.add_middleware(arr)
|
|
43
25
|
end
|
|
26
|
+
end
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
28
|
+
# Retrieve an array of the middleware classes
|
|
29
|
+
# and arguments that are currently applied to the
|
|
30
|
+
# application.
|
|
31
|
+
def middleware
|
|
32
|
+
inheritable_setting.middleware
|
|
51
33
|
end
|
|
52
34
|
end
|
|
53
35
|
end
|
data/lib/grape/dsl/parameters.rb
CHANGED
|
@@ -6,8 +6,6 @@ module Grape
|
|
|
6
6
|
# and describe the parameters accepted by an endpoint, or all endpoints
|
|
7
7
|
# within a namespace.
|
|
8
8
|
module Parameters
|
|
9
|
-
extend ActiveSupport::Concern
|
|
10
|
-
|
|
11
9
|
# Set the module used to build the request.params.
|
|
12
10
|
#
|
|
13
11
|
# @param build_with the ParamBuilder module to use when building request.params
|
|
@@ -31,7 +29,7 @@ module Grape
|
|
|
31
29
|
# end
|
|
32
30
|
# end
|
|
33
31
|
def build_with(build_with)
|
|
34
|
-
@api.
|
|
32
|
+
@api.inheritable_setting.build_params_with = build_with
|
|
35
33
|
end
|
|
36
34
|
|
|
37
35
|
# Include reusable params rules among current.
|
|
@@ -55,9 +53,10 @@ module Grape
|
|
|
55
53
|
# Collection.page(params[:page]).per(params[:per_page])
|
|
56
54
|
# end
|
|
57
55
|
# end
|
|
58
|
-
def use(*names)
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
def use(*names, **options)
|
|
57
|
+
return redispatch_legacy_options(:use, names, options) if legacy_options?(names)
|
|
58
|
+
|
|
59
|
+
named_params = @api.inheritable_setting.named_params || {}
|
|
61
60
|
names.each do |name|
|
|
62
61
|
params_block = named_params.fetch(name) do
|
|
63
62
|
raise "Params :#{name} not found!"
|
|
@@ -77,8 +76,8 @@ module Grape
|
|
|
77
76
|
#
|
|
78
77
|
# @param attrs list of parameters names, or, if :using is
|
|
79
78
|
# passed as an option, which keys to include (:all or :none) from
|
|
80
|
-
# the :using hash.
|
|
81
|
-
#
|
|
79
|
+
# the :using hash. Passing the options as a trailing positional Hash
|
|
80
|
+
# is deprecated; pass them as keyword arguments instead.
|
|
82
81
|
# @option attrs :type [Class] the type to coerce this parameter to before
|
|
83
82
|
# passing it to the endpoint. See {Grape::Validations::Types} for a list of
|
|
84
83
|
# types that are supported automatically. Custom classes may be used
|
|
@@ -125,77 +124,34 @@ module Grape
|
|
|
125
124
|
# requires :name, type: String
|
|
126
125
|
# end
|
|
127
126
|
# end
|
|
128
|
-
def requires(*attrs, &
|
|
129
|
-
|
|
127
|
+
def requires(*attrs, using: nil, except: nil, as: nil, **opts, &)
|
|
128
|
+
return redispatch_legacy_options(:requires, attrs, { using:, except:, as: }.compact.merge(opts), &) if legacy_options?(attrs)
|
|
130
129
|
|
|
131
|
-
opts
|
|
132
|
-
opts[:presence] = { value: true, message: opts[:message] }
|
|
133
|
-
opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
|
|
134
|
-
|
|
135
|
-
if opts[:using]
|
|
136
|
-
require_required_and_optional_fields(attrs.first, opts)
|
|
137
|
-
else
|
|
138
|
-
validate_attributes(attrs, opts, &block)
|
|
139
|
-
block ? new_scope(orig_attrs, &block) : push_declared_params(attrs, opts.slice(:as))
|
|
140
|
-
end
|
|
130
|
+
declare(attrs, opts, required: true, using:, except:, as:, &)
|
|
141
131
|
end
|
|
142
132
|
|
|
143
133
|
# Allow, but don't require, one or more parameters for the current
|
|
144
134
|
# endpoint.
|
|
145
135
|
# @param (see #requires)
|
|
146
136
|
# @option (see #requires)
|
|
147
|
-
def optional(*attrs, &
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
opts = attrs.extract_options!.clone
|
|
151
|
-
type = opts[:type]
|
|
152
|
-
opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
|
|
137
|
+
def optional(*attrs, using: nil, except: nil, as: nil, **opts, &)
|
|
138
|
+
return redispatch_legacy_options(:optional, attrs, { using:, except:, as: }.compact.merge(opts), &) if legacy_options?(attrs)
|
|
153
139
|
|
|
154
|
-
|
|
155
|
-
if attrs && block
|
|
156
|
-
raise Grape::Exceptions::MissingGroupType if type.nil?
|
|
157
|
-
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
if opts[:using]
|
|
161
|
-
require_optional_fields(attrs.first, opts)
|
|
162
|
-
else
|
|
163
|
-
validate_attributes(attrs, opts, &block)
|
|
164
|
-
|
|
165
|
-
block ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, opts.slice(:as))
|
|
166
|
-
end
|
|
140
|
+
declare(attrs, opts, required: false, using:, except:, as:, &)
|
|
167
141
|
end
|
|
168
142
|
|
|
169
143
|
# Define common settings for one or more parameters
|
|
170
144
|
# @param (see #requires)
|
|
171
145
|
# @option (see #requires)
|
|
172
|
-
def with(
|
|
173
|
-
new_group_attrs =
|
|
174
|
-
new_group_scope(
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Disallow the given parameters to be present in the same request.
|
|
178
|
-
# @param attrs [*Symbol] parameters to validate
|
|
179
|
-
def mutually_exclusive(*attrs)
|
|
180
|
-
validates(attrs, mutual_exclusion: { value: true, message: extract_message_option(attrs) })
|
|
146
|
+
def with(**opts, &)
|
|
147
|
+
new_group_attrs = @group&.deep_merge(opts) || opts
|
|
148
|
+
new_group_scope(new_group_attrs, &)
|
|
181
149
|
end
|
|
182
150
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# Require at least one of the given parameters to be present.
|
|
190
|
-
# @param (see #mutually_exclusive)
|
|
191
|
-
def at_least_one_of(*attrs)
|
|
192
|
-
validates(attrs, at_least_one_of: { value: true, message: extract_message_option(attrs) })
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Require that either all given params are present, or none are.
|
|
196
|
-
# @param (see #mutually_exclusive)
|
|
197
|
-
def all_or_none_of(*attrs)
|
|
198
|
-
validates(attrs, all_or_none_of: { value: true, message: extract_message_option(attrs) })
|
|
151
|
+
%i[mutually_exclusive exactly_one_of at_least_one_of all_or_none_of].each do |validator|
|
|
152
|
+
define_method validator do |*attrs, message: nil|
|
|
153
|
+
validates(attrs, { validator => { value: true, message: } })
|
|
154
|
+
end
|
|
199
155
|
end
|
|
200
156
|
|
|
201
157
|
# Define a block of validations which should be applied if and only if
|
|
@@ -205,62 +161,99 @@ module Grape
|
|
|
205
161
|
# @raise Grape::Exceptions::UnknownParameter if `attr` has not been
|
|
206
162
|
# defined in this scope yet
|
|
207
163
|
# @yield a parameter definition DSL
|
|
208
|
-
def given(*attrs, &
|
|
164
|
+
def given(*attrs, &)
|
|
209
165
|
attrs.each do |attr|
|
|
210
166
|
proxy_attr = first_hash_key_or_param(attr)
|
|
211
167
|
raise Grape::Exceptions::UnknownParameter.new(proxy_attr) unless declared_param?(proxy_attr)
|
|
212
168
|
end
|
|
213
|
-
new_lateral_scope(dependent_on: attrs, &
|
|
169
|
+
new_lateral_scope(dependent_on: attrs, &)
|
|
214
170
|
end
|
|
215
171
|
|
|
216
172
|
# Test for whether a certain parameter has been defined in this params
|
|
217
173
|
# block yet.
|
|
218
174
|
# @return [Boolean] whether the parameter has been defined
|
|
219
175
|
def declared_param?(param)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
first_hash_key_or_param(declared_param_attr.key) == param
|
|
228
|
-
end
|
|
176
|
+
# Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.
|
|
177
|
+
return @parent.declared_param?(param) if lateral?
|
|
178
|
+
|
|
179
|
+
# @declared_params also includes hashes of options and such, but those
|
|
180
|
+
# won't be flattened out.
|
|
181
|
+
@declared_params.flatten.any? do |declared_param_attr|
|
|
182
|
+
first_hash_key_or_param(declared_param_attr.key) == param
|
|
229
183
|
end
|
|
230
184
|
end
|
|
231
185
|
|
|
232
186
|
alias group requires
|
|
233
187
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def map_params(params, element, is_array = false)
|
|
237
|
-
if params.is_a?(Array)
|
|
238
|
-
params.map do |el|
|
|
239
|
-
map_params(el, element, true)
|
|
240
|
-
end
|
|
241
|
-
elsif params.is_a?(Hash)
|
|
242
|
-
params[element] || (@optional && is_array ? EmptyOptionalValue : {})
|
|
243
|
-
elsif params == EmptyOptionalValue
|
|
244
|
-
EmptyOptionalValue
|
|
245
|
-
else
|
|
246
|
-
{}
|
|
247
|
-
end
|
|
248
|
-
end
|
|
188
|
+
EmptyOptionalValue = Object.new.freeze
|
|
249
189
|
|
|
250
190
|
# @param params [Hash] initial hash of parameters
|
|
251
191
|
# @return hash of parameters relevant for the current scope
|
|
252
192
|
# @api private
|
|
253
193
|
def params(params)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
params
|
|
194
|
+
scoped = @parent ? (@parent.qualifying_params.presence || @parent.params(params)) : params
|
|
195
|
+
@element ? map_params(scoped, @element) : scoped
|
|
257
196
|
end
|
|
258
197
|
|
|
259
198
|
private
|
|
260
199
|
|
|
200
|
+
# @deprecated A trailing positional options Hash is deprecated; pass keyword
|
|
201
|
+
# arguments instead. Before Ruby 3 keyword separation this Hash was pulled
|
|
202
|
+
# off the argument list by `extract_options!`; now it lands in the splat and
|
|
203
|
+
# would silently be treated as a parameter name.
|
|
204
|
+
# +requires+ and +optional+ differ only in whether what they declare is
|
|
205
|
+
# required, so the declaration itself lives here.
|
|
206
|
+
#
|
|
207
|
+
# The two +using:+ helpers stay separate. They are not one computation
|
|
208
|
+
# with a flag flipped: +requires+ treats +except+ as "these go in the
|
|
209
|
+
# other bucket", while +optional+ has no other bucket — +:all+ ignores
|
|
210
|
+
# +except+ and +:none+ uses it to drop fields entirely.
|
|
211
|
+
def declare(attrs, opts, required:, using:, except:, as:, &block)
|
|
212
|
+
merged_opts = @group&.deep_merge(opts) || opts
|
|
213
|
+
declared_as = as || merged_opts[:as]
|
|
214
|
+
|
|
215
|
+
if using
|
|
216
|
+
return require_required_and_optional_fields(attrs.first, using:, except:) if required
|
|
217
|
+
|
|
218
|
+
return require_optional_fields(attrs.first, using:, except:)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
validates(attrs, merged_opts, required:)
|
|
222
|
+
return push_declared_params(attrs, as: declared_as) unless block
|
|
223
|
+
|
|
224
|
+
new_scope(attrs.first, type: merged_opts[:type], as: declared_as, optional: !required, &block)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def legacy_options?(args)
|
|
228
|
+
args.size > 1 && args.last.is_a?(Hash)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Re-invokes +method_name+ with the trailing Hash splatted as keyword
|
|
232
|
+
# arguments, so Ruby routes its keys to the same place they would have
|
|
233
|
+
# reached had the caller omitted the braces.
|
|
234
|
+
def redispatch_legacy_options(method_name, args, opts, &)
|
|
235
|
+
Grape.deprecator.warn("Passing a positional options Hash to `#{method_name}` is deprecated. Pass keyword arguments instead.")
|
|
236
|
+
__send__(method_name, *args[0..-2], **args.last.merge(opts), &)
|
|
237
|
+
end
|
|
238
|
+
|
|
261
239
|
def first_hash_key_or_param(parameter)
|
|
262
240
|
parameter.is_a?(Hash) ? parameter.keys.first : parameter
|
|
263
241
|
end
|
|
242
|
+
|
|
243
|
+
def map_params(params, element, is_array: false)
|
|
244
|
+
case params
|
|
245
|
+
when Array
|
|
246
|
+
params.map do |el|
|
|
247
|
+
map_params(el, element, is_array: true)
|
|
248
|
+
end
|
|
249
|
+
when Hash
|
|
250
|
+
params[element] || (@optional && is_array ? EmptyOptionalValue : {})
|
|
251
|
+
when EmptyOptionalValue
|
|
252
|
+
EmptyOptionalValue
|
|
253
|
+
else
|
|
254
|
+
{}
|
|
255
|
+
end
|
|
256
|
+
end
|
|
264
257
|
end
|
|
265
258
|
end
|
|
266
259
|
end
|
|
@@ -3,167 +3,167 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module RequestResponse
|
|
6
|
-
|
|
6
|
+
# Specify the default format for the API's serializers.
|
|
7
|
+
# May be `:json` or `:txt` (default).
|
|
8
|
+
def default_format(new_format = nil)
|
|
9
|
+
return inheritable_setting.default_format if new_format.nil?
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
inheritable_setting.default_format = new_format.to_sym
|
|
12
|
+
end
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
namespace_inheritable(:default_format, new_format.nil? ? nil : new_format.to_sym)
|
|
15
|
-
end
|
|
14
|
+
# Specify the format for the API's serializers.
|
|
15
|
+
# May be `:json`, `:xml`, `:txt`, etc.
|
|
16
|
+
def format(new_format = nil)
|
|
17
|
+
return inheritable_setting.format if new_format.nil?
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return namespace_inheritable(:format) unless new_format
|
|
19
|
+
symbolic_new_format = new_format.to_sym
|
|
20
|
+
inheritable_setting.format = symbolic_new_format
|
|
21
|
+
inheritable_setting.default_error_formatter = Grape::ErrorFormatter.formatter_for(symbolic_new_format)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(symbolic_new_format))
|
|
23
|
+
content_type = content_types[symbolic_new_format]
|
|
24
|
+
raise Grape::Exceptions::MissingMimeType.new(new_format) unless content_type
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
inheritable_setting.add_content_type(symbolic_new_format, content_type)
|
|
27
|
+
end
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
# Specify a custom formatter for a content-type.
|
|
30
|
+
def formatter(content_type, new_formatter)
|
|
31
|
+
inheritable_setting.add_formatter(content_type.to_sym, new_formatter)
|
|
32
|
+
end
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
# Specify a custom parser for a content-type.
|
|
35
|
+
def parser(content_type, new_parser)
|
|
36
|
+
inheritable_setting.add_parser(content_type.to_sym, new_parser)
|
|
37
|
+
end
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
# Specify a default error formatter, by the name it is registered under.
|
|
40
|
+
# A name nothing is registered for used to be stored as
|
|
41
|
+
# +ErrorFormatter::Txt+ — the fallback the lookup applied — so a typo
|
|
42
|
+
# read back as a working setting and silently rendered errors as text.
|
|
43
|
+
def default_error_formatter(new_formatter_name = nil)
|
|
44
|
+
return inheritable_setting.default_error_formatter if new_formatter_name.nil?
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return namespace_inheritable(:default_error_formatter) unless new_formatter_name
|
|
46
|
+
formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name)
|
|
47
|
+
raise Grape::Exceptions::UnknownErrorFormatter.new(new_formatter_name) if formatter.nil?
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
+
inheritable_setting.default_error_formatter = formatter
|
|
50
|
+
end
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
# Specify a custom error formatter for a format, passed positionally or
|
|
53
|
+
# as +with:+. A nil formatter used to be registered as-is, which is the
|
|
54
|
+
# one thing the registration cannot mean: the format then resolves as if
|
|
55
|
+
# the call had never been made. Reject it here, where the mistake is.
|
|
56
|
+
def error_formatter(format, options = nil, with: nil)
|
|
57
|
+
formatter = with || options
|
|
58
|
+
raise ArgumentError, "error_formatter `#{format.inspect}` requires a formatter, given positionally or as `with:`" if formatter.nil?
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
inheritable_setting.add_error_formatter(format.to_sym, formatter)
|
|
61
|
+
end
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
# Specify additional content-types, e.g.:
|
|
64
|
+
# content_type :xls, 'application/vnd.ms-excel'
|
|
65
|
+
def content_type(key, val)
|
|
66
|
+
inheritable_setting.add_content_type(key.to_sym, val)
|
|
67
|
+
end
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
end
|
|
69
|
+
# All available content types.
|
|
70
|
+
def content_types
|
|
71
|
+
Grape::ContentTypes.content_types_for(inheritable_setting.content_types)
|
|
72
|
+
end
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
end
|
|
74
|
+
# Specify the default status code for errors.
|
|
75
|
+
def default_error_status(new_status = nil)
|
|
76
|
+
return inheritable_setting.default_error_status if new_status.nil?
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# server level.
|
|
80
|
-
#
|
|
81
|
-
# @example Rescue from custom exceptions
|
|
82
|
-
# class ExampleAPI < Grape::API
|
|
83
|
-
# class CustomError < StandardError; end
|
|
84
|
-
#
|
|
85
|
-
# rescue_from CustomError
|
|
86
|
-
# end
|
|
87
|
-
#
|
|
88
|
-
# @overload rescue_from(*exception_classes, **options)
|
|
89
|
-
# @param [Array] exception_classes A list of classes that you want to rescue, or
|
|
90
|
-
# the symbol :all to rescue from all exceptions.
|
|
91
|
-
# @param [Block] block Execution block to handle the given exception.
|
|
92
|
-
# @param [Hash] options Options for the rescue usage.
|
|
93
|
-
# @option options [Boolean] :backtrace Include a backtrace in the rescue response.
|
|
94
|
-
# @option options [Boolean] :rescue_subclasses Also rescue subclasses of exception classes
|
|
95
|
-
# @param [Proc] handler Execution proc to handle the given exception as an
|
|
96
|
-
# alternative to passing a block.
|
|
97
|
-
def rescue_from(*args, &block)
|
|
98
|
-
if args.last.is_a?(Proc)
|
|
99
|
-
handler = args.pop
|
|
100
|
-
elsif block
|
|
101
|
-
handler = block
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
options = args.extract_options!
|
|
105
|
-
raise ArgumentError, 'both :with option and block cannot be passed' if block && options.key?(:with)
|
|
106
|
-
|
|
107
|
-
handler ||= extract_with(options)
|
|
108
|
-
|
|
109
|
-
if args.include?(:all)
|
|
110
|
-
namespace_inheritable(:rescue_all, true)
|
|
111
|
-
namespace_inheritable(:all_rescue_handler, handler)
|
|
112
|
-
elsif args.include?(:grape_exceptions)
|
|
113
|
-
namespace_inheritable(:rescue_all, true)
|
|
114
|
-
namespace_inheritable(:rescue_grape_exceptions, true)
|
|
115
|
-
namespace_inheritable(:grape_exceptions_rescue_handler, handler)
|
|
116
|
-
else
|
|
117
|
-
handler_type =
|
|
118
|
-
case options[:rescue_subclasses]
|
|
119
|
-
when nil, true
|
|
120
|
-
:rescue_handlers
|
|
121
|
-
else
|
|
122
|
-
:base_only_rescue_handlers
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
namespace_reverse_stackable(handler_type, args.to_h { |arg| [arg, handler] })
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
namespace_stackable(:rescue_options, options)
|
|
129
|
-
end
|
|
78
|
+
inheritable_setting.default_error_status = new_status
|
|
79
|
+
end
|
|
130
80
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
81
|
+
# Allows you to rescue certain exceptions that occur to return
|
|
82
|
+
# a grape error rather than raising all the way to the
|
|
83
|
+
# server level.
|
|
84
|
+
#
|
|
85
|
+
# @example Rescue from custom exceptions
|
|
86
|
+
# class ExampleAPI < Grape::API
|
|
87
|
+
# class CustomError < StandardError; end
|
|
88
|
+
#
|
|
89
|
+
# rescue_from CustomError
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
META_RESCUE_SELECTORS = %i[all grape_exceptions internal_grape_exceptions].freeze
|
|
93
|
+
private_constant :META_RESCUE_SELECTORS
|
|
94
|
+
|
|
95
|
+
# @overload rescue_from(*exception_classes, **options)
|
|
96
|
+
# @param [Array] exception_classes A list of classes that you want to rescue, or
|
|
97
|
+
# one of the meta selectors +:all+, +:grape_exceptions+,
|
|
98
|
+
# +:internal_grape_exceptions+. Meta selectors must be used alone;
|
|
99
|
+
# mixing with exception classes raises +ArgumentError+.
|
|
100
|
+
# @param [Block] block Execution block to handle the given exception.
|
|
101
|
+
# @param [Proc] with Execution proc to handle the given exception as an alternative
|
|
102
|
+
# to passing a block.
|
|
103
|
+
# @param [Boolean] rescue_subclasses Also rescue subclasses of exception classes;
|
|
104
|
+
# defaults to +true+.
|
|
105
|
+
# @param [Boolean] backtrace Include the rescued exception's backtrace in the
|
|
106
|
+
# rescue response body.
|
|
107
|
+
# @param [Boolean] original_exception Include +inspect+ of the rescued exception
|
|
108
|
+
# in the rescue response body.
|
|
109
|
+
def rescue_from(*args, with: nil, rescue_subclasses: true, backtrace: false, original_exception: false, &block)
|
|
110
|
+
handler = extract_handler(args, with:, block:)
|
|
111
|
+
meta_selector = (args & META_RESCUE_SELECTORS).first
|
|
112
|
+
raise ArgumentError, "rescue_from `#{meta_selector.inspect}` does not accept additional arguments" if meta_selector && args.size > 1
|
|
113
|
+
|
|
114
|
+
case meta_selector
|
|
115
|
+
when :all
|
|
116
|
+
inheritable_setting.add_all_rescue_handler(handler)
|
|
117
|
+
when :grape_exceptions
|
|
118
|
+
inheritable_setting.add_grape_exceptions_rescue_handler(handler)
|
|
119
|
+
when :internal_grape_exceptions
|
|
120
|
+
inheritable_setting.add_internal_grape_exceptions_rescue_handler(handler)
|
|
121
|
+
else
|
|
122
|
+
inheritable_setting.add_rescue_handlers(args.to_h { |klass| [klass, handler] }, subclasses: rescue_subclasses)
|
|
155
123
|
end
|
|
156
124
|
|
|
157
|
-
|
|
125
|
+
inheritable_setting.add_rescue_options(RescueOptions.new(backtrace:, original_exception:))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Allows you to specify a default representation entity for a
|
|
129
|
+
# class. This allows you to map your models to their respective
|
|
130
|
+
# entities once and then simply call `present` with the model.
|
|
131
|
+
#
|
|
132
|
+
# @example
|
|
133
|
+
# class ExampleAPI < Grape::API
|
|
134
|
+
# represent User, with: Entity::User
|
|
135
|
+
#
|
|
136
|
+
# get '/me' do
|
|
137
|
+
# present current_user # with: Entity::User is assumed
|
|
138
|
+
# end
|
|
139
|
+
# end
|
|
140
|
+
#
|
|
141
|
+
# Note that Grape will automatically go up the class ancestry to
|
|
142
|
+
# try to find a representing entity, so if you, for example, define
|
|
143
|
+
# an entity to represent `Object` then all presented objects will
|
|
144
|
+
# bubble up and utilize the entity provided on that `represent` call.
|
|
145
|
+
#
|
|
146
|
+
# @param model_class [Class] The model class that will be represented.
|
|
147
|
+
# @option options [Class] :with The entity class that will represent the model.
|
|
148
|
+
def represent(model_class, with:)
|
|
149
|
+
raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless with.is_a?(Class)
|
|
150
|
+
|
|
151
|
+
inheritable_setting.add_representation(model_class, with)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
158
155
|
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
def extract_handler(args, with:, block:)
|
|
157
|
+
raise ArgumentError, 'both :with option and block cannot be passed' if block && with
|
|
161
158
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
return args.pop if args.last.is_a?(Proc)
|
|
160
|
+
return block if block
|
|
161
|
+
return unless with
|
|
165
162
|
|
|
166
|
-
|
|
163
|
+
case with
|
|
164
|
+
when Proc, Symbol then with
|
|
165
|
+
when String then with.to_sym
|
|
166
|
+
else raise ArgumentError, "with: `#{with.class}`, expected Symbol, String or Proc"
|
|
167
167
|
end
|
|
168
168
|
end
|
|
169
169
|
end
|