grape 2.0.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +151 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +404 -334
  5. data/UPGRADING.md +279 -7
  6. data/grape.gemspec +8 -8
  7. data/lib/grape/api/instance.rb +34 -66
  8. data/lib/grape/api.rb +47 -70
  9. data/lib/grape/content_types.rb +13 -10
  10. data/lib/grape/cookies.rb +31 -24
  11. data/lib/grape/dry_types.rb +0 -2
  12. data/lib/grape/dsl/api.rb +0 -2
  13. data/lib/grape/dsl/desc.rb +49 -44
  14. data/lib/grape/dsl/headers.rb +2 -2
  15. data/lib/grape/dsl/helpers.rb +8 -4
  16. data/lib/grape/dsl/inside_route.rb +67 -54
  17. data/lib/grape/dsl/parameters.rb +10 -9
  18. data/lib/grape/dsl/request_response.rb +14 -18
  19. data/lib/grape/dsl/routing.rb +34 -17
  20. data/lib/grape/dsl/validations.rb +13 -0
  21. data/lib/grape/endpoint.rb +120 -118
  22. data/lib/grape/{util/env.rb → env.rb} +0 -5
  23. data/lib/grape/error_formatter/base.rb +51 -21
  24. data/lib/grape/error_formatter/json.rb +7 -15
  25. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  26. data/lib/grape/error_formatter/txt.rb +11 -17
  27. data/lib/grape/error_formatter/xml.rb +3 -13
  28. data/lib/grape/error_formatter.rb +5 -25
  29. data/lib/grape/exceptions/base.rb +18 -30
  30. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  31. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  32. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  33. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  34. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  35. data/lib/grape/exceptions/validation.rb +5 -6
  36. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  37. data/lib/grape/exceptions/validation_errors.rb +4 -6
  38. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  39. data/lib/grape/extensions/hash.rb +7 -2
  40. data/lib/grape/extensions/hashie/mash.rb +3 -5
  41. data/lib/grape/formatter/base.rb +16 -0
  42. data/lib/grape/formatter/json.rb +4 -6
  43. data/lib/grape/formatter/serializable_hash.rb +1 -1
  44. data/lib/grape/formatter/txt.rb +3 -5
  45. data/lib/grape/formatter/xml.rb +4 -6
  46. data/lib/grape/formatter.rb +7 -25
  47. data/lib/grape/{util/json.rb → json.rb} +1 -3
  48. data/lib/grape/locale/en.yml +46 -42
  49. data/lib/grape/middleware/auth/base.rb +11 -34
  50. data/lib/grape/middleware/auth/dsl.rb +23 -31
  51. data/lib/grape/middleware/base.rb +41 -23
  52. data/lib/grape/middleware/error.rb +77 -76
  53. data/lib/grape/middleware/formatter.rb +48 -79
  54. data/lib/grape/middleware/globals.rb +1 -3
  55. data/lib/grape/middleware/stack.rb +26 -37
  56. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  57. data/lib/grape/middleware/versioner/base.rb +74 -0
  58. data/lib/grape/middleware/versioner/header.rb +59 -126
  59. data/lib/grape/middleware/versioner/param.rb +4 -25
  60. data/lib/grape/middleware/versioner/path.rb +10 -34
  61. data/lib/grape/middleware/versioner.rb +7 -14
  62. data/lib/grape/namespace.rb +4 -5
  63. data/lib/grape/params_builder/base.rb +18 -0
  64. data/lib/grape/params_builder/hash.rb +11 -0
  65. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  66. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  67. data/lib/grape/params_builder.rb +32 -0
  68. data/lib/grape/parser/base.rb +16 -0
  69. data/lib/grape/parser/json.rb +6 -8
  70. data/lib/grape/parser/xml.rb +6 -8
  71. data/lib/grape/parser.rb +5 -23
  72. data/lib/grape/path.rb +38 -60
  73. data/lib/grape/request.rb +161 -30
  74. data/lib/grape/router/base_route.rb +39 -0
  75. data/lib/grape/router/greedy_route.rb +20 -0
  76. data/lib/grape/router/pattern.rb +45 -31
  77. data/lib/grape/router/route.rb +28 -57
  78. data/lib/grape/router.rb +56 -43
  79. data/lib/grape/util/base_inheritable.rb +4 -4
  80. data/lib/grape/util/cache.rb +0 -3
  81. data/lib/grape/util/endpoint_configuration.rb +1 -1
  82. data/lib/grape/util/header.rb +13 -0
  83. data/lib/grape/util/inheritable_values.rb +0 -2
  84. data/lib/grape/util/lazy/block.rb +29 -0
  85. data/lib/grape/util/lazy/value.rb +38 -0
  86. data/lib/grape/util/lazy/value_array.rb +21 -0
  87. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  88. data/lib/grape/util/lazy/value_hash.rb +21 -0
  89. data/lib/grape/util/media_type.rb +70 -0
  90. data/lib/grape/util/registry.rb +27 -0
  91. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  92. data/lib/grape/util/stackable_values.rb +1 -6
  93. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  94. data/lib/grape/validations/attributes_doc.rb +38 -36
  95. data/lib/grape/validations/attributes_iterator.rb +1 -0
  96. data/lib/grape/validations/contract_scope.rb +34 -0
  97. data/lib/grape/validations/params_scope.rb +36 -32
  98. data/lib/grape/validations/types/array_coercer.rb +0 -2
  99. data/lib/grape/validations/types/dry_type_coercer.rb +9 -15
  100. data/lib/grape/validations/types/json.rb +0 -2
  101. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  102. data/lib/grape/validations/types/set_coercer.rb +0 -3
  103. data/lib/grape/validations/types.rb +0 -3
  104. data/lib/grape/validations/validator_factory.rb +2 -2
  105. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  106. data/lib/grape/validations/validators/base.rb +8 -11
  107. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  108. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  109. data/lib/grape/validations/validators/default_validator.rb +6 -2
  110. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  111. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  112. data/lib/grape/validations/validators/length_validator.rb +49 -0
  113. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  114. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  115. data/lib/grape/validations/validators/values_validator.rb +20 -57
  116. data/lib/grape/validations.rb +8 -21
  117. data/lib/grape/version.rb +1 -1
  118. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  119. data/lib/grape.rb +42 -274
  120. metadata +45 -44
  121. data/lib/grape/eager_load.rb +0 -20
  122. data/lib/grape/http/headers.rb +0 -71
  123. data/lib/grape/middleware/helpers.rb +0 -12
  124. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  125. data/lib/grape/router/attribute_translator.rb +0 -63
  126. data/lib/grape/util/lazy_block.rb +0 -27
  127. data/lib/grape/util/lazy_object.rb +0 -43
  128. data/lib/grape/util/lazy_value.rb +0 -91
  129. data/lib/grape/util/registrable.rb +0 -15
  130. data/lib/grape/validations/types/build_coercer.rb +0 -94
@@ -6,15 +6,19 @@ 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
- def new(*args, &block)
17
- self == Endpoint ? Class.new(Endpoint).new(*args, &block) : super
20
+ def new(...)
21
+ self == Endpoint ? Class.new(Endpoint).new(...) : super
18
22
  end
19
23
 
20
24
  def before_each(new_setup = false, &block)
@@ -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
@@ -55,7 +59,7 @@ module Grape
55
59
 
56
60
  proc do |endpoint_instance|
57
61
  ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
58
- method.bind(endpoint_instance).call
62
+ method.bind_call(endpoint_instance)
59
63
  end
60
64
  end
61
65
  end
@@ -114,10 +118,10 @@ module Grape
114
118
  # Update our settings from a given set of stackable parameters. Used when
115
119
  # the endpoint's API is mounted under another one.
116
120
  def inherit_settings(namespace_stackable)
117
- inheritable_setting.route[:saved_validations] += namespace_stackable[:validations]
121
+ parent_validations = namespace_stackable[:validations]
122
+ inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?
118
123
  parent_declared_params = namespace_stackable[:declared_params]
119
-
120
- inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params
124
+ inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?
121
125
 
122
126
  endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
123
127
  end
@@ -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 << Grape::Http::Headers::HEAD if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::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
@@ -190,23 +194,20 @@ module Grape
190
194
  end
191
195
 
192
196
  def prepare_version
193
- version = namespace_inheritable(:version) || []
194
- return if version.empty?
197
+ version = namespace_inheritable(:version)
198
+ return if version.blank?
195
199
 
196
- version.length == 1 ? version.first.to_s : version
197
- end
198
-
199
- def merge_route_options(**default)
200
- options[:route_options].clone.merge!(**default)
200
+ version.length == 1 ? version.first : version
201
201
  end
202
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)
208
- path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
209
- Path.prepare(path, namespace, path_settings)
207
+ def prepare_default_path_settings
208
+ namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
209
+ namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
210
+ namespace_stackable_hash.merge!(namespace_inheritable_hash)
210
211
  end
211
212
 
212
213
  def namespace
@@ -227,30 +228,36 @@ module Grape
227
228
  # Return the collection of endpoints within this endpoint.
228
229
  # This is the case when an Grape::API mounts another Grape::API.
229
230
  def endpoints
230
- options[:app].endpoints if options[:app].respond_to?(:endpoints)
231
+ @endpoints ||= options[:app].try(:endpoints)
232
+ end
233
+
234
+ def equals?(endpoint)
235
+ (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)
231
236
  end
232
237
 
233
- def equals?(e)
234
- (options == e.options) && (inheritable_setting.to_hash == e.inheritable_setting.to_hash)
238
+ # The purpose of this override is solely for stripping internals when an error occurs while calling
239
+ # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398
240
+ # Otherwise, it calls super.
241
+ def inspect
242
+ return super unless env
243
+
244
+ "#{self.class} in '#{route.origin}' endpoint"
235
245
  end
236
246
 
237
247
  protected
238
248
 
239
249
  def run
240
250
  ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
241
- @header = {}
242
251
  @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
243
- @params = @request.params
244
- @headers = @request.headers
245
252
  begin
246
- cookies.read(@request)
247
253
  self.class.run_before_each(self)
248
254
  run_filters befores, :before
249
255
 
250
- if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
251
- 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?
252
259
 
253
- header Grape::Http::Headers::ALLOW, allowed_methods
260
+ header 'Allow', header['Allow']
254
261
  response_object = ''
255
262
  status 204
256
263
  else
@@ -261,7 +268,7 @@ module Grape
261
268
  end
262
269
 
263
270
  run_filters afters, :after
264
- cookies.write(header)
271
+ build_response_cookies
265
272
 
266
273
  # status verifies body presence when DELETE
267
274
  @body ||= response_object
@@ -276,54 +283,6 @@ module Grape
276
283
  end
277
284
  end
278
285
 
279
- def build_stack(helpers)
280
- stack = Grape::Middleware::Stack.new
281
-
282
- stack.use Rack::Head
283
- stack.use Class.new(Grape::Middleware::Error),
284
- helpers: helpers,
285
- format: namespace_inheritable(:format),
286
- content_types: namespace_stackable_with_hash(:content_types),
287
- default_status: namespace_inheritable(:default_error_status),
288
- rescue_all: namespace_inheritable(:rescue_all),
289
- rescue_grape_exceptions: namespace_inheritable(:rescue_grape_exceptions),
290
- default_error_formatter: namespace_inheritable(:default_error_formatter),
291
- error_formatters: namespace_stackable_with_hash(:error_formatters),
292
- rescue_options: namespace_stackable_with_hash(:rescue_options) || {},
293
- rescue_handlers: namespace_reverse_stackable_with_hash(:rescue_handlers) || {},
294
- base_only_rescue_handlers: namespace_stackable_with_hash(:base_only_rescue_handlers) || {},
295
- all_rescue_handler: namespace_inheritable(:all_rescue_handler),
296
- grape_exceptions_rescue_handler: namespace_inheritable(:grape_exceptions_rescue_handler)
297
-
298
- stack.concat namespace_stackable(:middleware)
299
-
300
- if namespace_inheritable(:version)
301
- stack.use Grape::Middleware::Versioner.using(namespace_inheritable(:version_options)[:using]),
302
- versions: namespace_inheritable(:version)&.flatten,
303
- version_options: namespace_inheritable(:version_options),
304
- prefix: namespace_inheritable(:root_prefix),
305
- mount_path: namespace_stackable(:mount_path).first
306
- end
307
-
308
- stack.use Grape::Middleware::Formatter,
309
- format: namespace_inheritable(:format),
310
- default_format: namespace_inheritable(:default_format) || :txt,
311
- content_types: namespace_stackable_with_hash(:content_types),
312
- formatters: namespace_stackable_with_hash(:formatters),
313
- parsers: namespace_stackable_with_hash(:parsers)
314
-
315
- builder = stack.build
316
- builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }
317
- builder.to_app
318
- end
319
-
320
- def build_helpers
321
- helpers = namespace_stackable(:helpers)
322
- Module.new { helpers&.each { |mod_to_include| include mod_to_include } }
323
- end
324
-
325
- private :build_stack, :build_helpers
326
-
327
286
  def execute
328
287
  @block&.call(self)
329
288
  end
@@ -338,7 +297,7 @@ module Grape
338
297
  @lazy_initialize_lock.synchronize do
339
298
  return true if @lazy_initialized
340
299
 
341
- @helpers = build_helpers.tap { |mod| self.class.send(:include, mod) }
300
+ @helpers = build_helpers&.tap { |mod| self.class.include mod }
342
301
  @app = options[:app] || build_stack(@helpers)
343
302
 
344
303
  @lazy_initialized = true
@@ -371,45 +330,88 @@ module Grape
371
330
  extend post_extension if post_extension
372
331
  end
373
332
 
374
- def befores
375
- 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
376
337
  end
377
338
 
378
- def before_validations
379
- namespace_stackable(:before_validations)
380
- end
339
+ def validations
340
+ return enum_for(:validations) unless block_given?
381
341
 
382
- def after_validations
383
- namespace_stackable(:after_validations)
342
+ route_setting(:saved_validations)&.each do |saved_validation|
343
+ yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)
344
+ end
384
345
  end
385
346
 
386
- def afters
387
- namespace_stackable(:afters)
347
+ def options?
348
+ options[:options_route_enabled] &&
349
+ env[Rack::REQUEST_METHOD] == Rack::OPTIONS
388
350
  end
389
351
 
390
- def finallies
391
- namespace_stackable(:finallies)
392
- end
352
+ private
393
353
 
394
- def validations
395
- return enum_for(:validations) unless block_given?
354
+ def build_stack(helpers)
355
+ stack = Grape::Middleware::Stack.new
396
356
 
397
- route_setting(:saved_validations)&.each do |saved_validation|
398
- yield Grape::Validations::ValidatorFactory.create_validator(**saved_validation)
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
399
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
400
397
  end
401
398
 
402
- def options?
403
- options[:options_route_enabled] &&
404
- env[Grape::Http::Headers::REQUEST_METHOD] == Grape::Http::Headers::OPTIONS
399
+ def build_helpers
400
+ helpers = namespace_stackable(:helpers)
401
+ return if helpers.empty?
402
+
403
+ Module.new { helpers.each { |mod_to_include| include mod_to_include } }
405
404
  end
406
405
 
407
- def method_missing(name, *_args)
408
- raise NoMethodError.new("undefined method `#{name}' for #{self.class} in `#{route.origin}' endpoint")
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
410
+ end
409
411
  end
410
412
 
411
- def respond_to_missing?(method_name, include_private = false)
412
- super
413
+ def lint?
414
+ namespace_inheritable(:lint) || Grape.config.lint
413
415
  end
414
416
  end
415
417
  end
@@ -11,11 +11,6 @@ module Grape
11
11
  API_VENDOR = 'api.vendor'
12
12
  API_FORMAT = 'api.format'
13
13
 
14
- RACK_INPUT = 'rack.input'
15
- RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
16
- RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
17
- RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
18
-
19
14
  GRAPE_REQUEST = 'grape.request'
20
15
  GRAPE_REQUEST_HEADERS = 'grape.request.headers'
21
16
  GRAPE_REQUEST_PARAMS = 'grape.request.params'
@@ -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 SerializableHash < Json; end
6
+ end
7
+ end
@@ -2,25 +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
- rescue_options = options[:rescue_options] || {}
14
- if rescue_options[:backtrace] && backtrace && !backtrace.empty?
15
- result += "\r\n backtrace:"
16
- result += backtrace.join("\r\n ")
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])
17
12
  end
18
- if rescue_options[:original_exception] && original_exception
19
- result += "\r\n original exception:"
20
- result += "\r\n #{original_exception.inspect}"
13
+ if structured_message.key?(:original_exception)
14
+ final_message << 'original exception:'
15
+ final_message << structured_message[:original_exception]
21
16
  end
22
- result
23
- end
17
+ end.join("\r\n ")
24
18
  end
25
19
  end
26
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