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 +4 -4
- data/README.md +26 -0
- data/lib/jsonapi/active_record_operations_processor.rb +13 -19
- data/lib/jsonapi/acts_as_resource_controller.rb +32 -0
- data/lib/jsonapi/operations_processor.rb +23 -6
- data/lib/jsonapi/request.rb +2 -1
- data/lib/jsonapi/resources/version.rb +1 -1
- data/test/controllers/controller_test.rb +51 -0
- data/test/fixtures/active_record.rb +5 -0
- data/test/unit/operation/operations_processor_test.rb +22 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f421adb3d1c9d4ac14ec14de18bffb8e03233b8f
|
4
|
+
data.tar.gz: 738efbc31c5bd1d1d1a7dc1c3bd83c7f9163e6ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/jsonapi/request.rb
CHANGED
@@ -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
|
@@ -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.
|
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-
|
12
|
+
date: 2015-09-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|