grape 2.1.3 → 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 (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)