grape 2.1.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +9 -7
  4. data/UPGRADING.md +27 -0
  5. data/grape.gemspec +7 -6
  6. data/lib/grape/api/instance.rb +22 -58
  7. data/lib/grape/api.rb +2 -11
  8. data/lib/grape/content_types.rb +13 -8
  9. data/lib/grape/dsl/desc.rb +27 -24
  10. data/lib/grape/dsl/helpers.rb +7 -3
  11. data/lib/grape/dsl/inside_route.rb +18 -24
  12. data/lib/grape/dsl/parameters.rb +2 -2
  13. data/lib/grape/dsl/request_response.rb +14 -18
  14. data/lib/grape/dsl/routing.rb +5 -12
  15. data/lib/grape/endpoint.rb +90 -82
  16. data/lib/grape/error_formatter/base.rb +51 -21
  17. data/lib/grape/error_formatter/json.rb +7 -15
  18. data/lib/grape/error_formatter/jsonapi.rb +7 -0
  19. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  20. data/lib/grape/error_formatter/txt.rb +13 -20
  21. data/lib/grape/error_formatter/xml.rb +3 -13
  22. data/lib/grape/error_formatter.rb +5 -25
  23. data/lib/grape/exceptions/base.rb +18 -30
  24. data/lib/grape/exceptions/validation.rb +5 -4
  25. data/lib/grape/exceptions/validation_errors.rb +2 -2
  26. data/lib/grape/formatter/base.rb +16 -0
  27. data/lib/grape/formatter/json.rb +4 -6
  28. data/lib/grape/formatter/serializable_hash.rb +1 -1
  29. data/lib/grape/formatter/txt.rb +3 -5
  30. data/lib/grape/formatter/xml.rb +4 -6
  31. data/lib/grape/formatter.rb +7 -25
  32. data/lib/grape/http/headers.rb +1 -0
  33. data/lib/grape/locale/en.yml +1 -0
  34. data/lib/grape/middleware/base.rb +14 -13
  35. data/lib/grape/middleware/error.rb +13 -9
  36. data/lib/grape/middleware/formatter.rb +3 -3
  37. data/lib/grape/middleware/versioner/accept_version_header.rb +7 -30
  38. data/lib/grape/middleware/versioner/base.rb +82 -0
  39. data/lib/grape/middleware/versioner/header.rb +89 -10
  40. data/lib/grape/middleware/versioner/param.rb +4 -22
  41. data/lib/grape/middleware/versioner/path.rb +10 -32
  42. data/lib/grape/middleware/versioner.rb +7 -14
  43. data/lib/grape/namespace.rb +1 -1
  44. data/lib/grape/parser/base.rb +16 -0
  45. data/lib/grape/parser/json.rb +6 -8
  46. data/lib/grape/parser/jsonapi.rb +7 -0
  47. data/lib/grape/parser/xml.rb +6 -8
  48. data/lib/grape/parser.rb +5 -23
  49. data/lib/grape/path.rb +39 -56
  50. data/lib/grape/request.rb +2 -2
  51. data/lib/grape/router/base_route.rb +2 -2
  52. data/lib/grape/router/greedy_route.rb +2 -2
  53. data/lib/grape/router/pattern.rb +23 -18
  54. data/lib/grape/router/route.rb +13 -5
  55. data/lib/grape/router.rb +5 -5
  56. data/lib/grape/util/registry.rb +27 -0
  57. data/lib/grape/validations/contract_scope.rb +2 -39
  58. data/lib/grape/validations/params_scope.rb +7 -11
  59. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  60. data/lib/grape/validations/validator_factory.rb +2 -2
  61. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +5 -9
  63. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  64. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  65. data/lib/grape/validations/validators/default_validator.rb +1 -1
  66. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  67. data/lib/grape/validations/validators/length_validator.rb +11 -4
  68. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  69. data/lib/grape/validations/validators/values_validator.rb +15 -57
  70. data/lib/grape/validations.rb +8 -17
  71. data/lib/grape/version.rb +1 -1
  72. data/lib/grape.rb +1 -1
  73. metadata +15 -12
  74. data/lib/grape/util/accept_header_handler.rb +0 -105
  75. data/lib/grape/util/registrable.rb +0 -15
  76. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -114,10 +114,10 @@ module Grape
114
114
  # Update our settings from a given set of stackable parameters. Used when
115
115
  # the endpoint's API is mounted under another one.
116
116
  def inherit_settings(namespace_stackable)
117
- inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
117
+ parent_validations = namespace_stackable[:validations]
118
+ inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
118
119
  parent_declared_params = namespace_stackable[:declared_params]
119
-
120
- inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
120
+ inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?
121
121
 
122
122
  endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
123
123
  end
@@ -145,27 +145,28 @@ module Grape
145
145
  end
146
146
 
147
147
  def mount_in(router)
148
- if endpoints
149
- endpoints.each { |e| e.mount_in(router) }
150
- else
151
- reset_routes!
152
- routes.each do |route|
153
- methods = [route.request_method]
154
- methods << Rack::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
155
- methods.each do |method|
156
- route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) unless route.request_method == method
157
- router.append(route.apply(self))
158
- end
148
+ return endpoints.each { |e| e.mount_in(router) } if endpoints
149
+
150
+ reset_routes!
151
+ routes.each do |route|
152
+ router.append(route.apply(self))
153
+ next unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
154
+
155
+ route.dup.then do |head_route|
156
+ head_route.convert_to_head_request!
157
+ router.append(head_route.apply(self))
159
158
  end
160
159
  end
161
160
  end
162
161
 
163
162
  def to_routes
164
- route_options = prepare_default_route_attributes
165
- map_routes do |method, path|
166
- path = prepare_path(path)
167
- params = merge_route_options(**route_options.merge(suffix: path.suffix))
168
- route = Router::Route.new(method, path.path, **params)
163
+ default_route_options = prepare_default_route_attributes
164
+ default_path_settings = prepare_default_path_settings
165
+
166
+ map_routes do |method, raw_path|
167
+ prepared_path = Path.new(raw_path, namespace, default_path_settings)
168
+ params = options[:route_options].present? ? options[:route_options].merge(default_route_options) : default_route_options
169
+ route = Grape::Router::Route.new(method, prepared_path.origin, prepared_path.suffix, params)
169
170
  route.apply(self)
170
171
  end.flatten
171
172
  end
@@ -191,23 +192,19 @@ module Grape
191
192
 
192
193
  def prepare_version
193
194
  version = namespace_inheritable(:version)
194
- return unless version
195
- return if version.empty?
195
+ return if version.blank?
196
196
 
197
197
  version.length == 1 ? version.first : version
198
198
  end
199
199
 
200
- def merge_route_options(**default)
201
- options[:route_options].clone.merge!(**default)
202
- end
203
-
204
200
  def map_routes
205
201
  options[:method].map { |method| options[:path].map { |path| yield method, path } }
206
202
  end
207
203
 
208
- def prepare_path(path)
209
- path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
210
- Path.new(path, namespace, path_settings)
204
+ def prepare_default_path_settings
205
+ namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
206
+ namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
207
+ namespace_stackable_hash.merge!(namespace_inheritable_hash)
211
208
  end
212
209
 
213
210
  def namespace
@@ -235,6 +232,15 @@ module Grape
235
232
  (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
236
233
  end
237
234
 
235
+ # The purpose of this override is solely for stripping internals when an error occurs while calling
236
+ # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
237
+ # Otherwise, it calls super.
238
+ def inspect
239
+ return super unless env
240
+
241
+ "#{self.class} in '#{route.origin}' endpoint"
242
+ end
243
+
238
244
  protected
239
245
 
240
246
  def run
@@ -249,9 +255,10 @@ module Grape
249
255
  run_filters befores, :before
250
256
 
251
257
  if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
252
- raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
258
+ allow_header_value = allowed_methods.join(', ')
259
+ raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allow_header_value)) unless options?
253
260
 
254
- header Grape::Http::Headers::ALLOW, allowed_methods
261
+ header Grape::Http::Headers::ALLOW, allow_header_value
255
262
  response_object = ''
256
263
  status 204
257
264
  else
@@ -277,54 +284,6 @@ module Grape
277
284
  end
278
285
  end
279
286
 
280
- def build_stack(helpers)
281
- stack = Grape::Middleware::Stack.new
282
-
283
- stack.use Rack::Head
284
- stack.use Class.new(Grape::Middleware::Error),
285
- helpers: helpers,
286
- format: namespace_inheritable(:format),
287
- content_types: namespace_stackable_with_hash(:content_types),
288
- default_status: namespace_inheritable(:default_error_status),
289
- rescue_all: namespace_inheritable(:rescue_all),
290
- rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
291
- default_error_formatter: namespace_inheritable(:default_error_formatter),
292
- error_formatters: namespace_stackable_with_hash(:error_formatters),
293
- rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
294
- rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers) || {},
295
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
296
- all_rescue_handler: namespace_inheritable(:all_rescue_handler),
297
- grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
298
-
299
- stack.concat namespace_stackable(:middleware)
300
-
301
- if namespace_inheritable(:version)
302
- stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
303
- versions: namespace_inheritable(:version)&.flatten,
304
- version_options: namespace_inheritable(:version_options),
305
- prefix: namespace_inheritable(:root_prefix),
306
- mount_path: namespace_stackable(:mount_path).first
307
- end
308
-
309
- stack.use Grape::Middleware::Formatter,
310
- format: namespace_inheritable(:format),
311
- default_format: namespace_inheritable(:default_format) || :txt,
312
- content_types: namespace_stackable_with_hash(:content_types),
313
- formatters: namespace_stackable_with_hash(:formatters),
314
- parsers: namespace_stackable_with_hash(:parsers)
315
-
316
- builder = stack.build
317
- builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
318
- builder.to_app
319
- end
320
-
321
- def build_helpers
322
- helpers = namespace_stackable(:helpers)
323
- Module.new { helpers&.each { |mod_to_include| include mod_to_include } }
324
- end
325
-
326
- private :build_stack, :build_helpers
327
-
328
287
  def execute
329
288
  @block&.call(self)
330
289
  end
@@ -339,7 +298,7 @@ module Grape
339
298
  @lazy_initialize_lock.synchronize do
340
299
  return true if @lazy_initialized
341
300
 
342
- @helpers = build_helpers.tap { |mod| self.class.send(:include, mod) }
301
+ @helpers = build_helpers&.tap { |mod| self.class.include mod }
343
302
  @app = options[:app] || build_stack(@helpers)
344
303
 
345
304
  @lazy_initialized = true
@@ -396,7 +355,7 @@ module Grape
396
355
  return enum_for(:validations) unless block_given?
397
356
 
398
357
  route_setting(:saved_validations)&.each do |saved_validation|
399
- yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation)
358
+ yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
400
359
  end
401
360
  end
402
361
 
@@ -405,8 +364,57 @@ module Grape
405
364
  env[Rack::REQUEST_METHOD] == Rack::OPTIONS
406
365
  end
407
366
 
408
- def inspect
409
- "#{self.class} in `#{route.origin}' endpoint"
367
+ private
368
+
369
+ def build_stack(helpers)
370
+ stack = Grape::Middleware::Stack.new
371
+
372
+ content_types = namespace_stackable_with_hash(:content_types)
373
+ format = namespace_inheritable(:format)
374
+
375
+ stack.use Rack::Head
376
+ stack.use Class.new(Grape::Middleware::Error),
377
+ helpers: helpers,
378
+ format: format,
379
+ content_types: content_types,
380
+ default_status: namespace_inheritable(:default_error_status),
381
+ rescue_all: namespace_inheritable(:rescue_all),
382
+ rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
383
+ default_error_formatter: namespace_inheritable(:default_error_formatter),
384
+ error_formatters: namespace_stackable_with_hash(:error_formatters),
385
+ rescue_options: namespace_stackable_with_hash(:rescue_options),
386
+ rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
387
+ base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
388
+ all_rescue_handler: namespace_inheritable(:all_rescue_handler),
389
+ grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
390
+
391
+ stack.concat namespace_stackable(:middleware)
392
+
393
+ if namespace_inheritable(:version).present?
394
+ stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
395
+ versions: namespace_inheritable(:version).flatten,
396
+ version_options: namespace_inheritable(:version_options),
397
+ prefix: namespace_inheritable(:root_prefix),
398
+ mount_path: namespace_stackable(:mount_path).first
399
+ end
400
+
401
+ stack.use Grape::Middleware::Formatter,
402
+ format: format,
403
+ default_format: namespace_inheritable(:default_format) || :txt,
404
+ content_types: content_types,
405
+ formatters: namespace_stackable_with_hash(:formatters),
406
+ parsers: namespace_stackable_with_hash(:parsers)
407
+
408
+ builder = stack.build
409
+ builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
410
+ builder.to_app
411
+ end
412
+
413
+ def build_helpers
414
+ helpers = namespace_stackable(:helpers)
415
+ return if helpers.empty?
416
+
417
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
410
418
  end
411
419
  end
412
420
  end
@@ -2,36 +2,66 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Base
6
- def present(message, env)
7
- present_options = {}
8
- presented_message = message
9
- if presented_message.is_a?(Hash)
10
- presented_message = presented_message.dup
11
- present_options[:with] = presented_message.delete(:with)
5
+ class Base
6
+ class << self
7
+ def call(message, backtrace, options = {}, env = nil, original_exception = nil)
8
+ merge_backtrace = backtrace.present? && options.dig(:rescue_options, :backtrace)
9
+ merge_original_exception = original_exception && options.dig(:rescue_options, :original_exception)
10
+
11
+ wrapped_message = wrap_message(present(message, env))
12
+ if wrapped_message.is_a?(Hash)
13
+ wrapped_message[:backtrace] = backtrace if merge_backtrace
14
+ wrapped_message[:original_exception] = original_exception.inspect if merge_original_exception
15
+ end
16
+
17
+ format_structured_message(wrapped_message)
12
18
  end
13
19
 
14
- presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options)
20
+ def present(message, env)
21
+ present_options = {}
22
+ presented_message = message
23
+ if presented_message.is_a?(Hash)
24
+ presented_message = presented_message.dup
25
+ present_options[:with] = presented_message.delete(:with)
26
+ end
27
+
28
+ presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options)
29
+
30
+ unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
31
+ # env['api.endpoint'].route does not work when the error occurs within a middleware
32
+ # the Endpoint does not have a valid env at this moment
33
+ http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
34
+
35
+ found_code = http_codes.find do |http_code|
36
+ (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
37
+ end if env[Grape::Env::API_ENDPOINT].request
15
38
 
16
- unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?
17
- # env['api.endpoint'].route does not work when the error occurs within a middleware
18
- # the Endpoint does not have a valid env at this moment
19
- http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []
39
+ presenter = found_code[2] if found_code
40
+ end
20
41
 
21
- found_code = http_codes.find do |http_code|
22
- (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)
23
- end if env[Grape::Env::API_ENDPOINT].request
42
+ if presenter
43
+ embeds = { env: env }
44
+ embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
45
+ presented_message = presenter.represent(presented_message, embeds).serializable_hash
46
+ end
24
47
 
25
- presenter = found_code[2] if found_code
48
+ presented_message
26
49
  end
27
50
 
28
- if presenter
29
- embeds = { env: env }
30
- embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)
31
- presented_message = presenter.represent(presented_message, embeds).serializable_hash
51
+ def wrap_message(message)
52
+ return message if message.is_a?(Hash)
53
+
54
+ { message: message }
55
+ end
56
+
57
+ def format_structured_message(_structured_message)
58
+ raise NotImplementedError
32
59
  end
33
60
 
34
- presented_message
61
+ def inherited(klass)
62
+ super
63
+ ErrorFormatter.register(klass)
64
+ end
35
65
  end
36
66
  end
37
67
  end
@@ -2,27 +2,19 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Json
6
- extend Base
7
-
5
+ class Json < Base
8
6
  class << self
9
- def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
- result = wrap_message(present(message, env))
11
-
12
- rescue_options = options[:rescue_options] || {}
13
- result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
14
- result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
15
- ::Grape::Json.dump(result)
7
+ def format_structured_message(structured_message)
8
+ ::Grape::Json.dump(structured_message)
16
9
  end
17
10
 
18
11
  private
19
12
 
20
13
  def wrap_message(message)
21
- if message.is_a?(Exceptions::ValidationErrors) || message.is_a?(Hash)
22
- message
23
- else
24
- { error: ensure_utf8(message) }
25
- end
14
+ return message if message.is_a?(Hash)
15
+ return message.as_json if message.is_a?(Exceptions::ValidationErrors)
16
+
17
+ { error: ensure_utf8(message) }
26
18
  end
27
19
 
28
20
  def ensure_utf8(message)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class Jsonapi < Json; end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module ErrorFormatter
5
+ class SerializableHash < Json; end
6
+ end
7
+ end
@@ -2,26 +2,19 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Txt
6
- extend Base
7
-
8
- class << self
9
- def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
- message = present(message, env)
11
-
12
- result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message
13
- Array.wrap(result).tap do |final_result|
14
- rescue_options = options[:rescue_options] || {}
15
- if rescue_options[:backtrace] && backtrace.present?
16
- final_result << 'backtrace:'
17
- final_result.concat(backtrace)
18
- end
19
- if rescue_options[:original_exception] && original_exception
20
- final_result << 'original exception:'
21
- final_result << original_exception.inspect
22
- end
23
- end.join("\r\n ")
24
- end
5
+ class Txt < Base
6
+ def self.format_structured_message(structured_message)
7
+ message = structured_message[:message] || Grape::Json.dump(structured_message)
8
+ Array.wrap(message).tap do |final_message|
9
+ if structured_message.key?(:backtrace)
10
+ final_message << 'backtrace:'
11
+ final_message.concat(structured_message[:backtrace])
12
+ end
13
+ if structured_message.key?(:original_exception)
14
+ final_message << 'original exception:'
15
+ final_message << structured_message[:original_exception]
16
+ end
17
+ end.join("\r\n ")
25
18
  end
26
19
  end
27
20
  end
@@ -2,19 +2,9 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module Xml
6
- extend Base
7
-
8
- class << self
9
- def call(message, backtrace, options = {}, env = nil, original_exception = nil)
10
- message = present(message, env)
11
-
12
- result = message.is_a?(Hash) ? message : { message: message }
13
- rescue_options = options[:rescue_options] || {}
14
- result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
15
- result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
16
- result.respond_to?(:to_xml) ? result.to_xml(root: :error) : result.to_s
17
- end
5
+ class Xml < Base
6
+ def self.format_structured_message(structured_message)
7
+ structured_message.respond_to?(:to_xml) ? structured_message.to_xml(root: :error) : structured_message.to_s
18
8
  end
19
9
  end
20
10
  end
@@ -2,34 +2,14 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- extend Util::Registrable
5
+ extend Grape::Util::Registry
6
6
 
7
- class << self
8
- def builtin_formatters
9
- @builtin_formatters ||= {
10
- serializable_hash: Grape::ErrorFormatter::Json,
11
- json: Grape::ErrorFormatter::Json,
12
- jsonapi: Grape::ErrorFormatter::Json,
13
- txt: Grape::ErrorFormatter::Txt,
14
- xml: Grape::ErrorFormatter::Xml
15
- }
16
- end
7
+ module_function
17
8
 
18
- def formatters(**options)
19
- builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {})
20
- end
9
+ def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
10
+ return error_formatters[format] if error_formatters&.key?(format)
21
11
 
22
- def formatter_for(api_format, **options)
23
- spec = formatters(**options)[api_format]
24
- case spec
25
- when nil
26
- options[:default_error_formatter] || Grape::ErrorFormatter::Txt
27
- when Symbol
28
- method(spec)
29
- else
30
- spec
31
- end
32
- end
12
+ registry[format] || default_error_formatter || Grape::ErrorFormatter::Txt
33
13
  end
34
14
  end
35
15
  end
@@ -9,7 +9,7 @@ module Grape
9
9
 
10
10
  attr_reader :status, :headers
11
11
 
12
- def initialize(status: nil, message: nil, headers: nil, **_options)
12
+ def initialize(status: nil, message: nil, headers: nil)
13
13
  super(message)
14
14
 
15
15
  @status = status
@@ -26,42 +26,32 @@ module Grape
26
26
  # if BASE_ATTRIBUTES_KEY.key respond to a string message, then short_message is returned
27
27
  # if BASE_ATTRIBUTES_KEY.key respond to a Hash, means it may have problem , summary and resolution
28
28
  def compose_message(key, **attributes)
29
- short_message = translate_message(key, **attributes)
30
- if short_message.is_a? Hash
31
- @problem = problem(key, **attributes)
32
- @summary = summary(key, **attributes)
33
- @resolution = resolution(key, **attributes)
34
- [['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].each_with_object(+'') do |detail_array, message|
35
- message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank?
36
- message
37
- end
38
- else
39
- short_message
40
- end
41
- end
29
+ short_message = translate_message(key, attributes)
30
+ return short_message unless short_message.is_a?(Hash)
42
31
 
43
- def problem(key, **attributes)
44
- translate_message(:"#{key}.problem", **attributes)
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
45
35
  end
46
36
 
47
- def summary(key, **attributes)
48
- translate_message(:"#{key}.summary", **attributes)
49
- end
37
+ def each_steps(key, attributes)
38
+ return enum_for(:each_steps, key, attributes) unless block_given?
50
39
 
51
- def resolution(key, **attributes)
52
- translate_message(:"#{key}.resolution", **attributes)
40
+ yield 'Problem', translate_message(:"#{key}.problem", attributes)
41
+ yield 'Summary', translate_message(:"#{key}.summary", attributes)
42
+ yield 'Resolution', translate_message(:"#{key}.resolution", attributes)
53
43
  end
54
44
 
55
- def translate_attributes(keys, **options)
45
+ def translate_attributes(keys, options = {})
56
46
  keys.map do |key|
57
- translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options)
47
+ translate("#{BASE_ATTRIBUTES_KEY}.#{key}", options.merge(default: key.to_s))
58
48
  end.join(', ')
59
49
  end
60
50
 
61
- def translate_message(key, **options)
51
+ def translate_message(key, options = {})
62
52
  case key
63
53
  when Symbol
64
- translate("#{BASE_MESSAGES_KEY}.#{key}", default: '', **options)
54
+ translate("#{BASE_MESSAGES_KEY}.#{key}", options.merge(default: ''))
65
55
  when Proc
66
56
  key.call
67
57
  else
@@ -69,14 +59,12 @@ module Grape
69
59
  end
70
60
  end
71
61
 
72
- def translate(key, **options)
73
- options = options.dup
74
- options[:default] &&= options[:default].to_s
62
+ def translate(key, options)
75
63
  message = ::I18n.translate(key, **options)
76
- message.presence || fallback_message(key, **options)
64
+ message.presence || fallback_message(key, options)
77
65
  end
78
66
 
79
- def fallback_message(key, **options)
67
+ def fallback_message(key, options)
80
68
  if ::I18n.enforce_available_locales && ::I18n.available_locales.exclude?(FALLBACK_LOCALE)
81
69
  key
82
70
  else
@@ -2,16 +2,17 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class Validation < Grape::Exceptions::Base
5
+ class Validation < Base
6
6
  attr_accessor :params, :message_key
7
7
 
8
- def initialize(params:, message: nil, **args)
8
+ def initialize(params:, message: nil, status: nil, headers: nil)
9
9
  @params = params
10
10
  if message
11
11
  @message_key = message if message.is_a?(Symbol)
12
- args[:message] = translate_message(message)
12
+ message = translate_message(message)
13
13
  end
14
- super(**args)
14
+
15
+ super(status: status, message: message, headers: headers)
15
16
  end
16
17
 
17
18
  # Remove all the unnecessary stuff from Grape::Exceptions::Base like status
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Exceptions
5
- class ValidationErrors < Grape::Exceptions::Base
5
+ class ValidationErrors < Base
6
6
  ERRORS_FORMAT_KEY = 'grape.errors.format'
7
7
  DEFAULT_ERRORS_FORMAT = '%<attributes>s %<message>s'
8
8
 
@@ -10,7 +10,7 @@ module Grape
10
10
 
11
11
  attr_reader :errors
12
12
 
13
- def initialize(errors: [], headers: {}, **_options)
13
+ def initialize(errors: [], headers: {})
14
14
  @errors = errors.group_by(&:params)
15
15
  super(message: full_messages.join(', '), status: 400, headers: headers)
16
16
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Formatter
5
+ class Base
6
+ def self.call(_object, _env)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.inherited(klass)
11
+ super
12
+ Formatter.register(klass)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Json
6
- class << self
7
- def call(object, _env)
8
- return object.to_json if object.respond_to?(:to_json)
5
+ class Json < Base
6
+ def self.call(object, _env)
7
+ return object.to_json if object.respond_to?(:to_json)
9
8
 
10
- ::Grape::Json.dump(object)
11
- end
9
+ ::Grape::Json.dump(object)
12
10
  end
13
11
  end
14
12
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module SerializableHash
5
+ class SerializableHash < Base
6
6
  class << self
7
7
  def call(object, _env)
8
8
  return object if object.is_a?(String)