grape 2.2.0 → 2.4.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +41 -18
  5. data/UPGRADING.md +75 -1
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +25 -60
  8. data/lib/grape/api.rb +44 -76
  9. data/lib/grape/cookies.rb +31 -25
  10. data/lib/grape/dsl/api.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +27 -24
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/helpers.rb +1 -1
  14. data/lib/grape/dsl/inside_route.rb +17 -40
  15. data/lib/grape/dsl/parameters.rb +5 -5
  16. data/lib/grape/dsl/routing.rb +14 -13
  17. data/lib/grape/endpoint.rb +100 -106
  18. data/lib/grape/error_formatter/base.rb +51 -21
  19. data/lib/grape/error_formatter/json.rb +7 -24
  20. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  21. data/lib/grape/error_formatter/txt.rb +13 -20
  22. data/lib/grape/error_formatter/xml.rb +3 -13
  23. data/lib/grape/error_formatter.rb +4 -12
  24. data/lib/grape/exceptions/base.rb +18 -30
  25. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  26. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  27. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  28. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  29. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  30. data/lib/grape/exceptions/validation.rb +5 -4
  31. data/lib/grape/exceptions/validation_errors.rb +2 -2
  32. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  33. data/lib/grape/extensions/hash.rb +2 -1
  34. data/lib/grape/extensions/hashie/mash.rb +3 -5
  35. data/lib/grape/formatter/base.rb +16 -0
  36. data/lib/grape/formatter/json.rb +4 -6
  37. data/lib/grape/formatter/serializable_hash.rb +1 -1
  38. data/lib/grape/formatter/txt.rb +3 -5
  39. data/lib/grape/formatter/xml.rb +4 -6
  40. data/lib/grape/formatter.rb +4 -12
  41. data/lib/grape/locale/en.yml +44 -44
  42. data/lib/grape/middleware/auth/base.rb +11 -32
  43. data/lib/grape/middleware/auth/dsl.rb +23 -29
  44. data/lib/grape/middleware/base.rb +30 -11
  45. data/lib/grape/middleware/error.rb +18 -24
  46. data/lib/grape/middleware/formatter.rb +39 -73
  47. data/lib/grape/middleware/stack.rb +26 -36
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -3
  49. data/lib/grape/middleware/versioner/base.rb +74 -0
  50. data/lib/grape/middleware/versioner/header.rb +4 -10
  51. data/lib/grape/middleware/versioner/param.rb +2 -5
  52. data/lib/grape/middleware/versioner/path.rb +0 -2
  53. data/lib/grape/middleware/versioner.rb +5 -3
  54. data/lib/grape/namespace.rb +1 -1
  55. data/lib/grape/params_builder/base.rb +18 -0
  56. data/lib/grape/params_builder/hash.rb +11 -0
  57. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  58. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  59. data/lib/grape/params_builder.rb +32 -0
  60. data/lib/grape/parser/base.rb +16 -0
  61. data/lib/grape/parser/json.rb +6 -8
  62. data/lib/grape/parser/xml.rb +6 -8
  63. data/lib/grape/parser.rb +5 -7
  64. data/lib/grape/path.rb +39 -56
  65. data/lib/grape/request.rb +162 -23
  66. data/lib/grape/router/base_route.rb +2 -2
  67. data/lib/grape/router/greedy_route.rb +2 -2
  68. data/lib/grape/router/pattern.rb +23 -18
  69. data/lib/grape/router/route.rb +14 -6
  70. data/lib/grape/router.rb +30 -12
  71. data/lib/grape/util/registry.rb +27 -0
  72. data/lib/grape/validations/contract_scope.rb +2 -39
  73. data/lib/grape/validations/params_scope.rb +15 -14
  74. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  75. data/lib/grape/validations/validator_factory.rb +2 -2
  76. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  77. data/lib/grape/validations/validators/base.rb +7 -11
  78. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  79. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  80. data/lib/grape/validations/validators/default_validator.rb +1 -1
  81. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  82. data/lib/grape/validations/validators/length_validator.rb +1 -1
  83. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  84. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  85. data/lib/grape/validations/validators/values_validator.rb +15 -57
  86. data/lib/grape/validations.rb +8 -17
  87. data/lib/grape/version.rb +1 -1
  88. data/lib/grape.rb +14 -2
  89. metadata +24 -16
  90. data/lib/grape/http/headers.rb +0 -55
  91. data/lib/grape/middleware/helpers.rb +0 -12
  92. data/lib/grape/middleware/versioner_helpers.rb +0 -75
  93. data/lib/grape/util/lazy/object.rb +0 -45
  94. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -6,11 +6,15 @@ module Grape
6
6
  # on the instance level of this class may be called
7
7
  # from inside a `get`, `post`, etc.
8
8
  class Endpoint
9
+ extend Forwardable
9
10
  include Grape::DSL::Settings
10
11
  include Grape::DSL::InsideRoute
11
12
 
12
13
  attr_accessor :block, :source, :options
13
- attr_reader :env, :request, :headers, :params
14
+ attr_reader :env, :request
15
+
16
+ def_delegators :request, :params, :headers, :cookies
17
+ def_delegator :cookies, :response_cookies
14
18
 
15
19
  class << self
16
20
  def new(...)
@@ -30,7 +34,7 @@ module Grape
30
34
 
31
35
  def run_before_each(endpoint)
32
36
  superclass.run_before_each(endpoint) unless self == Endpoint
33
- before_each.each { |blk| blk.call(endpoint) if blk.respond_to?(:call) }
37
+ before_each.each { |blk| blk.try(:call, endpoint) }
34
38
  end
35
39
 
36
40
  # @api private
@@ -135,7 +139,7 @@ module Grape
135
139
  end
136
140
 
137
141
  def routes
138
- @routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes
142
+ @routes ||= endpoints&.collect(&:routes)&.flatten || to_routes
139
143
  end
140
144
 
141
145
  def reset_routes!
@@ -145,27 +149,27 @@ module Grape
145
149
  end
146
150
 
147
151
  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
152
+ return endpoints.each { |e| e.mount_in(router) } if endpoints
153
+
154
+ reset_routes!
155
+ routes.each do |route|
156
+ router.append(route.apply(self))
157
+ next unless !namespace_inheritable(:do_not_route_head) && route.request_method == Rack::GET
158
+
159
+ route.dup.then do |head_route|
160
+ head_route.convert_to_head_request!
161
+ router.append(head_route.apply(self))
159
162
  end
160
163
  end
161
164
  end
162
165
 
163
166
  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)
167
+ default_route_options = prepare_default_route_attributes
168
+
169
+ map_routes do |method, raw_path|
170
+ prepared_path = Path.new(raw_path, namespace, prepare_default_path_settings)
171
+ params = options[:route_options].present? ? options[:route_options].merge(default_route_options) : default_route_options
172
+ route = Grape::Router::Route.new(method, prepared_path.origin, prepared_path.suffix, params)
169
173
  route.apply(self)
170
174
  end.flatten
171
175
  end
@@ -196,19 +200,14 @@ module Grape
196
200
  version.length == 1 ? version.first : version
197
201
  end
198
202
 
199
- def merge_route_options(**default)
200
- options[:route_options].clone.merge!(**default)
201
- end
202
-
203
203
  def map_routes
204
204
  options[:method].map { |method| options[:path].map { |path| yield method, path } }
205
205
  end
206
206
 
207
- def prepare_path(path)
207
+ def prepare_default_path_settings
208
208
  namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
209
209
  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)
210
+ namespace_stackable_hash.merge!(namespace_inheritable_hash)
212
211
  end
213
212
 
214
213
  def namespace
@@ -229,7 +228,7 @@ module Grape
229
228
  # Return the collection of endpoints within this endpoint.
230
229
  # This is the case when an Grape::API mounts another Grape::API.
231
230
  def endpoints
232
- options[:app].endpoints if options[:app].respond_to?(:endpoints)
231
+ @endpoints ||= options[:app].try(:endpoints)
233
232
  end
234
233
 
235
234
  def equals?(endpoint)
@@ -249,19 +248,16 @@ module Grape
249
248
 
250
249
  def run
251
250
  ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
252
- @header = Grape::Util::Header.new
253
251
  @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
254
- @params = @request.params
255
- @headers = @request.headers
256
252
  begin
257
- cookies.read(@request)
258
253
  self.class.run_before_each(self)
259
254
  run_filters befores, :before
260
255
 
261
- if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
262
- raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options?
256
+ if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
257
+ header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
258
+ raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?
263
259
 
264
- header Grape::Http::Headers::ALLOW, allowed_methods
260
+ header 'Allow', header['Allow']
265
261
  response_object = ''
266
262
  status 204
267
263
  else
@@ -272,7 +268,7 @@ module Grape
272
268
  end
273
269
 
274
270
  run_filters afters, :after
275
- cookies.write(header)
271
+ build_response_cookies
276
272
 
277
273
  # status verifies body presence when DELETE
278
274
  @body ||= response_object
@@ -287,59 +283,6 @@ module Grape
287
283
  end
288
284
  end
289
285
 
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
286
  def execute
344
287
  @block&.call(self)
345
288
  end
@@ -387,37 +330,88 @@ module Grape
387
330
  extend post_extension if post_extension
388
331
  end
389
332
 
390
- def befores
391
- namespace_stackable(:befores)
333
+ %i[befores before_validations after_validations afters finallies].each do |method|
334
+ define_method method do
335
+ namespace_stackable(method)
336
+ end
392
337
  end
393
338
 
394
- def before_validations
395
- namespace_stackable(:before_validations)
396
- end
339
+ def validations
340
+ return enum_for(:validations) unless block_given?
397
341
 
398
- def after_validations
399
- namespace_stackable(:after_validations)
342
+ route_setting(:saved_validations)&.each do |saved_validation|
343
+ yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
344
+ end
400
345
  end
401
346
 
402
- def afters
403
- namespace_stackable(:afters)
347
+ def options?
348
+ options[:options_route_enabled] &&
349
+ env[Rack::REQUEST_METHOD] == Rack::OPTIONS
404
350
  end
405
351
 
406
- def finallies
407
- namespace_stackable(:finallies)
352
+ private
353
+
354
+ def build_stack(helpers)
355
+ stack = Grape::Middleware::Stack.new
356
+
357
+ content_types = namespace_stackable_with_hash(:content_types)
358
+ format = namespace_inheritable(:format)
359
+
360
+ stack.use Rack::Head
361
+ stack.use Rack::Lint if lint?
362
+ stack.use Class.new(Grape::Middleware::Error),
363
+ helpers: helpers,
364
+ format: format,
365
+ content_types: content_types,
366
+ default_status: namespace_inheritable(:default_error_status),
367
+ rescue_all: namespace_inheritable(:rescue_all),
368
+ rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
369
+ default_error_formatter: namespace_inheritable(:default_error_formatter),
370
+ error_formatters: namespace_stackable_with_hash(:error_formatters),
371
+ rescue_options: namespace_stackable_with_hash(:rescue_options),
372
+ rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers),
373
+ base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers),
374
+ all_rescue_handler: namespace_inheritable(:all_rescue_handler),
375
+ grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
376
+
377
+ stack.concat namespace_stackable(:middleware)
378
+
379
+ if namespace_inheritable(:version).present?
380
+ stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
381
+ versions: namespace_inheritable(:version).flatten,
382
+ version_options: namespace_inheritable(:version_options),
383
+ prefix: namespace_inheritable(:root_prefix),
384
+ mount_path: namespace_stackable(:mount_path).first
385
+ end
386
+
387
+ stack.use Grape::Middleware::Formatter,
388
+ format: format,
389
+ default_format: namespace_inheritable(:default_format) || :txt,
390
+ content_types: content_types,
391
+ formatters: namespace_stackable_with_hash(:formatters),
392
+ parsers: namespace_stackable_with_hash(:parsers)
393
+
394
+ builder = stack.build
395
+ builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
396
+ builder.to_app
408
397
  end
409
398
 
410
- def validations
411
- return enum_for(:validations) unless block_given?
399
+ def build_helpers
400
+ helpers = namespace_stackable(:helpers)
401
+ return if helpers.empty?
412
402
 
413
- route_setting(:saved_validations)&.each do |saved_validation|
414
- yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation)
403
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
404
+ end
405
+
406
+ def build_response_cookies
407
+ response_cookies do |name, value|
408
+ cookie_value = value.is_a?(Hash) ? value : { value: value }
409
+ Rack::Utils.set_cookie_header! header, name, cookie_value
415
410
  end
416
411
  end
417
412
 
418
- def options?
419
- options[:options_route_enabled] &&
420
- env[Rack::REQUEST_METHOD] == Rack::OPTIONS
413
+ def lint?
414
+ namespace_inheritable(:lint) || Grape.config.lint
421
415
  end
422
416
  end
423
417
  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 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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class ConflictingTypes < Base
6
+ def initialize
7
+ super(message: compose_message(:conflicting_types), status: 400)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class InvalidParameters < Base
6
+ def initialize
7
+ super(message: compose_message(:invalid_parameters), status: 400)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Exceptions
5
+ class TooDeepParameters < Base
6
+ def initialize(limit)
7
+ super(message: compose_message(:too_deep_parameters, limit: limit), status: 400)
8
+ end
9
+ end
10
+ end
11
+ end