grape 3.1.1 → 3.2.0

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/README.md +76 -161
  4. data/UPGRADING.md +106 -0
  5. data/grape.gemspec +2 -2
  6. data/lib/grape/api/instance.rb +1 -1
  7. data/lib/grape/api.rb +1 -1
  8. data/lib/grape/declared_params_handler.rb +3 -3
  9. data/lib/grape/dsl/declared.rb +1 -1
  10. data/lib/grape/dsl/desc.rb +1 -1
  11. data/lib/grape/dsl/inside_route.rb +9 -9
  12. data/lib/grape/dsl/parameters.rb +14 -14
  13. data/lib/grape/dsl/routing.rb +8 -8
  14. data/lib/grape/endpoint.rb +42 -49
  15. data/lib/grape/error_formatter/base.rb +2 -2
  16. data/lib/grape/exceptions/base.rb +18 -44
  17. data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
  18. data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
  19. data/lib/grape/exceptions/invalid_formatter.rb +1 -1
  20. data/lib/grape/exceptions/invalid_message_body.rb +1 -1
  21. data/lib/grape/exceptions/invalid_version_header.rb +1 -1
  22. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
  23. data/lib/grape/exceptions/method_not_allowed.rb +1 -1
  24. data/lib/grape/exceptions/missing_mime_type.rb +1 -1
  25. data/lib/grape/exceptions/request_error.rb +11 -0
  26. data/lib/grape/exceptions/unknown_auth_strategy.rb +1 -1
  27. data/lib/grape/exceptions/unknown_parameter.rb +1 -1
  28. data/lib/grape/exceptions/unknown_params_builder.rb +1 -1
  29. data/lib/grape/exceptions/unknown_validator.rb +1 -1
  30. data/lib/grape/exceptions/validation.rb +7 -4
  31. data/lib/grape/exceptions/validation_errors.rb +13 -7
  32. data/lib/grape/locale/en.yml +0 -5
  33. data/lib/grape/middleware/auth/base.rb +2 -0
  34. data/lib/grape/middleware/base.rb +2 -4
  35. data/lib/grape/middleware/error.rb +2 -2
  36. data/lib/grape/middleware/formatter.rb +1 -1
  37. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  38. data/lib/grape/request.rb +2 -10
  39. data/lib/grape/router/pattern.rb +1 -1
  40. data/lib/grape/router.rb +4 -2
  41. data/lib/grape/util/api_description.rb +1 -1
  42. data/lib/grape/util/deep_freeze.rb +35 -0
  43. data/lib/grape/util/inheritable_setting.rb +1 -1
  44. data/lib/grape/util/media_type.rb +1 -1
  45. data/lib/grape/util/translation.rb +42 -0
  46. data/lib/grape/validations/attributes_iterator.rb +33 -18
  47. data/lib/grape/validations/contract_scope.rb +1 -7
  48. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  49. data/lib/grape/validations/param_scope_tracker.rb +57 -0
  50. data/lib/grape/validations/params_scope.rb +111 -107
  51. data/lib/grape/validations/single_attribute_iterator.rb +2 -2
  52. data/lib/grape/validations/validators/all_or_none_of_validator.rb +6 -3
  53. data/lib/grape/validations/validators/allow_blank_validator.rb +10 -5
  54. data/lib/grape/validations/validators/at_least_one_of_validator.rb +5 -2
  55. data/lib/grape/validations/validators/base.rb +95 -18
  56. data/lib/grape/validations/validators/coerce_validator.rb +15 -35
  57. data/lib/grape/validations/validators/contract_scope_validator.rb +10 -8
  58. data/lib/grape/validations/validators/default_validator.rb +12 -18
  59. data/lib/grape/validations/validators/exactly_one_of_validator.rb +10 -3
  60. data/lib/grape/validations/validators/except_values_validator.rb +13 -4
  61. data/lib/grape/validations/validators/length_validator.rb +21 -22
  62. data/lib/grape/validations/validators/multiple_params_base.rb +5 -5
  63. data/lib/grape/validations/validators/mutually_exclusive_validator.rb +3 -1
  64. data/lib/grape/validations/validators/presence_validator.rb +4 -2
  65. data/lib/grape/validations/validators/regexp_validator.rb +8 -10
  66. data/lib/grape/validations/validators/same_as_validator.rb +6 -15
  67. data/lib/grape/validations/validators/values_validator.rb +29 -21
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +18 -1
  70. metadata +11 -13
  71. data/lib/grape/exceptions/conflicting_types.rb +0 -11
  72. data/lib/grape/exceptions/empty_message_body.rb +0 -11
  73. data/lib/grape/exceptions/invalid_parameters.rb +0 -11
  74. data/lib/grape/exceptions/too_deep_parameters.rb +0 -11
  75. data/lib/grape/exceptions/too_many_multipart_files.rb +0 -11
  76. data/lib/grape/validations/validator_factory.rb +0 -15
@@ -60,7 +60,7 @@ module Grape
60
60
  end
61
61
  end
62
62
 
63
- @versions.last if instance_variable_defined?(:@versions) && @versions
63
+ @versions&.last
64
64
  end
65
65
 
66
66
  # Define a root URL prefix for your entire API.
@@ -136,8 +136,8 @@ module Grape
136
136
  endpoints << Grape::Endpoint.new(
137
137
  in_setting,
138
138
  method: :any,
139
- path: path,
140
- app: app,
139
+ path:,
140
+ app:,
141
141
  route_options: { anchor: false },
142
142
  forward_match: !app.respond_to?(:inheritable_setting),
143
143
  for: self
@@ -167,13 +167,13 @@ module Grape
167
167
 
168
168
  new_endpoint = Grape::Endpoint.new(
169
169
  inheritable_setting,
170
- method: method,
170
+ method:,
171
171
  path: paths,
172
172
  for: self,
173
173
  route_options: all_route_options,
174
174
  &
175
175
  )
176
- endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
176
+ endpoints << new_endpoint unless endpoints.include?(new_endpoint)
177
177
 
178
178
  inheritable_setting.route_end
179
179
  reset_validations!
@@ -203,7 +203,7 @@ module Grape
203
203
 
204
204
  within_namespace do
205
205
  nest(block) do
206
- inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, requirements: requirements, **options) if space
206
+ inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, requirements:, **options) if space
207
207
  end
208
208
  end
209
209
  end
@@ -223,14 +223,14 @@ module Grape
223
223
  #
224
224
  # @param param [Symbol] The name of the parameter you wish to declare.
225
225
  # @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
226
- def route_param(param, requirements: nil, type: nil, **options, &)
226
+ def route_param(param, requirements: nil, type: nil, **, &)
227
227
  requirements = { param.to_sym => requirements } if requirements.is_a?(Regexp)
228
228
 
229
229
  Grape::Validations::ParamsScope.new(api: self) do
230
230
  requires param, type: type
231
231
  end if type
232
232
 
233
- namespace(":#{param}", requirements: requirements, **options, &)
233
+ namespace(":#{param}", requirements:, **, &)
234
234
  end
235
235
 
236
236
  # @return array of defined versions
@@ -11,7 +11,7 @@ module Grape
11
11
  include Grape::DSL::Headers
12
12
  include Grape::DSL::InsideRoute
13
13
 
14
- attr_reader :env, :request, :source, :options
14
+ attr_reader :env, :request, :source, :options, :endpoints
15
15
 
16
16
  def_delegators :request, :params, :headers, :cookies
17
17
  def_delegator :cookies, :response_cookies
@@ -66,20 +66,21 @@ module Grape
66
66
  inheritable_setting.route[:declared_params] = inheritable_setting.namespace_stackable[:declared_params].flatten
67
67
  inheritable_setting.route[:saved_validations] = inheritable_setting.namespace_stackable[:validations]
68
68
 
69
- inheritable_setting.namespace_stackable[:representations] = [] unless inheritable_setting.namespace_stackable[:representations]
70
- inheritable_setting.namespace_inheritable[:default_error_status] = 500 unless inheritable_setting.namespace_inheritable[:default_error_status]
69
+ inheritable_setting.namespace_stackable[:representations] ||= []
70
+ inheritable_setting.namespace_inheritable[:default_error_status] ||= 500
71
71
 
72
72
  @options = options
73
73
 
74
- @options[:path] = Array(options[:path])
75
- @options[:path] << '/' if options[:path].empty?
76
- @options[:method] = Array(options[:method])
74
+ @options[:path] = Array(@options[:path])
75
+ @options[:path] << '/' if @options[:path].empty?
76
+ @options[:method] = Array(@options[:method])
77
77
 
78
78
  @status = nil
79
79
  @stream = nil
80
80
  @body = nil
81
81
  @source = self.class.block_to_unbound_method(block)
82
82
  @before_filter_passed = false
83
+ @endpoints = @options[:app].endpoints if @options[:app].respond_to?(:endpoints)
83
84
  end
84
85
 
85
86
  # Update our settings from a given set of stackable parameters. Used when
@@ -94,7 +95,7 @@ module Grape
94
95
  end
95
96
 
96
97
  def routes
97
- @routes ||= endpoints&.collect(&:routes)&.flatten || to_routes
98
+ @routes ||= endpoints&.flat_map(&:routes) || to_routes
98
99
  end
99
100
 
100
101
  def reset_routes!
@@ -113,7 +114,7 @@ module Grape
113
114
  compile!
114
115
  routes.each do |route|
115
116
  router.append(route.apply(self))
116
- next unless !inheritable_setting.namespace_inheritable[:do_not_route_head] && route.request_method == Rack::GET
117
+ next if inheritable_setting.namespace_inheritable[:do_not_route_head] || route.request_method != Rack::GET
117
118
 
118
119
  route.dup.then do |head_route|
119
120
  head_route.convert_to_head_request!
@@ -138,15 +139,12 @@ module Grape
138
139
  @app.call(env)
139
140
  end
140
141
 
141
- # Return the collection of endpoints within this endpoint.
142
- # This is the case when an Grape::API mounts another Grape::API.
143
- def endpoints
144
- @endpoints ||= options[:app].respond_to?(:endpoints) ? options[:app].endpoints : nil
145
- end
146
-
147
- def equals?(endpoint)
148
- (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
142
+ def ==(other)
143
+ other.is_a?(self.class) &&
144
+ options == other.options &&
145
+ inheritable_setting.to_hash == other.inheritable_setting.to_hash
149
146
  end
147
+ alias eql? ==
150
148
 
151
149
  # The purpose of this override is solely for stripping internals when an error occurs while calling
152
150
  # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
@@ -160,7 +158,7 @@ module Grape
160
158
  protected
161
159
 
162
160
  def run
163
- ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
161
+ ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env:) do
164
162
  @request = Grape::Request.new(env, build_params_with: inheritable_setting.namespace_inheritable[:build_params_with])
165
163
  begin
166
164
  self.class.run_before_each(self)
@@ -176,7 +174,7 @@ module Grape
176
174
  status 204
177
175
  else
178
176
  run_filters before_validations, :before_validation
179
- run_validators validations, request
177
+ run_validators(request:)
180
178
  run_filters after_validations, :after_validation
181
179
  response_object = execute
182
180
  end
@@ -198,35 +196,40 @@ module Grape
198
196
  end
199
197
 
200
198
  def execute
201
- return unless @source
199
+ return unless source
202
200
 
203
201
  ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do
204
- @source.bind_call(self)
202
+ source.bind_call(self)
205
203
  end
206
204
  end
207
205
 
208
- def run_validators(validators, request)
206
+ def run_validators(request:)
207
+ validators = inheritable_setting.route[:saved_validations]
208
+ return if validators.empty?
209
+
209
210
  validation_errors = []
210
211
 
211
- ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
212
- validators.each do |validator|
213
- validator.validate(request)
214
- rescue Grape::Exceptions::Validation => e
215
- validation_errors << e
216
- break if validator.fail_fast?
217
- rescue Grape::Exceptions::ValidationArrayErrors => e
218
- validation_errors.concat e.errors
219
- break if validator.fail_fast?
212
+ Grape::Validations::ParamScopeTracker.track do
213
+ ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators:, request:) do
214
+ validators.each do |validator|
215
+ validator.validate(request)
216
+ rescue Grape::Exceptions::Validation => e
217
+ validation_errors << e
218
+ break if validator.fail_fast?
219
+ rescue Grape::Exceptions::ValidationArrayErrors => e
220
+ validation_errors.concat e.errors
221
+ break if validator.fail_fast?
222
+ end
220
223
  end
221
224
  end
222
225
 
223
- validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header))
226
+ raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header)) if validation_errors.any?
224
227
  end
225
228
 
226
229
  def run_filters(filters, type = :other)
227
230
  return unless filters
228
231
 
229
- ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
232
+ ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters:, type:) do
230
233
  filters.each { |filter| instance_eval(&filter) }
231
234
  end
232
235
  end
@@ -237,16 +240,6 @@ module Grape
237
240
  end
238
241
  end
239
242
 
240
- def validations
241
- saved_validations = inheritable_setting.route[:saved_validations]
242
- return if saved_validations.nil?
243
- return enum_for(:validations) unless block_given?
244
-
245
- saved_validations.each do |saved_validation|
246
- yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
247
- end
248
- end
249
-
250
243
  def options?
251
244
  options[:options_route_enabled] &&
252
245
  env[Rack::REQUEST_METHOD] == Rack::OPTIONS
@@ -286,7 +279,7 @@ module Grape
286
279
 
287
280
  def prepare_default_route_attributes(route_options)
288
281
  {
289
- namespace: namespace,
282
+ namespace:,
290
283
  version: prepare_version(inheritable_setting.namespace_inheritable[:version]),
291
284
  requirements: prepare_routes_requirements(route_options[:requirements]),
292
285
  prefix: inheritable_setting.namespace_inheritable[:root_prefix],
@@ -323,15 +316,15 @@ module Grape
323
316
  stack.use Rack::Head
324
317
  stack.use Rack::Lint if lint?
325
318
  stack.use Grape::Middleware::Error,
326
- format: format,
327
- content_types: content_types,
319
+ format:,
320
+ content_types:,
328
321
  default_status: inheritable_setting.namespace_inheritable[:default_error_status],
329
322
  rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],
330
323
  rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],
331
324
  default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],
332
325
  error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),
333
326
  rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),
334
- rescue_handlers: rescue_handlers,
327
+ rescue_handlers:,
335
328
  base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),
336
329
  all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],
337
330
  grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]
@@ -347,9 +340,9 @@ module Grape
347
340
  end
348
341
 
349
342
  stack.use Grape::Middleware::Formatter,
350
- format: format,
343
+ format:,
351
344
  default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,
352
- content_types: content_types,
345
+ content_types:,
353
346
  formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),
354
347
  parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)
355
348
 
@@ -367,7 +360,7 @@ module Grape
367
360
 
368
361
  def build_response_cookies
369
362
  response_cookies do |name, value|
370
- cookie_value = value.is_a?(Hash) ? value : { value: value }
363
+ cookie_value = value.is_a?(Hash) ? value : { value: }
371
364
  Rack::Utils.set_cookie_header! header, name, cookie_value
372
365
  end
373
366
  end
@@ -40,7 +40,7 @@ module Grape
40
40
  end
41
41
 
42
42
  if presenter
43
- embeds = { env: env }
43
+ embeds = { env: }
44
44
  embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
45
45
  presented_message = presenter.represent(presented_message, embeds).serializable_hash
46
46
  end
@@ -51,7 +51,7 @@ module Grape
51
51
  def wrap_message(message)
52
52
  return message if message.is_a?(Hash)
53
53
 
54
- { message: message }
54
+ { message: }
55
55
  end
56
56
 
57
57
  def format_structured_message(_structured_message)
@@ -3,9 +3,9 @@
3
3
  module Grape
4
4
  module Exceptions
5
5
  class Base < StandardError
6
- BASE_MESSAGES_KEY = 'grape.errors.messages'
7
- BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'
8
- FALLBACK_LOCALE = :en
6
+ include Grape::Util::Translation
7
+
8
+ MESSAGE_STEPS = %w[problem summary resolution].to_h { |s| [s, s.capitalize] }.freeze
9
9
 
10
10
  attr_reader :status, :headers
11
11
 
@@ -20,55 +20,29 @@ module Grape
20
20
  __send__ index
21
21
  end
22
22
 
23
- protected
23
+ private
24
24
 
25
- # TODO: translate attribute first
26
- # if BASE_ATTRIBUTES_KEY.key respond to a string message, then short_message is returned
27
- # if BASE_ATTRIBUTES_KEY.key respond to a Hash, means it may have problem , summary and resolution
28
- def compose_message(key, **attributes)
29
- short_message = translate_message(key, attributes)
25
+ def compose_message(key, **)
26
+ short_message = translate_message(key, **)
30
27
  return short_message unless short_message.is_a?(Hash)
31
28
 
32
- each_steps(key, attributes).with_object(+'') do |detail_array, message|
33
- message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
34
- end
35
- end
36
-
37
- def each_steps(key, attributes)
38
- return enum_for(:each_steps, key, attributes) unless block_given?
39
-
40
- yield 'Problem', translate_message(:"#{key}.problem", attributes)
41
- yield 'Summary', translate_message(:"#{key}.summary", attributes)
42
- yield 'Resolution', translate_message(:"#{key}.resolution", attributes)
43
- end
44
-
45
- def translate_attributes(keys, options = {})
46
- keys.map do |key|
47
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", options.merge(default: key.to_s))
48
- end.join(', ')
29
+ MESSAGE_STEPS.filter_map do |step, label|
30
+ detail = translate_message(:"#{key}.#{step}", **)
31
+ "\n#{label}:\n #{detail}" if detail.present?
32
+ end.join
49
33
  end
50
34
 
51
- def translate_message(key, options = {})
52
- case key
35
+ def translate_message(translation_key, **)
36
+ case translation_key
53
37
  when Symbol
54
- translate("#{BASE_MESSAGES_KEY}.#{key}", options.merge(default: ''))
38
+ translate(translation_key, **)
39
+ when Hash
40
+ translation_key => { key:, **opts }
41
+ translate(key, **opts)
55
42
  when Proc
56
- key.call
57
- else
58
- key
59
- end
60
- end
61
-
62
- def translate(key, options)
63
- message = ::I18n.translate(key, **options)
64
- message.presence || fallback_message(key, options)
65
- end
66
-
67
- def fallback_message(key, options)
68
- if ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
69
- key
43
+ translation_key.call
70
44
  else
71
- ::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
45
+ translation_key
72
46
  end
73
47
  end
74
48
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class IncompatibleOptionValues < Base
6
6
  def initialize(option1, value1, option2, value2)
7
- super(message: compose_message(:incompatible_option_values, option1: option1, value1: value1, option2: option2, value2: value2))
7
+ super(message: compose_message(:incompatible_option_values, option1:, value1:, option2:, value2:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidAcceptHeader < Base
6
6
  def initialize(message, headers)
7
- super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers: headers)
7
+ super(message: compose_message(:invalid_accept_header, message:), status: 406, headers:)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidFormatter < Base
6
6
  def initialize(klass, to_format)
7
- super(message: compose_message(:invalid_formatter, klass: klass, to_format: to_format))
7
+ super(message: compose_message(:invalid_formatter, klass:, to_format:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidMessageBody < Base
6
6
  def initialize(body_format)
7
- super(message: compose_message(:invalid_message_body, body_format: body_format), status: 400)
7
+ super(message: compose_message(:invalid_message_body, body_format:), status: 400)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidVersionHeader < Base
6
6
  def initialize(message, headers)
7
- super(message: compose_message(:invalid_version_header, message: message), status: 406, headers: headers)
7
+ super(message: compose_message(:invalid_version_header, message:), status: 406, headers:)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class InvalidVersionerOption < Base
6
6
  def initialize(strategy)
7
- super(message: compose_message(:invalid_versioner_option, strategy: strategy))
7
+ super(message: compose_message(:invalid_versioner_option, strategy:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class MethodNotAllowed < Base
6
6
  def initialize(headers)
7
- super(message: '405 Not Allowed', status: 405, headers: headers)
7
+ super(message: '405 Not Allowed', status: 405, headers:)
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class MissingMimeType < Base
6
6
  def initialize(new_format)
7
- super(message: compose_message(:missing_mime_type, new_format: new_format))
7
+ super(message: compose_message(:missing_mime_type, new_format:))
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class RequestError < Base
6
+ def initialize(status: 400)
7
+ super(message: $ERROR_INFO&.message, status:)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class UnknownAuthStrategy < Base
6
6
  def initialize(strategy:)
7
- super(message: compose_message(:unknown_auth_strategy, strategy: strategy))
7
+ super(message: compose_message(:unknown_auth_strategy, strategy:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class UnknownParameter < Base
6
6
  def initialize(param)
7
- super(message: compose_message(:unknown_parameter, param: param))
7
+ super(message: compose_message(:unknown_parameter, param:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class UnknownParamsBuilder < Base
6
6
  def initialize(params_builder_type)
7
- super(message: compose_message(:unknown_params_builder, params_builder_type: params_builder_type))
7
+ super(message: compose_message(:unknown_params_builder, params_builder_type:))
8
8
  end
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Exceptions
5
5
  class UnknownValidator < Base
6
6
  def initialize(validator_type)
7
- super(message: compose_message(:unknown_validator, validator_type: validator_type))
7
+ super(message: compose_message(:unknown_validator, validator_type:))
8
8
  end
9
9
  end
10
10
  end
@@ -3,16 +3,19 @@
3
3
  module Grape
4
4
  module Exceptions
5
5
  class Validation < Base
6
- attr_accessor :params, :message_key
6
+ attr_reader :params, :message_key
7
7
 
8
8
  def initialize(params:, message: nil, status: nil, headers: nil)
9
- @params = params
9
+ @params = Array(params)
10
10
  if message
11
- @message_key = message if message.is_a?(Symbol)
11
+ @message_key = case message
12
+ when Symbol then message
13
+ when Hash then message[:key]
14
+ end
12
15
  message = translate_message(message)
13
16
  end
14
17
 
15
- super(status: status, message: message, headers: headers)
18
+ super(status:, message:, headers:)
16
19
  end
17
20
 
18
21
  # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
@@ -3,16 +3,13 @@
3
3
  module Grape
4
4
  module Exceptions
5
5
  class ValidationErrors < Base
6
- ERRORS_FORMAT_KEY = 'grape.errors.format'
7
- DEFAULT_ERRORS_FORMAT = '%<attributes>s %<message>s'
8
-
9
6
  include Enumerable
10
7
 
11
8
  attr_reader :errors
12
9
 
13
10
  def initialize(errors: [], headers: {})
14
11
  @errors = errors.group_by(&:params)
15
- super(message: full_messages.join(', '), status: 400, headers: headers)
12
+ super(message: full_messages.join(', '), status: 400, headers:)
16
13
  end
17
14
 
18
15
  def each
@@ -38,9 +35,10 @@ module Grape
38
35
 
39
36
  def full_messages
40
37
  messages = map do |attributes, error|
41
- I18n.t(
42
- ERRORS_FORMAT_KEY,
43
- default: DEFAULT_ERRORS_FORMAT,
38
+ translate(
39
+ :format,
40
+ scope: 'grape.errors',
41
+ default: '%<attributes>s %<message>s',
44
42
  attributes: translate_attributes(attributes),
45
43
  message: error.message
46
44
  )
@@ -48,6 +46,14 @@ module Grape
48
46
  messages.uniq!
49
47
  messages
50
48
  end
49
+
50
+ private
51
+
52
+ def translate_attributes(keys)
53
+ keys.map do |key|
54
+ translate(key, scope: 'grape.errors.attributes', default: key.to_s)
55
+ end.join(', ')
56
+ end
51
57
  end
52
58
  end
53
59
  end
@@ -8,8 +8,6 @@ en:
8
8
  at_least_one: 'are missing, at least one parameter must be provided'
9
9
  blank: 'is empty'
10
10
  coerce: 'is invalid'
11
- conflicting_types: 'query params contains conflicting types'
12
- empty_message_body: 'empty message body supplied with %{body_format} content-type'
13
11
  exactly_one: 'are missing, exactly one parameter must be provided'
14
12
  except_values: 'has a value not allowed'
15
13
  incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'
@@ -20,7 +18,6 @@ en:
20
18
  invalid_message_body:
21
19
  problem: 'message body does not match declared format'
22
20
  resolution: 'when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request''s ''body'' '
23
- invalid_parameters: 'query params contains invalid format or byte sequence'
24
21
  invalid_response: 'Invalid response'
25
22
  invalid_version_header:
26
23
  problem: 'invalid version header'
@@ -48,8 +45,6 @@ en:
48
45
  presence: 'is missing'
49
46
  regexp: 'is invalid'
50
47
  same_as: 'is not the same as %{parameter}'
51
- too_deep_parameters: 'query params are recursively nested over the specified limit (%{limit})'
52
- too_many_multipart_files: 'the number of uploaded files exceeded the system''s configured limit (%{limit})'
53
48
  unknown_auth_strategy: 'unknown auth strategy: %{strategy}'
54
49
  unknown_options: 'unknown options: %{options}'
55
50
  unknown_parameter: 'unknown parameter: %{param}'
@@ -6,6 +6,8 @@ module Grape
6
6
  class Base < Grape::Middleware::Base
7
7
  def initialize(app, **options)
8
8
  super
9
+ return unless options.key?(:type)
10
+
9
11
  @auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]].tap do |auth_strategy|
10
12
  raise Grape::Exceptions::UnknownAuthStrategy.new(strategy: options[:type]) unless auth_strategy
11
13
  end
@@ -79,10 +79,8 @@ module Grape
79
79
 
80
80
  def query_params
81
81
  rack_request.GET
82
- rescue Rack::QueryParser::ParamsTooDeepError
83
- raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)
84
- rescue Rack::Utils::ParameterTypeError
85
- raise Grape::Exceptions::ConflictingTypes
82
+ rescue *Grape::RACK_ERRORS
83
+ raise Grape::Exceptions::RequestError
86
84
  end
87
85
 
88
86
  private
@@ -38,8 +38,8 @@ module Grape
38
38
  throw :error,
39
39
  status: 406,
40
40
  message: "The requested format '#{format}' is not supported.",
41
- backtrace: backtrace,
42
- original_exception: original_exception
41
+ backtrace:,
42
+ original_exception:
43
43
  end
44
44
 
45
45
  def find_handler(klass)
@@ -38,7 +38,7 @@ module Grape
38
38
  else
39
39
  # Allow content-type to be explicitly overwritten
40
40
  formatter = fetch_formatter(headers, options)
41
- bodymap = ActiveSupport::Notifications.instrument('format_response.grape', formatter: formatter, env: env) do
41
+ bodymap = ActiveSupport::Notifications.instrument('format_response.grape', formatter:, env:) do
42
42
  bodies.collect { |body| formatter.call(body, env) }
43
43
  end
44
44
  Rack::Response.new(bodymap, status, headers)