grape 2.4.0 → 3.2.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +109 -0
  3. data/CONTRIBUTING.md +2 -10
  4. data/README.md +137 -181
  5. data/UPGRADING.md +154 -0
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +51 -104
  8. data/lib/grape/api.rb +29 -42
  9. data/lib/grape/content_types.rb +1 -4
  10. data/lib/grape/declared_params_handler.rb +118 -0
  11. data/lib/grape/dry_types.rb +48 -4
  12. data/lib/grape/dsl/callbacks.rb +8 -58
  13. data/lib/grape/dsl/declared.rb +35 -0
  14. data/lib/grape/dsl/desc.rb +8 -67
  15. data/lib/grape/dsl/helpers.rb +59 -64
  16. data/lib/grape/dsl/inside_route.rb +28 -189
  17. data/lib/grape/dsl/logger.rb +3 -6
  18. data/lib/grape/dsl/middleware.rb +22 -40
  19. data/lib/grape/dsl/parameters.rb +24 -51
  20. data/lib/grape/dsl/request_response.rb +136 -139
  21. data/lib/grape/dsl/routing.rb +240 -200
  22. data/lib/grape/dsl/settings.rb +23 -135
  23. data/lib/grape/dsl/validations.rb +38 -44
  24. data/lib/grape/endpoint.rb +169 -205
  25. data/lib/grape/error_formatter/base.rb +4 -2
  26. data/lib/grape/exceptions/base.rb +19 -45
  27. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  28. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  29. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  30. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  31. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  32. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  33. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  34. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  35. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  36. data/lib/grape/exceptions/request_error.rb +11 -0
  37. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  38. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  39. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  40. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  41. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  42. data/lib/grape/exceptions/validation.rb +7 -4
  43. data/lib/grape/exceptions/validation_errors.rb +13 -7
  44. data/lib/grape/locale/en.yml +0 -5
  45. data/lib/grape/middleware/auth/base.rb +2 -0
  46. data/lib/grape/middleware/auth/dsl.rb +9 -10
  47. data/lib/grape/middleware/base.rb +6 -4
  48. data/lib/grape/middleware/error.rb +4 -14
  49. data/lib/grape/middleware/formatter.rb +11 -7
  50. data/lib/grape/middleware/stack.rb +3 -2
  51. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  52. data/lib/grape/middleware/versioner/base.rb +42 -40
  53. data/lib/grape/middleware/versioner/header.rb +2 -18
  54. data/lib/grape/middleware/versioner/param.rb +2 -2
  55. data/lib/grape/middleware/versioner/path.rb +2 -2
  56. data/lib/grape/namespace.rb +15 -8
  57. data/lib/grape/params_builder/base.rb +2 -0
  58. data/lib/grape/params_builder.rb +2 -19
  59. data/lib/grape/request.rb +2 -10
  60. data/lib/grape/router/base_route.rb +14 -5
  61. data/lib/grape/router/greedy_route.rb +11 -5
  62. data/lib/grape/router/pattern.rb +6 -20
  63. data/lib/grape/router/route.rb +7 -11
  64. data/lib/grape/router.rb +42 -65
  65. data/lib/grape/util/api_description.rb +58 -0
  66. data/lib/grape/util/base_inheritable.rb +5 -2
  67. data/lib/grape/util/cache.rb +1 -0
  68. data/lib/grape/util/deep_freeze.rb +35 -0
  69. data/lib/grape/util/inheritable_setting.rb +8 -1
  70. data/lib/grape/util/media_type.rb +2 -2
  71. data/lib/grape/util/registry.rb +1 -1
  72. data/lib/grape/util/translation.rb +42 -0
  73. data/lib/grape/validations/attributes_iterator.rb +35 -20
  74. data/lib/grape/validations/contract_scope.rb +2 -8
  75. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  76. data/lib/grape/validations/param_scope_tracker.rb +57 -0
  77. data/lib/grape/validations/params_documentation.rb +50 -0
  78. data/lib/grape/validations/params_scope.rb +147 -156
  79. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  80. data/lib/grape/validations/types/array_coercer.rb +2 -3
  81. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  82. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  83. data/lib/grape/validations/types.rb +10 -25
  84. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  85. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  86. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  87. data/lib/grape/validations/validators/base.rb +95 -25
  88. data/lib/grape/validations/validators/coerce_validator.rb +15 -35
  89. data/lib/grape/validations/validators/contract_scope_validator.rb +9 -5
  90. data/lib/grape/validations/validators/default_validator.rb +12 -18
  91. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  92. data/lib/grape/validations/validators/except_values_validator.rb +13 -4
  93. data/lib/grape/validations/validators/length_validator.rb +21 -22
  94. data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
  95. data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +4 -2
  96. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  97. data/lib/grape/validations/validators/regexp_validator.rb +11 -3
  98. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  99. data/lib/grape/validations/validators/values_validator.rb +29 -21
  100. data/lib/grape/version.rb +1 -1
  101. data/lib/grape.rb +29 -23
  102. metadata +31 -26
  103. data/lib/grape/api/helpers.rb +0 -9
  104. data/lib/grape/dsl/api.rb +0 -17
  105. data/lib/grape/dsl/configuration.rb +0 -15
  106. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  107. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  108. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  109. data/lib/grape/exceptions/missing_option.rb +0 -11
  110. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  111. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  112. data/lib/grape/exceptions/unknown_options.rb +0 -11
  113. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
  114. data/lib/grape/extensions/hash.rb +0 -27
  115. data/lib/grape/extensions/hashie/mash.rb +0 -24
  116. data/lib/grape/types/invalid_value.rb +0 -8
  117. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  118. data/lib/grape/validations/attributes_doc.rb +0 -60
  119. data/lib/grape/validations/validator_factory.rb +0 -15
@@ -3,152 +3,10 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module InsideRoute
6
- extend ActiveSupport::Concern
7
- include Grape::DSL::Settings
8
- include Grape::DSL::Headers
6
+ include Declared
9
7
 
10
- # Denotes a situation where a DSL method has been invoked in a
11
- # filter which it should not yet be available in
12
- class MethodNotYetAvailable < StandardError; end
13
-
14
- # @param type [Symbol] The type of filter for which evaluation has been
15
- # completed
16
- # @return [Module] A module containing method overrides suitable for the
17
- # position in the filter evaluation sequence denoted by +type+. This
18
- # defaults to an empty module if no overrides are defined for the given
19
- # filter +type+.
20
- def self.post_filter_methods(type)
21
- @post_filter_modules ||= { before: PostBeforeFilter }
22
- @post_filter_modules[type]
23
- end
24
-
25
- # Methods which should not be available in filters until the before filter
26
- # has completed
27
- module PostBeforeFilter
28
- def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
29
- options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
30
- declared_params ||= optioned_declared_params(options[:include_parent_namespaces])
31
-
32
- res = if passed_params.is_a?(Array)
33
- declared_array(passed_params, options, declared_params, params_nested_path)
34
- else
35
- declared_hash(passed_params, options, declared_params, params_nested_path)
36
- end
37
-
38
- if (key_maps = namespace_stackable(:contract_key_map))
39
- key_maps.each { |key_map| key_map.write(passed_params, res) }
40
- end
41
-
42
- res
43
- end
44
-
45
- private
46
-
47
- def declared_array(passed_params, options, declared_params, params_nested_path)
48
- passed_params.map do |passed_param|
49
- declared(passed_param || {}, options, declared_params, params_nested_path)
50
- end
51
- end
52
-
53
- def declared_hash(passed_params, options, declared_params, params_nested_path)
54
- declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
55
- next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params)
56
-
57
- declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
58
- end
59
- end
60
-
61
- def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
62
- renamed_params = route_setting(:renamed_params) || {}
63
- if declared_param.is_a?(Hash)
64
- declared_param.each_pair do |declared_parent_param, declared_children_params|
65
- params_nested_path_dup = params_nested_path.dup
66
- params_nested_path_dup << declared_parent_param.to_s
67
- next unless options[:include_missing] || passed_params.key?(declared_parent_param)
68
-
69
- rename_path = params_nested_path + [declared_parent_param.to_s]
70
- renamed_param_name = renamed_params[rename_path]
71
-
72
- memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
73
- passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
74
-
75
- memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
76
- declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
77
- end
78
- end
79
- else
80
- # If it is not a Hash then it does not have children.
81
- # Find its value or set it to nil.
82
- return unless options[:include_missing] || passed_params.try(:key?, declared_param)
83
-
84
- rename_path = params_nested_path + [declared_param.to_s]
85
- renamed_param_name = renamed_params[rename_path]
86
-
87
- memo_key = optioned_param_key(renamed_param_name || declared_param, options)
88
- passed_param = passed_params[declared_param]
89
-
90
- params_nested_path_dup = params_nested_path.dup
91
- params_nested_path_dup << declared_param.to_s
92
- memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
93
- passed_param
94
- end
95
- end
96
- end
97
-
98
- def handle_passed_param(params_nested_path, has_passed_children = false, &_block)
99
- return yield if has_passed_children
100
-
101
- key = params_nested_path[0]
102
- key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1
103
-
104
- route_options_params = options[:route_options][:params] || {}
105
- type = route_options_params.dig(key, :type)
106
- has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }
107
-
108
- if type == 'Hash' && !has_children
109
- {}
110
- elsif type == 'Array' || (type&.start_with?('[') && type.exclude?(','))
111
- []
112
- elsif type == 'Set' || type&.start_with?('#<Set')
113
- Set.new
114
- else
115
- yield
116
- end
117
- end
118
-
119
- def optioned_param_key(declared_param, options)
120
- options[:stringify] ? declared_param.to_s : declared_param.to_sym
121
- end
122
-
123
- def optioned_declared_params(include_parent_namespaces)
124
- declared_params = if include_parent_namespaces
125
- # Declared params including parent namespaces
126
- route_setting(:declared_params)
127
- else
128
- # Declared params at current namespace
129
- namespace_stackable(:declared_params).last || []
130
- end
131
-
132
- raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
133
-
134
- declared_params
135
- end
136
- end
137
-
138
- # A filtering method that will return a hash
139
- # consisting only of keys that have been declared by a
140
- # `params` statement against the current/target endpoint or parent
141
- # namespaces.
142
- #
143
- # @see +PostBeforeFilter#declared+
144
- #
145
- # @param params [Hash] The initial hash to filter. Usually this will just be `params`
146
- # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
147
- # options. `:include_parent_namespaces` defaults to true, hence must be set to false if
148
- # you want only to return params declared against the current/target endpoint.
149
- def declared(*)
150
- raise MethodNotYetAvailable, '#declared is not available prior to parameter validation.'
151
- end
8
+ # Backward compatibility: alias exception class to previous location
9
+ MethodNotYetAvailable = Declared::MethodNotYetAvailable
152
10
 
153
11
  # The API version as specified in the URL.
154
12
  def version
@@ -168,32 +26,14 @@ module Grape
168
26
  # @param backtrace [Array<String>] The backtrace of the exception that caused the error.
169
27
  # @param original_exception [Exception] The original exception that caused the error.
170
28
  def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)
171
- status = self.status(status || namespace_inheritable(:default_error_status))
29
+ status = self.status(status || inheritable_setting.namespace_inheritable[:default_error_status])
172
30
  headers = additional_headers.present? ? header.merge(additional_headers) : header
173
31
  throw :error,
174
- message: message,
175
- status: status,
176
- headers: headers,
177
- backtrace: backtrace,
178
- original_exception: original_exception
179
- end
180
-
181
- # Creates a Rack response based on the provided message, status, and headers.
182
- # The content type in the headers is set to the default content type unless provided.
183
- # The message is HTML-escaped if the content type is 'text/html'.
184
- #
185
- # @param message [String] The content of the response.
186
- # @param status [Integer] The HTTP status code.
187
- # @params headers [Hash] (optional) Headers for the response
188
- # (default: {Rack::CONTENT_TYPE => content_type}).
189
- #
190
- # Returns:
191
- # A Rack::Response object containing the specified message, status, and headers.
192
- #
193
- def rack_response(message, status = 200, headers = { Rack::CONTENT_TYPE => content_type })
194
- Grape.deprecator.warn('The rack_response method has been deprecated, use error! instead.')
195
- message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'
196
- Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), headers)
32
+ message:,
33
+ status:,
34
+ headers:,
35
+ backtrace:,
36
+ original_exception:
197
37
  end
198
38
 
199
39
  # Redirect to a new url.
@@ -230,12 +70,12 @@ module Grape
230
70
  when Integer
231
71
  @status = status
232
72
  when nil
233
- return @status if instance_variable_defined?(:@status) && @status
73
+ return @status if @status
234
74
 
235
75
  if request.post?
236
76
  201
237
77
  elsif request.delete?
238
- if instance_variable_defined?(:@body) && @body.present?
78
+ if @body.present?
239
79
  200
240
80
  else
241
81
  204
@@ -274,7 +114,7 @@ module Grape
274
114
  @body = ''
275
115
  status 204
276
116
  else
277
- instance_variable_defined?(:@body) ? @body : nil
117
+ @body
278
118
  end
279
119
  end
280
120
 
@@ -359,8 +199,7 @@ module Grape
359
199
  # with: API::Entities::User,
360
200
  # admin: current_user.admin?
361
201
  # end
362
- def present(*args)
363
- options = args.count > 1 ? args.extract_options! : {}
202
+ def present(*args, **options)
364
203
  key, object = if args.count == 2 && args.first.is_a?(Symbol)
365
204
  args
366
205
  else
@@ -411,29 +250,29 @@ module Grape
411
250
  # @return [Class] the located Entity class, or nil if none is found
412
251
  def entity_class_for_obj(object, options)
413
252
  entity_class = options.delete(:with)
414
-
415
- if entity_class.nil?
416
- # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
417
- object_class = if object.respond_to?(:klass)
418
- object.klass
419
- else
420
- object.respond_to?(:first) ? object.first.class : object.class
421
- end
422
-
423
- object_class.ancestors.each do |potential|
424
- entity_class ||= (namespace_stackable_with_hash(:representations) || {})[potential]
425
- end
426
-
427
- entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
253
+ return entity_class if entity_class
254
+
255
+ # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection
256
+ object_class = if object.respond_to?(:klass)
257
+ object.klass
258
+ else
259
+ object.respond_to?(:first) ? object.first.class : object.class
260
+ end
261
+
262
+ representations = inheritable_setting.namespace_stackable_with_hash(:representations)
263
+ if representations
264
+ potential = object_class.ancestors.detect { |potential| representations.key?(potential) }
265
+ entity_class = representations[potential] if potential
428
266
  end
429
267
 
268
+ entity_class = object_class.const_get(:Entity) if !entity_class && object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
430
269
  entity_class
431
270
  end
432
271
 
433
272
  # @return the representation of the given object as done through
434
273
  # the given entity_class.
435
274
  def entity_representation_for(entity_class, object, options)
436
- embeds = { env: env }
275
+ embeds = { env: }
437
276
  embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
438
277
  entity_class.represent(object, **embeds, **options)
439
278
  end
@@ -3,18 +3,15 @@
3
3
  module Grape
4
4
  module DSL
5
5
  module Logger
6
- include Grape::DSL::Settings
7
-
8
- attr_writer :logger
9
-
10
6
  # Set or retrive the configured logger. If none was configured, this
11
7
  # method will create a new one, logging to stdout.
12
8
  # @param logger [Object] the new logger to use
13
9
  def logger(logger = nil)
10
+ global_settings = inheritable_setting.global
14
11
  if logger
15
- global_setting(:logger, logger)
12
+ global_settings[:logger] = logger
16
13
  else
17
- global_setting(:logger) || global_setting(:logger, ::Logger.new($stdout))
14
+ global_settings[:logger] || global_settings[:logger] = ::Logger.new($stdout)
18
15
  end
19
16
  end
20
17
  end
@@ -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.namespace_stackable[: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.namespace_stackable[: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.namespace_stackable[: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.namespace_inheritable[:build_params_with] = build_with
35
33
  end
36
34
 
37
35
  # Include reusable params rules among current.
@@ -55,9 +53,8 @@ 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
+ named_params = @api.inheritable_setting.namespace_stackable_with_hash(:named_params) || {}
61
58
  names.each do |name|
62
59
  params_block = named_params.fetch(name) do
63
60
  raise "Params :#{name} not found!"
@@ -125,18 +122,15 @@ module Grape
125
122
  # requires :name, type: String
126
123
  # end
127
124
  # end
128
- def requires(*attrs, &block)
129
- orig_attrs = attrs.clone
130
-
131
- opts = attrs.extract_options!.clone
125
+ def requires(*attrs, **opts, &block)
132
126
  opts[:presence] = { value: true, message: opts[:message] }
133
- opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
127
+ opts = @group.deep_merge(opts) if @group
134
128
 
135
129
  if opts[:using]
136
- require_required_and_optional_fields(attrs.first, opts)
130
+ require_required_and_optional_fields(attrs.first, using: opts[:using], except: opts[:except])
137
131
  else
138
- validate_attributes(attrs, opts, &block)
139
- block ? new_scope(orig_attrs, &block) : push_declared_params(attrs, opts.slice(:as))
132
+ validate_attributes(attrs, **opts, &block)
133
+ block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], &block) : push_declared_params(attrs, as: opts[:as])
140
134
  end
141
135
  end
142
136
 
@@ -144,12 +138,9 @@ module Grape
144
138
  # endpoint.
145
139
  # @param (see #requires)
146
140
  # @option (see #requires)
147
- def optional(*attrs, &block)
148
- orig_attrs = attrs.clone
149
-
150
- opts = attrs.extract_options!.clone
141
+ def optional(*attrs, **opts, &block)
151
142
  type = opts[:type]
152
- opts = @group.deep_merge(opts) if instance_variable_defined?(:@group) && @group
143
+ opts = @group.deep_merge(opts) if @group
153
144
 
154
145
  # check type for optional parameter group
155
146
  if attrs && block
@@ -158,44 +149,26 @@ module Grape
158
149
  end
159
150
 
160
151
  if opts[:using]
161
- require_optional_fields(attrs.first, opts)
152
+ require_optional_fields(attrs.first, using: opts[:using], except: opts[:except])
162
153
  else
163
- validate_attributes(attrs, opts, &block)
154
+ validate_attributes(attrs, **opts, &block)
164
155
 
165
- block ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, opts.slice(:as))
156
+ block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], optional: true, &block) : push_declared_params(attrs, as: opts[:as])
166
157
  end
167
158
  end
168
159
 
169
160
  # Define common settings for one or more parameters
170
161
  # @param (see #requires)
171
162
  # @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) })
163
+ def with(**opts, &)
164
+ new_group_attrs = @group&.deep_merge(opts) || opts
165
+ new_group_scope(new_group_attrs, &)
181
166
  end
182
167
 
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) })
168
+ %i[mutually_exclusive exactly_one_of at_least_one_of all_or_none_of].each do |validator|
169
+ define_method validator do |*attrs, message: nil|
170
+ validates(attrs, validator => { value: true, message: })
171
+ end
199
172
  end
200
173
 
201
174
  # Define a block of validations which should be applied if and only if
@@ -205,12 +178,12 @@ module Grape
205
178
  # @raise Grape::Exceptions::UnknownParameter if `attr` has not been
206
179
  # defined in this scope yet
207
180
  # @yield a parameter definition DSL
208
- def given(*attrs, &block)
181
+ def given(*attrs, &)
209
182
  attrs.each do |attr|
210
183
  proxy_attr = first_hash_key_or_param(attr)
211
184
  raise Grape::Exceptions::UnknownParameter.new(proxy_attr) unless declared_param?(proxy_attr)
212
185
  end
213
- new_lateral_scope(dependent_on: attrs, &block)
186
+ new_lateral_scope(dependent_on: attrs, &)
214
187
  end
215
188
 
216
189
  # Test for whether a certain parameter has been defined in this params
@@ -251,8 +224,8 @@ module Grape
251
224
  # @return hash of parameters relevant for the current scope
252
225
  # @api private
253
226
  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
227
+ params = @parent.qualifying_params.presence || @parent.params(params) if @parent
228
+ params = map_params(params, @element) if @element
256
229
  params
257
230
  end
258
231