light-service-ext 0.1.8 → 0.1.10
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.
- checksums.yaml +4 -4
- data/.env.example +1 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -0
- data/README.md +5 -2
- data/lib/light-service-ext/application_context.rb +37 -1
- data/lib/light-service-ext/application_organizer.rb +6 -3
- data/lib/light-service-ext/configuration.rb +1 -1
- data/lib/light-service-ext/error_info.rb +4 -4
- data/lib/light-service-ext/record_actions.rb +25 -2
- data/lib/light-service-ext/version.rb +1 -1
- data/lib/light-service-ext/with_error_handler.rb +4 -14
- data/light-service-ext.gemspec +3 -3
- data/spec/light-service-ext/application_context_spec.rb +119 -0
- data/spec/light-service-ext/application_organizer_spec.rb +8 -3
- data/spec/light-service-ext/configuration_spec.rb +27 -0
- data/spec/light-service-ext/error_info_spec.rb +55 -2
- data/spec/light-service-ext/record_actions_spec.rb +34 -0
- data/spec/light-service-ext/with_error_handler_spec.rb +14 -38
- metadata +8 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aad64b8f2360c7b9e4576bcfe6e167f10a87424392b9677ca620b973ba3dc8ef
|
4
|
+
data.tar.gz: 18a7ff6bde7fab9b58eb1e883fbb79cc346be9d98e9516135de3b5e6eabd570c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0f1d41f1530c3b8e9624f0c3ef360464b9a67d8c7e21893b4ecea3db8b09d19b967e7a0dbbc6d66cc1e90677186954c62ab1d734e026ca434b183d7ad3bbdc8
|
7
|
+
data.tar.gz: d81ae82181b451ad2330dc42ccbf33f11cab6b99a0afaa47ba77d4a8bf88bcb6903b486439be926ea408fc2dc06b8e27626843f45a69e6053c9afb4d3dd4f401
|
data/.env.example
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export RBENV_VERSION=$(head -n 1 .ruby-version)
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.2
|
data/README.md
CHANGED
@@ -173,12 +173,14 @@ end
|
|
173
173
|
- `LightServiceExt::Status::INCOMPLETE`
|
174
174
|
- `:last_failed_context` ~ copy of context that failed e.g. with `errors` field present
|
175
175
|
- `internal_only` ~ includes the likes of raised error summary and should never be passed to endpoint responses
|
176
|
+
- `meta` ~ used to store any additional information that could be helpful especially for debugging purposes.
|
176
177
|
Example
|
177
178
|
|
178
179
|
````ruby
|
179
180
|
input = { order: order }
|
180
181
|
overrides = {} # optionally override `params`, `errors` and `allow_raise_on_failure`
|
181
|
-
|
182
|
+
meta = { current_user_id: 12345, request_id: some-unique-request-id, impersonator_id: 54321 }
|
183
|
+
LightServiceExt::ApplicationContext.make_with_defaults(input, overrides, meta: meta)
|
182
184
|
|
183
185
|
# => { input: { order: order },
|
184
186
|
# errors: { email: ['not found'] },
|
@@ -190,7 +192,8 @@ LightServiceExt::ApplicationContext.make_with_defaults(input, overrides)
|
|
190
192
|
# api_responses: [ { user_id: 1, status: 'ACTIVE' } ],
|
191
193
|
# last_failed_context: {input: { order: order }, params: {}, ...},
|
192
194
|
# allow_raise_on_failure: true,
|
193
|
-
# internal_only: { error_info: ErrorInfoInstance }
|
195
|
+
# internal_only: { error_info: ErrorInfoInstance },
|
196
|
+
# meta: { current_user_id: 12345, request_id: some-unique-request-id, impersonator_id: 54321 }
|
194
197
|
# }
|
195
198
|
````
|
196
199
|
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module LightServiceExt
|
4
4
|
class ApplicationContext < LightService::Context
|
5
|
+
attr_reader :error_info
|
6
|
+
|
5
7
|
class << self
|
6
8
|
def make_with_defaults(input = {}, overrides = {})
|
7
9
|
defaults = default_attrs
|
@@ -23,7 +25,8 @@ module LightServiceExt
|
|
23
25
|
api_responses: [],
|
24
26
|
last_failed_context: nil,
|
25
27
|
allow_raise_on_failure: LightServiceExt.config.allow_raise_on_failure?,
|
26
|
-
internal_only: {
|
28
|
+
internal_only: {},
|
29
|
+
meta: {}
|
27
30
|
}.freeze
|
28
31
|
end
|
29
32
|
end
|
@@ -61,6 +64,23 @@ module LightServiceExt
|
|
61
64
|
add_attrs_to_ctx(:internal_only, **attrs)
|
62
65
|
end
|
63
66
|
|
67
|
+
def add_meta(**attrs)
|
68
|
+
add_attrs_to_ctx(:meta, **attrs)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def record_raised_error(error)
|
73
|
+
@error_info = ErrorInfo.new(error)
|
74
|
+
error_type = @error_info.type
|
75
|
+
error_message = @error_info.message
|
76
|
+
add_internal_only(error_info: { organizer: organizer_name,
|
77
|
+
action_name: action_name,
|
78
|
+
error: { type: error_type, message: error_message, backtrace: @error_info.clean_backtrace } })
|
79
|
+
add_errors(base: error_message)
|
80
|
+
|
81
|
+
LightServiceExt.config.on_raised_error.call(self, error)
|
82
|
+
end
|
83
|
+
|
64
84
|
def add_to_api_responses(*api_response)
|
65
85
|
add_collection_to_ctx(:api_responses, *api_response)
|
66
86
|
end
|
@@ -77,6 +97,22 @@ module LightServiceExt
|
|
77
97
|
!!self[:allow_raise_on_failure]
|
78
98
|
end
|
79
99
|
|
100
|
+
def organizer_name
|
101
|
+
return nil if organized_by.nil?
|
102
|
+
|
103
|
+
organized_by.name.split('::').last
|
104
|
+
end
|
105
|
+
|
106
|
+
def action_name
|
107
|
+
return nil if invoked_action.blank?
|
108
|
+
|
109
|
+
invoked_action.name.split('::').last
|
110
|
+
end
|
111
|
+
|
112
|
+
def formatted_errors
|
113
|
+
JSON.pretty_generate(errors.presence || {})
|
114
|
+
end
|
115
|
+
|
80
116
|
private
|
81
117
|
|
82
118
|
def add_value_to_ctx(key, value)
|
@@ -3,12 +3,15 @@
|
|
3
3
|
module LightServiceExt
|
4
4
|
class ApplicationOrganizer
|
5
5
|
extend LightService::Organizer
|
6
|
+
extend WithErrorHandler
|
6
7
|
|
7
8
|
class << self
|
8
9
|
def call(context)
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
ctx = ApplicationContext.make_with_defaults(context)
|
11
|
+
|
12
|
+
with_error_handler(ctx: ctx) do
|
13
|
+
with(ctx).around_each(RecordActions).reduce(all_steps)
|
14
|
+
end
|
12
15
|
end
|
13
16
|
|
14
17
|
def steps
|
@@ -7,7 +7,7 @@ module LightServiceExt
|
|
7
7
|
config_accessor(:allow_raise_on_failure) { true }
|
8
8
|
config_accessor(:non_fatal_error_classes) { [] }
|
9
9
|
config_accessor(:default_non_fatal_error_classes) { ['Rails::ActiveRecordError'.safe_constantize] }
|
10
|
-
config_accessor(:
|
10
|
+
config_accessor(:on_raised_error) { proc {|_ctx, _error|} }
|
11
11
|
|
12
12
|
def allow_raise_on_failure?
|
13
13
|
!!allow_raise_on_failure
|
@@ -8,7 +8,7 @@ module LightServiceExt
|
|
8
8
|
@fatal = fatal
|
9
9
|
@error = error
|
10
10
|
@type = error.class.name
|
11
|
-
@message = message || error
|
11
|
+
@message = message || error.try(:message)
|
12
12
|
@title = "#{error.class.name} : #{error&.message}"
|
13
13
|
end
|
14
14
|
|
@@ -30,8 +30,8 @@ module LightServiceExt
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def errors
|
33
|
-
model = error
|
34
|
-
|
33
|
+
model = error.try(:model) || error.try(:record)
|
34
|
+
message = model.present? ? model.errors.messages : error.message
|
35
35
|
|
36
36
|
{ base: message }
|
37
37
|
end
|
@@ -41,7 +41,7 @@ module LightServiceExt
|
|
41
41
|
type: type,
|
42
42
|
message: message,
|
43
43
|
exception: title,
|
44
|
-
backtrace: clean_backtrace[0,
|
44
|
+
backtrace: clean_backtrace[0, 5]&.join("\n"),
|
45
45
|
error: error,
|
46
46
|
fatal_error?: fatal_error?,
|
47
47
|
errors: errors
|
@@ -6,14 +6,37 @@ module LightServiceExt
|
|
6
6
|
|
7
7
|
def self.call(context)
|
8
8
|
with_error_handler(ctx: context) do
|
9
|
-
|
10
|
-
|
9
|
+
self.before_execute_block.call(context)
|
10
|
+
|
11
|
+
result = yield || context
|
12
|
+
|
13
|
+
self.after_execute_block.call(context)
|
14
|
+
self.after_success_block.call(context) if result.success?
|
15
|
+
self.after_failure_block.call(context) if result.failure?
|
11
16
|
|
17
|
+
return context if outcomes_complete?(ctx: context, result: result)
|
12
18
|
merge_api_responses!(ctx: context, result: result)
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
16
22
|
class << self
|
23
|
+
attr_writer :before_execute_block, :after_execute_block, :after_success_block, :after_failure_block
|
24
|
+
|
25
|
+
def before_execute_block
|
26
|
+
@before_execute_block ||= ->(_context) {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_execute_block
|
30
|
+
@after_execute_block ||= ->(_context) {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def after_success_block
|
34
|
+
@after_success_block ||= ->(_context) {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def after_failure_block
|
38
|
+
@after_failure_block ||= ->(_context) {}
|
39
|
+
end
|
17
40
|
def merge_api_responses!(ctx:, result:)
|
18
41
|
invoked_action = result.invoked_action
|
19
42
|
return if invoked_action.nil?
|
@@ -1,25 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# rubocop:disable Metrics/AbcSize
|
4
|
-
|
5
4
|
module LightServiceExt
|
6
5
|
module WithErrorHandler
|
7
6
|
def with_error_handler(ctx:)
|
8
|
-
@result = yield ||
|
9
|
-
rescue
|
10
|
-
|
11
|
-
ctx.
|
12
|
-
ctx.add_errors(**error_info.errors)
|
13
|
-
|
14
|
-
LightServiceExt.config.logger.error(error_info.error_summary)
|
15
|
-
|
7
|
+
@result = yield || ctx
|
8
|
+
rescue StandardError => e
|
9
|
+
ctx.record_raised_error(e)
|
10
|
+
ctx.add_status(Status::COMPLETE)
|
16
11
|
ctx.fail!
|
17
12
|
ctx
|
18
|
-
rescue StandardError => e
|
19
|
-
error_info = ErrorInfo.new(e, fatal: false)
|
20
|
-
LightServiceExt.config.logger.error(error_info.error_summary)
|
21
|
-
|
22
|
-
raise
|
23
13
|
end
|
24
14
|
end
|
25
15
|
end
|
data/light-service-ext.gemspec
CHANGED
@@ -16,17 +16,17 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.name = "light-service-ext"
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
gem.version = LightServiceExt::VERSION
|
19
|
-
gem.required_ruby_version = ">=
|
19
|
+
gem.required_ruby_version = ">= 2.7"
|
20
20
|
|
21
21
|
gem.metadata["homepage_uri"] = gem.homepage
|
22
22
|
gem.metadata["source_code_uri"] = gem.homepage
|
23
23
|
gem.metadata["changelog_uri"] = "#{gem.homepage}/CHANGELOG.md"
|
24
24
|
|
25
25
|
gem.add_runtime_dependency 'light-service', '~> 0.18', '>= 0.18.0'
|
26
|
-
gem.add_runtime_dependency 'dry-struct', '~> 1.6'
|
26
|
+
gem.add_runtime_dependency 'dry-struct', '~> 1.6'
|
27
27
|
gem.add_runtime_dependency 'dry-validation', '~> 1.10'
|
28
28
|
gem.add_runtime_dependency 'json', '~> 2.6', '>= 2.6.3'
|
29
|
-
gem.add_runtime_dependency 'activesupport', '~> 6', '>= 6.0.6'
|
29
|
+
gem.add_runtime_dependency 'activesupport', '~> 6.0', '>= 6.0.6'
|
30
30
|
|
31
31
|
gem.add_development_dependency("rake", "~> 13.0.6")
|
32
32
|
gem.add_development_dependency("rspec", "~> 3.12.0")
|
@@ -7,6 +7,110 @@ module LightServiceExt
|
|
7
7
|
|
8
8
|
subject(:ctx) { described_class.make_with_defaults(input) }
|
9
9
|
|
10
|
+
describe '#record_raised_error' do
|
11
|
+
let(:error_message) { 'Something went wrong' }
|
12
|
+
let(:error) { RuntimeError.new('Something went wrong') }
|
13
|
+
let(:backtrace) { ['some-backtrace-line'] }
|
14
|
+
|
15
|
+
before do
|
16
|
+
allow(subject).to receive(:organized_by) { ApplicationOrganizer }
|
17
|
+
allow(ctx).to receive(:invoked_action) { ApplicationAction }
|
18
|
+
|
19
|
+
error.set_backtrace(backtrace)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'records the error and adds it to the errors hash' do
|
23
|
+
subject.record_raised_error(error)
|
24
|
+
|
25
|
+
expect(subject.errors).to eql({ base: error_message })
|
26
|
+
expect(subject.success?).to be_truthy
|
27
|
+
|
28
|
+
internal_only = subject.internal_only
|
29
|
+
expect(internal_only.keys).to eql([:error_info])
|
30
|
+
|
31
|
+
error_info = internal_only[:error_info]
|
32
|
+
expect(error_info.keys).to eql([:organizer, :action_name, :error])
|
33
|
+
expect(error_info[:organizer]).to eql('ApplicationOrganizer')
|
34
|
+
expect(error_info[:action_name]).to eql('ApplicationAction')
|
35
|
+
|
36
|
+
raised_error_info = error_info[:error]
|
37
|
+
expect(raised_error_info.keys).to eql([:type, :message, :backtrace])
|
38
|
+
expect(raised_error_info[:type]).to eql(error.class.name)
|
39
|
+
expect(raised_error_info[:message]).to eql(error_message)
|
40
|
+
expect(raised_error_info[:backtrace]).to eql(error.backtrace)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'fails the operation and sets the error info' do
|
44
|
+
subject.record_raised_error(error)
|
45
|
+
|
46
|
+
expect(subject.success?).to be(true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#organizer_name' do
|
51
|
+
context 'with organizer' do
|
52
|
+
before { allow(subject).to receive(:organized_by) { ApplicationOrganizer } }
|
53
|
+
|
54
|
+
it 'returns the name of the organizer class' do
|
55
|
+
expect(subject.organizer_name).to eq('ApplicationOrganizer')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'without organizer' do
|
60
|
+
before { allow(subject).to receive(:organized_by) { nil } }
|
61
|
+
|
62
|
+
it 'returns nil' do
|
63
|
+
expect(subject.organizer_name).to be(nil)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#action_name' do
|
69
|
+
context 'with invoked action' do
|
70
|
+
before { allow(subject).to receive(:invoked_action) { ApplicationAction } }
|
71
|
+
|
72
|
+
it 'returns the name of the organizer class' do
|
73
|
+
expect(subject.action_name).to eq('ApplicationAction')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'without invoked action' do
|
78
|
+
before { allow(subject).to receive(:invoked_action) { nil } }
|
79
|
+
|
80
|
+
it 'returns nil' do
|
81
|
+
expect(subject.action_name).to be(nil)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#formatted_errors' do
|
87
|
+
before { allow(subject).to receive(:errors) { errors } }
|
88
|
+
|
89
|
+
context 'with errors' do
|
90
|
+
let(:errors) { { name: ['is required'], email: ['is invalid'] } }
|
91
|
+
|
92
|
+
it 'returns a JSON string of the errors' do
|
93
|
+
expect(subject.formatted_errors).to eq(JSON.pretty_generate(errors))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'without errors' do
|
98
|
+
let(:errors) { {} }
|
99
|
+
|
100
|
+
it 'returns an empty JSON string if there are no errors' do
|
101
|
+
expect(subject.formatted_errors).to eq(JSON.pretty_generate({}))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'without errors' do
|
106
|
+
let(:errors) { nil }
|
107
|
+
|
108
|
+
it 'returns an empty JSON string if there are no errors' do
|
109
|
+
expect(subject.formatted_errors).to eq(JSON.pretty_generate({}))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
10
114
|
describe '#add_to_successful_actions' do
|
11
115
|
it 'adds successful action name to context' do
|
12
116
|
ctx.add_to_successful_actions(value)
|
@@ -221,6 +325,21 @@ module LightServiceExt
|
|
221
325
|
|
222
326
|
subject(:ctx_with_defaults) { described_class.make_with_defaults(input, overrides) }
|
223
327
|
|
328
|
+
describe '#add_meta' do
|
329
|
+
it 'adds meta to context' do
|
330
|
+
ctx_with_defaults.add_meta(key => value)
|
331
|
+
expect(ctx_with_defaults.meta).to eql(key => value)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context 'with meta as an override' do
|
336
|
+
let(:overrides) { { meta: { key: 'some-value' } } }
|
337
|
+
|
338
|
+
it 'adds meta to context' do
|
339
|
+
expect(ctx_with_defaults.meta).to eql(key => value)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
224
343
|
context 'with non symbolized input keys' do
|
225
344
|
let(:input) { { "key" => 'some-value' } }
|
226
345
|
|
@@ -24,6 +24,9 @@ module LightServiceExt
|
|
24
24
|
|
25
25
|
let(:input) { { some_proc: proc {} } }
|
26
26
|
|
27
|
+
before { allow_any_instance_of(ApplicationContext).to receive(:organized_by) { ApplicationOrganizer } }
|
28
|
+
|
29
|
+
|
27
30
|
it 'adds inputted data as input key value pair' do
|
28
31
|
ctx = subject_class.call(input)
|
29
32
|
|
@@ -32,9 +35,11 @@ module LightServiceExt
|
|
32
35
|
end
|
33
36
|
|
34
37
|
it 'calls underlying action' do
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
+
ctx = subject_class.call(some_proc: proc { raise 'error' })
|
39
|
+
|
40
|
+
expect(ctx.errors).to be_present
|
41
|
+
expect(ctx).to be_failure
|
42
|
+
expect(ctx.status).to eql(Status::COMPLETE)
|
38
43
|
end
|
39
44
|
end
|
40
45
|
end
|
@@ -60,6 +60,33 @@ module LightServiceExt
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
63
|
+
|
64
|
+
describe '#on_raised_error' do
|
65
|
+
it 'returns a proc' do
|
66
|
+
expect(config.on_raised_error).to be_a(Proc)
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with configured callback' do
|
70
|
+
let(:error) { ArgumentError.new('some-error') }
|
71
|
+
let(:callback) { proc { |_ctx, _error| } }
|
72
|
+
|
73
|
+
before do
|
74
|
+
described_class.configure { |config| config.on_raised_error = callback }
|
75
|
+
end
|
76
|
+
|
77
|
+
after(:each) do
|
78
|
+
described_class.configure { |config| config.on_raised_error = proc {|_ctx, _error|} }
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
it 'returns set proc' do
|
83
|
+
ctx = ApplicationContext.make_with_defaults
|
84
|
+
ctx.record_raised_error(error)
|
85
|
+
|
86
|
+
expect(described_class.config.on_raised_error).to eql(callback)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
63
90
|
end
|
64
91
|
end
|
65
92
|
end
|
@@ -9,8 +9,57 @@ RSpec.describe LightServiceExt::ErrorInfo do
|
|
9
9
|
let(:error) { StandardError.new(message) }
|
10
10
|
let(:error_info_class) { Class.new(described_class) }
|
11
11
|
|
12
|
-
|
13
|
-
error
|
12
|
+
describe '#errors' do
|
13
|
+
context 'when error is a StandardError with no associated model or record' do
|
14
|
+
let(:error) { StandardError.new(message) }
|
15
|
+
|
16
|
+
it 'returns a hash with a base error message' do
|
17
|
+
expect(instance.errors).to eq({ base: message })
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with stubbed error' do
|
22
|
+
before(:each) do
|
23
|
+
allow(error).to receive(:set_backtrace)
|
24
|
+
allow(error).to receive(:backtrace) { backtrace }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when error is associated with a model with validation errors' do
|
28
|
+
let(:model) { double('Model', errors: double(:messages, messages: { field1: ['error1', 'error2'], field2: ['error3'] })) }
|
29
|
+
let(:error) { double('Error', model: model, message: nil) }
|
30
|
+
|
31
|
+
it 'returns a hash with model validation errors' do
|
32
|
+
expect(instance.errors).to eq({ base: { field1: ['error1', 'error2'], field2: ['error3'] } })
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when error is associated with a model with no validation errors' do
|
37
|
+
let(:model) { double('Model', errors: double(:messages, messages: {})) }
|
38
|
+
let(:error) { double('Error', model: model, message: nil) }
|
39
|
+
|
40
|
+
it 'returns an empty hash' do
|
41
|
+
expect(instance.errors).to eq({base: {}})
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when error is associated with a record with validation errors' do
|
46
|
+
let(:record) { double('Record', errors: double(:messages, messages: { field1: ['error1', 'error2'], field2: ['error3'] })) }
|
47
|
+
let(:error) { double('Error', record: record, message: nil) }
|
48
|
+
|
49
|
+
it 'returns a hash with record validation errors' do
|
50
|
+
expect(instance.errors).to eq({ base: { field1: ['error1', 'error2'], field2: ['error3'] } })
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when error is associated with a record with no validation errors' do
|
55
|
+
let(:record) { double('Record', errors: double(:messages, messages: {})) }
|
56
|
+
let(:error) { double('Error', record: record, message: nil) }
|
57
|
+
|
58
|
+
it 'returns an empty hash' do
|
59
|
+
expect(instance.errors).to eq({base: {}})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
14
63
|
end
|
15
64
|
|
16
65
|
describe '#type' do
|
@@ -80,6 +129,8 @@ RSpec.describe LightServiceExt::ErrorInfo do
|
|
80
129
|
end
|
81
130
|
|
82
131
|
describe '#error_summary' do
|
132
|
+
before { error.set_backtrace(backtrace) }
|
133
|
+
|
83
134
|
it 'returns summary of error' do
|
84
135
|
expect(instance.error_summary).to eql(<<~TEXT
|
85
136
|
=========== SERVER ERROR FOUND: StandardError : some-error ===========
|
@@ -96,6 +147,8 @@ RSpec.describe LightServiceExt::ErrorInfo do
|
|
96
147
|
describe '#to_h' do
|
97
148
|
subject(:hash) { instance.to_h }
|
98
149
|
|
150
|
+
before { error.set_backtrace(backtrace) }
|
151
|
+
|
99
152
|
it 'returns custom key value pairs' do
|
100
153
|
expect(hash.keys).to match_array(%i[type message exception backtrace error errors fatal_error?])
|
101
154
|
|
@@ -19,6 +19,40 @@ module LightServiceExt
|
|
19
19
|
|
20
20
|
subject(:called_ctx) { described_class.call(ctx, &proc) }
|
21
21
|
|
22
|
+
describe 'lifecycle callbacks' do
|
23
|
+
before do
|
24
|
+
allow(described_class.before_execute_block).to receive(:call)
|
25
|
+
allow(described_class.after_execute_block).to receive(:call)
|
26
|
+
allow(described_class.after_success_block).to receive(:call)
|
27
|
+
allow(described_class.after_failure_block).to receive(:call)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calls appropriate lifecycle callbacks' do
|
31
|
+
called_ctx
|
32
|
+
|
33
|
+
expect(described_class.before_execute_block).to have_received(:call).with(kind_of(ApplicationContext))
|
34
|
+
expect(described_class.after_execute_block).to have_received(:call).with(kind_of(ApplicationContext))
|
35
|
+
expect(described_class.after_success_block).to have_received(:call).with(kind_of(ApplicationContext))
|
36
|
+
expect(described_class.after_failure_block).not_to have_received(:call)
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with failure' do
|
40
|
+
before do
|
41
|
+
allow_any_instance_of(ApplicationContext).to receive(:success?) { false }
|
42
|
+
allow_any_instance_of(ApplicationContext).to receive(:failure?) { true }
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'calls appropriate lifecycle callbacks' do
|
46
|
+
called_ctx
|
47
|
+
|
48
|
+
expect(described_class.before_execute_block).to have_received(:call).with(kind_of(ApplicationContext))
|
49
|
+
expect(described_class.after_execute_block).to have_received(:call).with(kind_of(ApplicationContext))
|
50
|
+
expect(described_class.after_failure_block).to have_received(:call).with(kind_of(ApplicationContext))
|
51
|
+
expect(described_class.after_success_block).not_to have_received(:call)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
22
56
|
it 'returns expected context' do
|
23
57
|
expect(called_ctx.success?).to be_truthy
|
24
58
|
expect(called_ctx.successful_actions).to be_empty
|
@@ -6,52 +6,28 @@ module LightServiceExt
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
let(:error_message) { 'some-error' }
|
10
9
|
let(:ctx) { ApplicationContext.make_with_defaults }
|
11
10
|
|
12
|
-
before
|
11
|
+
before do
|
12
|
+
allow(ctx).to receive(:organized_by) { ApplicationOrganizer }
|
13
|
+
allow(ctx).to receive(:invoked_action) { ApplicationAction }
|
14
|
+
end
|
13
15
|
|
14
16
|
describe '.with_error_handler' do
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
context 'with non validation error' do
|
20
|
-
let(:error) { ArgumentError.new(error_message) }
|
21
|
-
let(:results_ctx_proc) { -> { raise error } }
|
17
|
+
let(:error_message) { 'some-error' }
|
18
|
+
let(:error) { RuntimeError.new(error_message) }
|
19
|
+
let(:results_ctx_proc) { -> { raise error } }
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
expect(LightServiceExt.config.logger).to have_received(:error)
|
26
|
-
end
|
21
|
+
subject(:raised_error_handled_ctx) do
|
22
|
+
implementor_class.with_error_handler(ctx: ctx, &results_ctx_proc)
|
27
23
|
end
|
28
24
|
|
29
|
-
|
30
|
-
let(:key) { :key }
|
31
|
-
let(:key_error) { 'invalid' }
|
32
|
-
let(:messages) { { key => [key_error] } }
|
33
|
-
let(:error) { Rails::ActiveRecordError.new(error_message) }
|
34
|
-
let(:errors) { double(:errors, messages: messages) }
|
35
|
-
let(:model) { double(:model, errors: errors) }
|
36
|
-
let(:results_ctx_proc) { -> { raise error } }
|
37
|
-
|
38
|
-
context 'with error including model' do
|
39
|
-
before { allow(error).to receive(:model) { model } }
|
40
|
-
|
41
|
-
it 'adds model errors' do
|
42
|
-
expect(errors_handled_ctx.failure?).to be_truthy
|
43
|
-
expect(errors_handled_ctx.errors).to eql({ key => [key_error] })
|
44
|
-
expect(errors_handled_ctx.internal_only[:error_info]).to be_an_instance_of(ErrorInfo)
|
45
|
-
expect(LightServiceExt.config.logger).to have_received(:error)
|
46
|
-
end
|
47
|
-
end
|
25
|
+
before { allow(ctx).to receive(:record_raised_error) }
|
48
26
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
expect(LightServiceExt.config.logger).to have_received(:error)
|
54
|
-
end
|
27
|
+
it 'fails context and records error' do
|
28
|
+
expect(raised_error_handled_ctx.failure?).to be_truthy
|
29
|
+
expect(raised_error_handled_ctx).to have_received(:record_raised_error).with(error)
|
30
|
+
expect(raised_error_handled_ctx.status).to eql(Status::COMPLETE)
|
55
31
|
end
|
56
32
|
end
|
57
33
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: light-service-ext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Desmond O'Leary
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: light-service
|
@@ -37,9 +37,6 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '1.6'
|
40
|
-
- - "<"
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: '2'
|
43
40
|
type: :runtime
|
44
41
|
prerelease: false
|
45
42
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -47,9 +44,6 @@ dependencies:
|
|
47
44
|
- - "~>"
|
48
45
|
- !ruby/object:Gem::Version
|
49
46
|
version: '1.6'
|
50
|
-
- - "<"
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: '2'
|
53
47
|
- !ruby/object:Gem::Dependency
|
54
48
|
name: dry-validation
|
55
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,7 +84,7 @@ dependencies:
|
|
90
84
|
requirements:
|
91
85
|
- - "~>"
|
92
86
|
- !ruby/object:Gem::Version
|
93
|
-
version: '6'
|
87
|
+
version: '6.0'
|
94
88
|
- - ">="
|
95
89
|
- !ruby/object:Gem::Version
|
96
90
|
version: 6.0.6
|
@@ -100,7 +94,7 @@ dependencies:
|
|
100
94
|
requirements:
|
101
95
|
- - "~>"
|
102
96
|
- !ruby/object:Gem::Version
|
103
|
-
version: '6'
|
97
|
+
version: '6.0'
|
104
98
|
- - ">="
|
105
99
|
- !ruby/object:Gem::Version
|
106
100
|
version: 6.0.6
|
@@ -167,10 +161,12 @@ executables: []
|
|
167
161
|
extensions: []
|
168
162
|
extra_rdoc_files: []
|
169
163
|
files:
|
164
|
+
- ".env.example"
|
170
165
|
- ".github/workflows/main.yml"
|
171
166
|
- ".gitignore"
|
172
167
|
- ".rspec"
|
173
168
|
- ".rubocop.yml"
|
169
|
+
- ".ruby-version"
|
174
170
|
- CHANGELOG.md
|
175
171
|
- CODE_OF_CONDUCT.md
|
176
172
|
- Gemfile
|
@@ -233,14 +229,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
233
229
|
requirements:
|
234
230
|
- - ">="
|
235
231
|
- !ruby/object:Gem::Version
|
236
|
-
version: '
|
232
|
+
version: '2.7'
|
237
233
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
238
234
|
requirements:
|
239
235
|
- - ">="
|
240
236
|
- !ruby/object:Gem::Version
|
241
237
|
version: '0'
|
242
238
|
requirements: []
|
243
|
-
rubygems_version: 3.
|
239
|
+
rubygems_version: 3.4.10
|
244
240
|
signing_key:
|
245
241
|
specification_version: 4
|
246
242
|
summary: Extends light-service with opinionated functionality
|