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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +351 -0
  3. data/CONTRIBUTING.md +18 -10
  4. data/README.md +457 -228
  5. data/UPGRADING.md +1240 -1
  6. data/grape.gemspec +7 -7
  7. data/lib/grape/api/instance.rb +65 -116
  8. data/lib/grape/api.rb +53 -62
  9. data/lib/grape/content_types.rb +58 -3
  10. data/lib/grape/cookies.rb +2 -6
  11. data/lib/grape/declared_params_handler.rb +116 -0
  12. data/lib/grape/dry_types.rb +48 -4
  13. data/lib/grape/dsl/callbacks.rb +8 -58
  14. data/lib/grape/dsl/declared.rb +35 -0
  15. data/lib/grape/dsl/desc.rb +24 -67
  16. data/lib/grape/dsl/entity.rb +106 -0
  17. data/lib/grape/dsl/helpers.rb +80 -65
  18. data/lib/grape/dsl/inside_route.rb +56 -296
  19. data/lib/grape/dsl/logger.rb +4 -9
  20. data/lib/grape/dsl/middleware.rb +22 -40
  21. data/lib/grape/dsl/parameters.rb +86 -93
  22. data/lib/grape/dsl/request_response.rb +139 -139
  23. data/lib/grape/dsl/rescue_options.rb +25 -0
  24. data/lib/grape/dsl/routing.rb +328 -203
  25. data/lib/grape/dsl/settings.rb +29 -135
  26. data/lib/grape/dsl/validations.rb +38 -44
  27. data/lib/grape/dsl/version_options.rb +23 -0
  28. data/lib/grape/endpoint/options.rb +25 -0
  29. data/lib/grape/endpoint.rb +285 -245
  30. data/lib/grape/env.rb +8 -3
  31. data/lib/grape/error_formatter/base.rb +27 -21
  32. data/lib/grape/error_formatter/json.rb +6 -0
  33. data/lib/grape/error_formatter/txt.rb +10 -10
  34. data/lib/grape/error_formatter.rb +6 -2
  35. data/lib/grape/exceptions/base.rb +20 -44
  36. data/lib/grape/exceptions/error_response.rb +48 -0
  37. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  38. data/lib/grape/exceptions/internal_server_error.rb +16 -0
  39. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  40. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  41. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  42. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  43. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  44. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  45. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  46. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  47. data/lib/grape/exceptions/request_error.rb +11 -0
  48. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  49. data/lib/grape/exceptions/unknown_error_formatter.rb +11 -0
  50. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  51. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  52. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  53. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  54. data/lib/grape/exceptions/validation.rb +25 -7
  55. data/lib/grape/exceptions/validation_array_errors.rb +4 -0
  56. data/lib/grape/exceptions/validation_errors.rb +21 -23
  57. data/lib/grape/formatter/json.rb +1 -0
  58. data/lib/grape/formatter/serializable_hash.rb +6 -9
  59. data/lib/grape/json.rb +38 -2
  60. data/lib/grape/locale/en.yml +3 -5
  61. data/lib/grape/middleware/auth/base.rb +4 -3
  62. data/lib/grape/middleware/auth/dsl.rb +9 -23
  63. data/lib/grape/middleware/base.rb +41 -36
  64. data/lib/grape/middleware/error.rb +271 -73
  65. data/lib/grape/middleware/formatter.rb +152 -63
  66. data/lib/grape/middleware/precomputed_content_types.rb +51 -0
  67. data/lib/grape/middleware/stack.rb +20 -11
  68. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  69. data/lib/grape/middleware/versioner/base.rb +40 -48
  70. data/lib/grape/middleware/versioner/header.rb +5 -23
  71. data/lib/grape/middleware/versioner/param.rb +2 -2
  72. data/lib/grape/middleware/versioner/path.rb +54 -7
  73. data/lib/grape/mountable.rb +22 -0
  74. data/lib/grape/namespace.rb +19 -11
  75. data/lib/grape/params_builder/base.rb +2 -0
  76. data/lib/grape/params_builder/hash_with_indifferent_access.rb +1 -1
  77. data/lib/grape/params_builder.rb +2 -19
  78. data/lib/grape/parser/json.rb +1 -1
  79. data/lib/grape/path.rb +8 -72
  80. data/lib/grape/precompiled_json.rb +50 -0
  81. data/lib/grape/request.rb +57 -20
  82. data/lib/grape/router/base_route.rb +53 -7
  83. data/lib/grape/router/greedy_route.rb +16 -6
  84. data/lib/grape/router/mustermann_pattern.rb +44 -0
  85. data/lib/grape/router/pattern/path.rb +78 -0
  86. data/lib/grape/router/pattern.rb +65 -37
  87. data/lib/grape/router/route.rb +66 -18
  88. data/lib/grape/router.rb +126 -115
  89. data/lib/grape/serve_stream/file_body.rb +7 -0
  90. data/lib/grape/serve_stream/sendfile_response.rb +3 -5
  91. data/lib/grape/serve_stream/stream_response.rb +7 -0
  92. data/lib/grape/testing.rb +33 -0
  93. data/lib/grape/util/api_description.rb +67 -0
  94. data/lib/grape/util/cache.rb +22 -2
  95. data/lib/grape/util/deep_freeze.rb +34 -0
  96. data/lib/grape/util/freeze_on_new.rb +20 -0
  97. data/lib/grape/util/inheritable_setting.rb +777 -39
  98. data/lib/grape/util/lazy/base.rb +16 -0
  99. data/lib/grape/util/lazy/block.rb +2 -9
  100. data/lib/grape/util/lazy/value.rb +2 -9
  101. data/lib/grape/util/lazy/value_enumerable.rb +13 -16
  102. data/lib/grape/util/media_type.rb +13 -9
  103. data/lib/grape/util/path_normalizer.rb +37 -0
  104. data/lib/grape/util/registry.rb +13 -3
  105. data/lib/grape/util/shadowed_rescue_handlers.rb +49 -0
  106. data/lib/grape/util/stackable_values.rb +39 -10
  107. data/lib/grape/util/translation.rb +42 -0
  108. data/lib/grape/validations/attributes_iterator.rb +61 -29
  109. data/lib/grape/validations/coerce_options.rb +21 -0
  110. data/lib/grape/validations/contract_scope.rb +8 -13
  111. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  112. data/lib/grape/validations/oneof_collector.rb +35 -0
  113. data/lib/grape/validations/param_scope_tracker.rb +62 -0
  114. data/lib/grape/validations/params_documentation.rb +52 -0
  115. data/lib/grape/validations/params_scope.rb +217 -304
  116. data/lib/grape/validations/shared_options.rb +19 -0
  117. data/lib/grape/validations/single_attribute_iterator.rb +6 -4
  118. data/lib/grape/validations/types/array_coercer.rb +7 -10
  119. data/lib/grape/validations/types/custom_type_coercer.rb +47 -85
  120. data/lib/grape/validations/types/custom_type_collection_coercer.rb +1 -1
  121. data/lib/grape/validations/types/dry_type_coercer.rb +10 -15
  122. data/lib/grape/validations/types/json.rb +1 -3
  123. data/lib/grape/validations/types/multiple_type_coercer.rb +5 -3
  124. data/lib/grape/validations/types/primitive_coercer.rb +14 -33
  125. data/lib/grape/validations/types/set_coercer.rb +1 -1
  126. data/lib/grape/validations/types/variant_collection_coercer.rb +16 -3
  127. data/lib/grape/validations/types.rb +29 -51
  128. data/lib/grape/validations/validations_spec.rb +164 -0
  129. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  130. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  131. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  132. data/lib/grape/validations/validators/base.rb +118 -34
  133. data/lib/grape/validations/validators/coerce_validator.rb +25 -37
  134. data/lib/grape/validations/validators/contract_scope_validator.rb +10 -5
  135. data/lib/grape/validations/validators/default_validator.rb +13 -20
  136. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  137. data/lib/grape/validations/validators/except_values_validator.rb +14 -4
  138. data/lib/grape/validations/validators/length_validator.rb +23 -22
  139. data/lib/grape/validations/validators/multiple_params_base.rb +12 -9
  140. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  141. data/lib/grape/validations/validators/oneof_validator.rb +51 -0
  142. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  143. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  144. data/lib/grape/validations/validators/same_as_validator.rb +7 -15
  145. data/lib/grape/validations/validators/values_validator.rb +34 -26
  146. data/lib/grape/version.rb +1 -2
  147. data/lib/grape/xml.rb +8 -1
  148. data/lib/grape.rb +58 -31
  149. metadata +62 -39
  150. data/lib/grape/api/helpers.rb +0 -9
  151. data/lib/grape/dsl/api.rb +0 -17
  152. data/lib/grape/dsl/configuration.rb +0 -15
  153. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  154. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  155. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  156. data/lib/grape/exceptions/missing_option.rb +0 -11
  157. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  158. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  159. data/lib/grape/exceptions/unknown_options.rb +0 -11
  160. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  161. data/lib/grape/extensions/hash.rb +0 -27
  162. data/lib/grape/extensions/hashie/mash.rb +0 -24
  163. data/lib/grape/middleware/globals.rb +0 -14
  164. data/lib/grape/types/invalid_value.rb +0 -8
  165. data/lib/grape/util/base_inheritable.rb +0 -43
  166. data/lib/grape/util/inheritable_values.rb +0 -29
  167. data/lib/grape/util/reverse_stackable_values.rb +0 -15
  168. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  169. data/lib/grape/validations/attributes_doc.rb +0 -60
  170. data/lib/grape/validations/validator_factory.rb +0 -15
@@ -3,51 +3,33 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Middleware
6
- extend ActiveSupport::Concern
7
-
8
- include Grape::DSL::Configuration
9
-
10
- module ClassMethods
11
- # Apply a custom middleware to the API. Applies
12
- # to the current namespace and any children, but
13
- # not parents.
14
- #
15
- # @param middleware_class [Class] The class of the middleware you'd like
16
- # to inject.
17
- def use(middleware_class, *args, &block)
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
- def insert_after(*args, &block)
39
- arr = [:insert_after, *args]
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
- namespace_stackable(:middleware, arr)
24
+ inheritable_setting.add_middleware(arr)
43
25
  end
26
+ end
44
27
 
45
- # Retrieve an array of the middleware classes
46
- # and arguments that are currently applied to the
47
- # application.
48
- def middleware
49
- namespace_stackable(:middleware) || []
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
@@ -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.namespace_inheritable(:build_params_with, build_with)
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
- named_params = @api.namespace_stackable_with_hash(:named_params) || {}
60
- options = names.extract_options!
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. The last key can be a hash, which specifies
81
- # options for the parameters
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, &block)
129
- orig_attrs = attrs.clone
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 = attrs.extract_options!.clone
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, &block)
148
- orig_attrs = attrs.clone
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
- # check type for optional parameter group
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(*attrs, &block)
173
- new_group_attrs = [@group, attrs.clone.first].compact.reduce(&:deep_merge)
174
- new_group_scope([new_group_attrs], &block)
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
- # Require exactly one of the given parameters to be present.
184
- # @param (see #mutually_exclusive)
185
- def exactly_one_of(*attrs)
186
- validates(attrs, exactly_one_of: { value: true, message: extract_message_option(attrs) })
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, &block)
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, &block)
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
- if lateral?
221
- # Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.
222
- @parent.declared_param?(param)
223
- else
224
- # @declared_params also includes hashes of options and such, but those
225
- # won't be flattened out.
226
- @declared_params.flatten.any? do |declared_param_attr|
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
- class EmptyOptionalValue; end # rubocop:disable Lint/EmptyClass
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
- params = @parent.params_meeting_dependency.presence || @parent.params(params) if instance_variable_defined?(:@parent) && @parent
255
- params = map_params(params, @element) if instance_variable_defined?(:@element) && @element
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
- extend ActiveSupport::Concern
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
- include Grape::DSL::Configuration
11
+ inheritable_setting.default_format = new_format.to_sym
12
+ end
9
13
 
10
- module ClassMethods
11
- # Specify the default format for the API's serializers.
12
- # May be `:json` or `:txt` (default).
13
- def default_format(new_format = nil)
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
- # Specify the format for the API's serializers.
18
- # May be `:json`, `:xml`, `:txt`, etc.
19
- def format(new_format = nil)
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
- symbolic_new_format = new_format.to_sym
23
- namespace_inheritable(:format, symbolic_new_format)
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
- content_type = content_types[symbolic_new_format]
27
- raise Grape::Exceptions::MissingMimeType.new(new_format) unless content_type
26
+ inheritable_setting.add_content_type(symbolic_new_format, content_type)
27
+ end
28
28
 
29
- namespace_stackable(:content_types, symbolic_new_format => content_type)
30
- end
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
- # Specify a custom formatter for a content-type.
33
- def formatter(content_type, new_formatter)
34
- namespace_stackable(:formatters, content_type.to_sym => new_formatter)
35
- end
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
- # Specify a custom parser for a content-type.
38
- def parser(content_type, new_parser)
39
- namespace_stackable(:parsers, content_type.to_sym => new_parser)
40
- end
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
- # Specify a default error formatter.
43
- def default_error_formatter(new_formatter_name = nil)
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
- new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name)
47
- namespace_inheritable(:default_error_formatter, new_formatter)
48
- end
49
+ inheritable_setting.default_error_formatter = formatter
50
+ end
49
51
 
50
- def error_formatter(format, options)
51
- formatter = if options.is_a?(Hash) && options.key?(:with)
52
- options[:with]
53
- else
54
- options
55
- end
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
- namespace_stackable(:error_formatters, format.to_sym => formatter)
58
- end
60
+ inheritable_setting.add_error_formatter(format.to_sym, formatter)
61
+ end
59
62
 
60
- # Specify additional content-types, e.g.:
61
- # content_type :xls, 'application/vnd.ms-excel'
62
- def content_type(key, val)
63
- namespace_stackable(:content_types, key.to_sym => val)
64
- end
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
- # All available content types.
67
- def content_types
68
- c_types = namespace_stackable_with_hash(:content_types)
69
- Grape::ContentTypes.content_types_for c_types
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
- # Specify the default status code for errors.
73
- def default_error_status(new_status = nil)
74
- namespace_inheritable(:default_error_status, new_status)
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
- # Allows you to rescue certain exceptions that occur to return
78
- # a grape error rather than raising all the way to the
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
- # Allows you to specify a default representation entity for a
132
- # class. This allows you to map your models to their respective
133
- # entities once and then simply call `present` with the model.
134
- #
135
- # @example
136
- # class ExampleAPI < Grape::API
137
- # represent User, with: Entity::User
138
- #
139
- # get '/me' do
140
- # present current_user # with: Entity::User is assumed
141
- # end
142
- # end
143
- #
144
- # Note that Grape will automatically go up the class ancestry to
145
- # try to find a representing entity, so if you, for example, define
146
- # an entity to represent `Object` then all presented objects will
147
- # bubble up and utilize the entity provided on that `represent` call.
148
- #
149
- # @param model_class [Class] The model class that will be represented.
150
- # @option options [Class] :with The entity class that will represent the model.
151
- def represent(model_class, options)
152
- raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with].is_a?(Class)
153
-
154
- namespace_stackable(:representations, model_class => options[:with])
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
- private
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
- def extract_with(options)
160
- return unless options.key?(:with)
156
+ def extract_handler(args, with:, block:)
157
+ raise ArgumentError, 'both :with option and block cannot be passed' if block && with
161
158
 
162
- with_option = options.delete(:with)
163
- return with_option if with_option.instance_of?(Proc)
164
- return with_option.to_sym if with_option.instance_of?(Symbol) || with_option.instance_of?(String)
159
+ return args.pop if args.last.is_a?(Proc)
160
+ return block if block
161
+ return unless with
165
162
 
166
- raise ArgumentError, "with: #{with_option.class}, expected Symbol, String or Proc"
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