grape 2.2.0 → 2.3.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +7 -6
  4. data/UPGRADING.md +19 -0
  5. data/grape.gemspec +5 -5
  6. data/lib/grape/api/instance.rb +22 -58
  7. data/lib/grape/api.rb +2 -11
  8. data/lib/grape/dsl/desc.rb +27 -24
  9. data/lib/grape/dsl/inside_route.rb +12 -23
  10. data/lib/grape/dsl/parameters.rb +2 -2
  11. data/lib/grape/dsl/routing.rb +5 -12
  12. data/lib/grape/endpoint.rb +76 -79
  13. data/lib/grape/error_formatter/base.rb +51 -21
  14. data/lib/grape/error_formatter/json.rb +7 -24
  15. data/lib/grape/error_formatter/jsonapi.rb +7 -0
  16. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  17. data/lib/grape/error_formatter/txt.rb +13 -20
  18. data/lib/grape/error_formatter/xml.rb +3 -13
  19. data/lib/grape/error_formatter.rb +4 -12
  20. data/lib/grape/exceptions/base.rb +18 -30
  21. data/lib/grape/exceptions/validation.rb +5 -4
  22. data/lib/grape/exceptions/validation_errors.rb +2 -2
  23. data/lib/grape/formatter/base.rb +16 -0
  24. data/lib/grape/formatter/json.rb +4 -6
  25. data/lib/grape/formatter/serializable_hash.rb +1 -1
  26. data/lib/grape/formatter/txt.rb +3 -5
  27. data/lib/grape/formatter/xml.rb +4 -6
  28. data/lib/grape/formatter.rb +4 -12
  29. data/lib/grape/http/headers.rb +1 -0
  30. data/lib/grape/middleware/error.rb +2 -0
  31. data/lib/grape/middleware/formatter.rb +1 -1
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -3
  33. data/lib/grape/middleware/versioner/base.rb +82 -0
  34. data/lib/grape/middleware/versioner/header.rb +3 -9
  35. data/lib/grape/middleware/versioner/param.rb +0 -2
  36. data/lib/grape/middleware/versioner/path.rb +0 -2
  37. data/lib/grape/middleware/versioner.rb +5 -3
  38. data/lib/grape/namespace.rb +1 -1
  39. data/lib/grape/parser/base.rb +16 -0
  40. data/lib/grape/parser/json.rb +6 -8
  41. data/lib/grape/parser/jsonapi.rb +7 -0
  42. data/lib/grape/parser/xml.rb +6 -8
  43. data/lib/grape/parser.rb +5 -7
  44. data/lib/grape/path.rb +39 -56
  45. data/lib/grape/request.rb +2 -2
  46. data/lib/grape/router/base_route.rb +2 -2
  47. data/lib/grape/router/greedy_route.rb +2 -2
  48. data/lib/grape/router/pattern.rb +23 -18
  49. data/lib/grape/router/route.rb +13 -5
  50. data/lib/grape/router.rb +5 -5
  51. data/lib/grape/util/registry.rb +27 -0
  52. data/lib/grape/validations/contract_scope.rb +2 -39
  53. data/lib/grape/validations/params_scope.rb +7 -11
  54. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  55. data/lib/grape/validations/validator_factory.rb +2 -2
  56. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  57. data/lib/grape/validations/validators/base.rb +5 -9
  58. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  59. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  60. data/lib/grape/validations/validators/default_validator.rb +1 -1
  61. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  62. data/lib/grape/validations/validators/length_validator.rb +1 -1
  63. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  64. data/lib/grape/validations/validators/values_validator.rb +15 -57
  65. data/lib/grape/validations.rb +8 -17
  66. data/lib/grape/version.rb +1 -1
  67. data/lib/grape.rb +1 -1
  68. metadata +14 -11
  69. data/lib/grape/middleware/versioner_helpers.rb +0 -75
  70. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -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
@@ -196,19 +197,14 @@ module Grape
196
197
  version.length == 1 ? version.first : version
197
198
  end
198
199
 
199
- def merge_route_options(**default)
200
- options[:route_options].clone.merge!(**default)
201
- end
202
-
203
200
  def map_routes
204
201
  options[:method].map { |method| options[:path].map { |path| yield method, path } }
205
202
  end
206
203
 
207
- def prepare_path(path)
204
+ def prepare_default_path_settings
208
205
  namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
209
206
  namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
210
- path_settings = namespace_stackable_hash.merge!(namespace_inheritable_hash)
211
- Path.new(path, namespace, path_settings)
207
+ namespace_stackable_hash.merge!(namespace_inheritable_hash)
212
208
  end
213
209
 
214
210
  def namespace
@@ -259,9 +255,10 @@ module Grape
259
255
  run_filters befores, :before
260
256
 
261
257
  if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
262
- 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?
263
260
 
264
- header Grape::Http::Headers::ALLOW, allowed_methods
261
+ header Grape::Http::Headers::ALLOW, allow_header_value
265
262
  response_object = ''
266
263
  status 204
267
264
  else
@@ -287,59 +284,6 @@ module Grape
287
284
  end
288
285
  end
289
286
 
290
- def build_stack(helpers)
291
- stack = Grape::Middleware::Stack.new
292
-
293
- content_types = namespace_stackable_with_hash(:content_types)
294
- format = namespace_inheritable(:format)
295
-
296
- stack.use Rack::Head
297
- stack.use Class.new(Grape::Middleware::Error),
298
- helpers: helpers,
299
- format: format,
300
- content_types: content_types,
301
- default_status: namespace_inheritable(:default_error_status),
302
- rescue_all: namespace_inheritable(:rescue_all),
303
- rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
304
- default_error_formatter: namespace_inheritable(:default_error_formatter),
305
- error_formatters: namespace_stackable_with_hash(:error_formatters),
306
- rescue_options: namespace_stackable_with_hash(:rescue_options),
307
- rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
308
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
309
- all_rescue_handler: namespace_inheritable(:all_rescue_handler),
310
- grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
311
-
312
- stack.concat namespace_stackable(:middleware)
313
-
314
- if namespace_inheritable(:version).present?
315
- stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
316
- versions: namespace_inheritable(:version).flatten,
317
- version_options: namespace_inheritable(:version_options),
318
- prefix: namespace_inheritable(:root_prefix),
319
- mount_path: namespace_stackable(:mount_path).first
320
- end
321
-
322
- stack.use Grape::Middleware::Formatter,
323
- format: format,
324
- default_format: namespace_inheritable(:default_format) || :txt,
325
- content_types: content_types,
326
- formatters: namespace_stackable_with_hash(:formatters),
327
- parsers: namespace_stackable_with_hash(:parsers)
328
-
329
- builder = stack.build
330
- builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
331
- builder.to_app
332
- end
333
-
334
- def build_helpers
335
- helpers = namespace_stackable(:helpers)
336
- return if helpers.empty?
337
-
338
- Module.new { helpers.each { |mod_to_include| include mod_to_include } }
339
- end
340
-
341
- private :build_stack, :build_helpers
342
-
343
287
  def execute
344
288
  @block&.call(self)
345
289
  end
@@ -411,7 +355,7 @@ module Grape
411
355
  return enum_for(:validations) unless block_given?
412
356
 
413
357
  route_setting(:saved_validations)&.each do |saved_validation|
414
- yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation)
358
+ yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
415
359
  end
416
360
  end
417
361
 
@@ -419,5 +363,58 @@ module Grape
419
363
  options[:options_route_enabled] &&
420
364
  env[Rack::REQUEST_METHOD] == Rack::OPTIONS
421
365
  end
366
+
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 } }
418
+ end
422
419
  end
423
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,28 +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
- result = merge_rescue_options(result, backtrace, options, original_exception) if result.is_a?(Hash)
13
-
14
- ::Grape::Json.dump(result)
7
+ def format_structured_message(structured_message)
8
+ ::Grape::Json.dump(structured_message)
15
9
  end
16
10
 
17
11
  private
18
12
 
19
13
  def wrap_message(message)
20
- if message.is_a?(Hash)
21
- message
22
- elsif message.is_a?(Exceptions::ValidationErrors)
23
- message.as_json
24
- else
25
- { error: ensure_utf8(message) }
26
- 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) }
27
18
  end
28
19
 
29
20
  def ensure_utf8(message)
@@ -31,14 +22,6 @@ module Grape
31
22
 
32
23
  message.encode('UTF-8', invalid: :replace, undef: :replace)
33
24
  end
34
-
35
- def merge_rescue_options(result, backtrace, options, original_exception)
36
- rescue_options = options[:rescue_options] || {}
37
- result = result.merge(backtrace: backtrace) if rescue_options[:backtrace] && backtrace && !backtrace.empty?
38
- result = result.merge(original_exception: original_exception.inspect) if rescue_options[:original_exception] && original_exception
39
-
40
- result
41
- end
42
25
  end
43
26
  end
44
27
  end
@@ -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,22 +2,14 @@
2
2
 
3
3
  module Grape
4
4
  module ErrorFormatter
5
- module_function
5
+ extend Grape::Util::Registry
6
6
 
7
- DEFAULTS = {
8
- serializable_hash: Grape::ErrorFormatter::Json,
9
- json: Grape::ErrorFormatter::Json,
10
- jsonapi: Grape::ErrorFormatter::Json,
11
- txt: Grape::ErrorFormatter::Txt,
12
- xml: Grape::ErrorFormatter::Xml
13
- }.freeze
7
+ module_function
14
8
 
15
9
  def formatter_for(format, error_formatters = nil, default_error_formatter = nil)
16
- select_formatter(error_formatters, format) || default_error_formatter || DEFAULTS[:txt]
17
- end
10
+ return error_formatters[format] if error_formatters&.key?(format)
18
11
 
19
- def select_formatter(error_formatters, format)
20
- error_formatters&.key?(format) ? error_formatters[format] : DEFAULTS[format]
12
+ registry[format] || default_error_formatter || Grape::ErrorFormatter::Txt
21
13
  end
22
14
  end
23
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)
@@ -2,11 +2,9 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Txt
6
- class << self
7
- def call(object, _env)
8
- object.respond_to?(:to_txt) ? object.to_txt : object.to_s
9
- end
5
+ class Txt < Base
6
+ def self.call(object, _env)
7
+ object.respond_to?(:to_txt) ? object.to_txt : object.to_s
10
8
  end
11
9
  end
12
10
  end
@@ -2,13 +2,11 @@
2
2
 
3
3
  module Grape
4
4
  module Formatter
5
- module Xml
6
- class << self
7
- def call(object, _env)
8
- return object.to_xml if object.respond_to?(:to_xml)
5
+ class Xml < Base
6
+ def self.call(object, _env)
7
+ return object.to_xml if object.respond_to?(:to_xml)
9
8
 
10
- raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
11
- end
9
+ raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')
12
10
  end
13
11
  end
14
12
  end