light-service-ext 0.1.8 → 0.1.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|