jsonapi-resources 0.5.8 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 61e3e8cacf54569eb7d57771d08cdd4861a84a22
4
- data.tar.gz: 25ccc4dc6996b991af26ebc7598129615ee14b4c
3
+ metadata.gz: f421adb3d1c9d4ac14ec14de18bffb8e03233b8f
4
+ data.tar.gz: 738efbc31c5bd1d1d1a7dc1c3bd83c7f9163e6ae
5
5
  SHA512:
6
- metadata.gz: 20e94f28d19e31142b2f80c562356a87d17f6d49df68918b4ff9be66a4528b1f734b86a0f98b25fafa566d4c2e3565d08f9cca6ec34878c32b8e40d3e580f029
7
- data.tar.gz: 52bcc55a0d1200315129584fb978c75cbcd67bdcebb546cb8a62d137cc4d21f95cf9b1b938303a2dbbe603bb785f265d4f3d2b0b7d6c25746a717de40d5629fe
6
+ metadata.gz: 16c6f58ddc71ba0f97489b7ff725ff41905f43b1329a23b60004f250567180ff385d87d822dd4c4197ec13b9f7bfad6ee20062335dd558328ce6e78e5bb182c0
7
+ data.tar.gz: f1263437a5904619e8f11ff34f6bf7f701ad9f48bb1b272a6790ec45fcffb361121cfe5f0d66f8332f8283aa16b2b7e68e003895919f4f5e7e1998315e1512b0
data/README.md CHANGED
@@ -28,6 +28,7 @@ backed by ActiveRecord models or by custom objects.
28
28
  * [Controllers] (#controllers)
29
29
  * [Namespaces] (#namespaces)
30
30
  * [Error Codes] (#error-codes)
31
+ * [Handling Exceptions] (#handling-exceptions)
31
32
  * [Serializer] (#serializer)
32
33
  * [Configuration] (#configuration)
33
34
  * [Contributing] (#contributing)
@@ -988,6 +989,31 @@ JSONAPI.configure do |config|
988
989
  end
989
990
  ```
990
991
 
992
+
993
+ #### Handling Exceptions
994
+
995
+ By default, all exceptions raised downstream from a resource controller will be caught, logged, and a ```500 Internal Server Error``` will be rendered. Exceptions can be whitelisted in the config to pass through the handler and be caught manually, or you can pass a callback from a resource controller to insert logic into the rescue block without interrupting the control flow. This can be particularly useful for additional logging or monitoring without the added work of rendering responses.
996
+
997
+ Pass a block, refer to controller class methods, or both. Note that methods must be defined as class methods on a controller and accept one parameter, which is passed the exception object that was rescued.
998
+
999
+ ```ruby
1000
+ class ApplicationController < JSONAPI::ResourceController
1001
+
1002
+ on_server_error :first_callback
1003
+
1004
+ #or
1005
+
1006
+ # on_server_error do |error|
1007
+ #do things
1008
+ #end
1009
+
1010
+ def self.first_callback(error)
1011
+ #env["airbrake.error_id"] = notify_airbrake(error)
1012
+ end
1013
+ end
1014
+
1015
+ ```
1016
+
991
1017
  ### Serializer
992
1018
 
993
1019
  The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` must be
@@ -15,26 +15,20 @@ class ActiveRecordOperationsProcessor < JSONAPI::OperationsProcessor
15
15
  fail ActiveRecord::Rollback if @transactional
16
16
  end
17
17
 
18
+ # Catch errors that should be handled before JSONAPI::Exceptions::Error
19
+ # and other unprocessed exceptions
18
20
  def process_operation(operation)
19
- operation.apply
20
- rescue ActiveRecord::DeleteRestrictionError => e
21
- record_locked_error = JSONAPI::Exceptions::RecordLocked.new(e.message)
22
- return JSONAPI::ErrorsOperationResult.new(record_locked_error.errors[0].code, record_locked_error.errors)
21
+ with_default_handling do
22
+ begin
23
+ operation.apply
24
+ rescue ActiveRecord::DeleteRestrictionError => e
25
+ record_locked_error = JSONAPI::Exceptions::RecordLocked.new(e.message)
26
+ return JSONAPI::ErrorsOperationResult.new(record_locked_error.errors[0].code, record_locked_error.errors)
23
27
 
24
- rescue ActiveRecord::RecordNotFound
25
- record_not_found = JSONAPI::Exceptions::RecordNotFound.new(operation.associated_key)
26
- return JSONAPI::ErrorsOperationResult.new(record_not_found.errors[0].code, record_not_found.errors)
27
-
28
- rescue JSONAPI::Exceptions::Error => e
29
- raise e
30
-
31
- rescue => e
32
- if JSONAPI.configuration.exception_class_whitelist.any? { |k| e.class.ancestors.include?(k) }
33
- raise e
34
- else
35
- internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
36
- Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
37
- return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
38
- end
28
+ rescue ActiveRecord::RecordNotFound
29
+ record_not_found = JSONAPI::Exceptions::RecordNotFound.new(operation.associated_key)
30
+ return JSONAPI::ErrorsOperationResult.new(record_not_found.errors[0].code, record_not_found.errors)
31
+ end
32
+ end
39
33
  end
40
34
  end
@@ -182,5 +182,37 @@ module JSONAPI
182
182
  # :nocov:
183
183
  end
184
184
  end
185
+
186
+ def add_error_callbacks(callbacks)
187
+ @request.server_error_callbacks = callbacks || []
188
+ end
189
+
190
+ # Pass in a methods or a block to be run when an exception is
191
+ # caught that is not a JSONAPI::Exceptions::Error
192
+ # Useful for additional logging or notification configuration that
193
+ # would normally depend on rails catching and rendering an exception.
194
+ # Ignores whitelist exceptions from config
195
+
196
+ class_methods do
197
+ def on_server_error(*args, &callback_block)
198
+ callbacks = []
199
+
200
+ if callback_block
201
+ callbacks << callback_block
202
+ end
203
+
204
+ method_callbacks = args.map do |method|
205
+ ->(error) do
206
+ if self.respond_to? method
207
+ send(method, error)
208
+ else
209
+ Rails.logger.warn("#{method} not defined on #{self}, skipping error callback")
210
+ end
211
+ end
212
+ end.compact
213
+ callbacks += method_callbacks
214
+ append_before_filter { add_error_callbacks(callbacks) }
215
+ end
216
+ end
185
217
  end
186
218
  end
@@ -77,25 +77,42 @@ module JSONAPI
77
77
  def rollback
78
78
  end
79
79
 
80
+ # If overriding in child operation processors, call operation.apply and
81
+ # catch errors that should be handled before JSONAPI::Exceptions::Error
82
+ # and other unprocessed exceptions
80
83
  def process_operation(operation)
81
- operation.apply
84
+ with_default_handling do
85
+ operation.apply
86
+ end
87
+ end
82
88
 
89
+ def with_default_handling(&block)
90
+ yield
83
91
  rescue JSONAPI::Exceptions::Error => e
84
- # :nocov:
85
92
  raise e
86
- # :nocov:
87
93
 
88
94
  rescue => e
89
- # :nocov:
90
- if JSONAPI.configuration.exception_class_whitelist.include?(e.class)
95
+ if JSONAPI.configuration.exception_class_whitelist.any? { |k| e.class.ancestors.include?(k) }
91
96
  raise e
92
97
  else
98
+ @request.server_error_callbacks.each { |callback| safe_run_callback(callback, e) }
99
+
93
100
  internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
94
101
  Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
95
102
  return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
96
103
  end
97
- # :nocov:
98
104
  end
105
+
106
+ def safe_run_callback(callback, error)
107
+ begin
108
+ callback.call(error)
109
+ rescue => e
110
+ Rails.logger.error { "Error in error handling callback: #{e.message} #{e.backtrace.join("\n")}" }
111
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
112
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
113
+ end
114
+ end
115
+
99
116
  end
100
117
  end
101
118
 
@@ -5,7 +5,7 @@ module JSONAPI
5
5
  class Request
6
6
  attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
7
7
  :resource_klass, :context, :paginator, :source_klass, :source_id,
8
- :include_directives, :params, :warnings
8
+ :include_directives, :params, :warnings, :server_error_callbacks
9
9
 
10
10
  def initialize(params = nil, options = {})
11
11
  @params = params
@@ -22,6 +22,7 @@ module JSONAPI
22
22
  @include_directives = nil
23
23
  @paginator = nil
24
24
  @id = nil
25
+ @server_error_callbacks = []
25
26
 
26
27
  setup_action(@params)
27
28
  end
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = '0.5.8'
3
+ VERSION = '0.5.9'
4
4
  end
5
5
  end
@@ -31,6 +31,57 @@ class PostsControllerTest < ActionController::TestCase
31
31
  JSONAPI.configuration = original_config
32
32
  end
33
33
 
34
+ def test_on_server_error_block_callback_with_exception
35
+ original_config = JSONAPI.configuration.dup
36
+ JSONAPI.configuration.operations_processor = :error_raising
37
+ JSONAPI.configuration.exception_class_whitelist = []
38
+
39
+ @controller.class.instance_variable_set(:@callback_message, "none")
40
+ @controller.class.on_server_error do
41
+ @controller.class.instance_variable_set(:@callback_message, "Sent from block")
42
+ end
43
+
44
+ get :index
45
+ assert_equal @controller.class.instance_variable_get(:@callback_message), "Sent from block"
46
+
47
+ # test that it renders the default server error response
48
+ assert_equal "Internal Server Error", json_response['errors'][0]['title']
49
+ assert_equal "Internal Server Error", json_response['errors'][0]['detail']
50
+ ensure
51
+ JSONAPI.configuration = original_config
52
+ end
53
+
54
+ def test_on_server_error_method_callback_with_exception
55
+ original_config = JSONAPI.configuration.dup
56
+ JSONAPI.configuration.operations_processor = :error_raising
57
+ JSONAPI.configuration.exception_class_whitelist = []
58
+
59
+ #ignores methods that don't exist
60
+ @controller.class.on_server_error :set_callback_message, :a_bogus_method
61
+ @controller.class.instance_variable_set(:@callback_message, "none")
62
+
63
+ get :index
64
+ assert_equal @controller.class.instance_variable_get(:@callback_message), "Sent from method"
65
+
66
+ # test that it renders the default server error response
67
+ assert_equal "Internal Server Error", json_response['errors'][0]['title']
68
+ ensure
69
+ JSONAPI.configuration = original_config
70
+ end
71
+
72
+ def test_on_server_error_callback_without_exception
73
+
74
+ callback = Proc.new { @controller.class.instance_variable_set(:@callback_message, "Sent from block") }
75
+ @controller.class.on_server_error callback
76
+ @controller.class.instance_variable_set(:@callback_message, "none")
77
+
78
+ get :index
79
+ assert_equal @controller.class.instance_variable_get(:@callback_message), "none"
80
+
81
+ # test that it does not render error
82
+ assert json_response.key?('data')
83
+ end
84
+
34
85
  def test_index_filter_with_empty_result
35
86
  get :index, {filter: {title: 'post that does not exist'}}
36
87
  assert_response :success
@@ -478,6 +478,11 @@ class PostsController < ActionController::Base
478
478
  rescue_from PostsController::SpecialError do
479
479
  head :forbidden
480
480
  end
481
+
482
+ #called by test_on_server_error
483
+ def self.set_callback_message(error)
484
+ @callback_message = "Sent from method"
485
+ end
481
486
  end
482
487
 
483
488
  class CommentsController < JSONAPI::ResourceController
@@ -503,4 +503,26 @@ class OperationsProcessorTest < Minitest::Test
503
503
  assert_equal(operation_results.results.size, 1)
504
504
  assert operation_results.has_errors?
505
505
  end
506
+
507
+ def test_safe_run_callback_pass
508
+ op = JSONAPI::OperationsProcessor.new
509
+ error = StandardError.new
510
+
511
+ check = false
512
+ callback = ->(error) { check = true}
513
+
514
+ op.send(:safe_run_callback, callback, error)
515
+ assert check
516
+ end
517
+
518
+ def test_safe_run_callback_catch_fail
519
+ op = JSONAPI::OperationsProcessor.new
520
+ error = StandardError.new
521
+
522
+ callback = ->(error) { nil.explosions}
523
+ result = op.send(:safe_run_callback, callback, error)
524
+
525
+ assert_instance_of(JSONAPI::ErrorsOperationResult, result)
526
+ assert_equal(result.code, 500)
527
+ end
506
528
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-resources
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
4
+ version: 0.5.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Gebhardt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-09-02 00:00:00.000000000 Z
12
+ date: 2015-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler