light-service-ext 0.1.2 → 0.1.3
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/.rubocop.yml +3 -0
- data/CHANGELOG.md +10 -1
- data/README.md +111 -75
- data/lib/light-service-ext/all_actions_complete_action.rb +1 -1
- data/lib/light-service-ext/application_context.rb +64 -23
- data/lib/light-service-ext/application_orchestrator.rb +32 -0
- data/lib/light-service-ext/application_organizer.rb +21 -5
- data/lib/light-service-ext/application_validator_action.rb +1 -1
- data/lib/light-service-ext/constants.rb +2 -1
- data/lib/light-service-ext/error_info.rb +1 -1
- data/lib/light-service-ext/record_actions.rb +41 -0
- data/lib/light-service-ext/version.rb +1 -1
- data/lib/light-service-ext.rb +2 -0
- data/spec/light-service-ext/all_actions_complete_action_spec.rb +1 -1
- data/spec/light-service-ext/application_context_spec.rb +113 -11
- data/spec/light-service-ext/application_orchestrator_spec.rb +76 -0
- data/spec/light-service-ext/application_organizer_spec.rb +1 -9
- data/spec/light-service-ext/application_validator_action_spec.rb +1 -1
- data/spec/light-service-ext/record_actions_spec.rb +68 -0
- data/spec/light-service-ext/with_error_handler_spec.rb +17 -21
- data/spec/spec_helper.rb +13 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 962aaba417524bf102f137c7f62dd622b440701dc106c6afa8372bcb4c2c36ec
|
4
|
+
data.tar.gz: 71430081be864aee8f985f67adc936ce46c078a8e19f9e476e4337f5d1d89ca0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d05157f2a3b4f92591046ef6a51b5fdd55dc9f8d7eeb3fd314529b71a6981bd71bb1d382c58bdd3c3d8bf71cf46095637682b94c35aad37dc9770fba9caef935
|
7
|
+
data.tar.gz: 52144674f23c819c5680d4ab53d54260b1919abaf2bd46ed3fe05351bf169056981c8c89ce762f1a4ad837605ba96b9ab12dc2ff9d63f51e074525c2ae728cb2
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -8,6 +8,15 @@
|
|
8
8
|
|
9
9
|
- Fixing issue with with use of `relative_path` inside of `light-service-ext.gemspec`
|
10
10
|
|
11
|
-
## [0.1.2] -
|
11
|
+
## [0.1.2] - 2023-02-24
|
12
12
|
|
13
13
|
- Updates `README.md` with detailed information on features provided
|
14
|
+
|
15
|
+
## [0.1.3] - 2023-02-25
|
16
|
+
|
17
|
+
- Adds `Orchestration` of organizers via `ApplicationOrchestrator` functionality to support calling an organizer(s) inside an orchestrator without polluting the orchestrator context from changes to the called organizer
|
18
|
+
- Updates `README.md` with detailed information on features provided
|
19
|
+
- Records Actions
|
20
|
+
- adds better error handling
|
21
|
+
- records api_responses to organizer's context from each action filled with `current_api_response`
|
22
|
+
- fails the organizer's context if action returned `errors`
|
data/README.md
CHANGED
@@ -23,77 +23,9 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
$ gem install light-service-ext
|
25
25
|
|
26
|
-
## ApplicationContext
|
27
|
-
|
28
|
-
> Adds useful defaults to the organizer/orchestrator context
|
29
|
-
- `:input` ~> values originally provided to organizer get moved here for better isolation
|
30
|
-
- `:params`
|
31
|
-
- stores values `filtered` and `mapped` from original `input`
|
32
|
-
- outcomes/return values provided by any action that implements `LightServiceExt::ApplicationAction`
|
33
|
-
- `:errors`
|
34
|
-
- validation errors processed by `LightServiceExt::ApplicationValidatorAction` [dry-validation](https://github.com/dry-rb/dry-validation) contract
|
35
|
-
- manually added by an action e.g. `{ errors: { email: 'not found' } }`
|
36
|
-
- `:successful_actions` ~> provides a list of actions processed mostly useful for debugging purposes
|
37
|
-
- `:api_responses` ~> contains a list of external API interactions mostly for recording/debugging purposes
|
38
|
-
- `:allow_raise_on_failure` ~> determines whether or not to throw a `RaiseOnContextError` error up the stack in the case of validation errors and/or captured exceptions
|
39
|
-
- `:outcome` denotes the current status of the organizer with one of the following flags:
|
40
|
-
- `LightServiceExt::Outcome::COMPLETE`
|
41
|
-
|
42
|
-
Example
|
43
|
-
|
44
|
-
````ruby
|
45
|
-
input = { order: order }
|
46
|
-
overrides = {} # optionally override `params`, `errors` and `allow_raise_on_failure`
|
47
|
-
LightServiceExt::ApplicationContext.make_with_defaults(input, overrides)
|
48
|
-
|
49
|
-
# => { input: { order: order },
|
50
|
-
# params: {},
|
51
|
-
# errors: {},
|
52
|
-
# successful_actions: [],
|
53
|
-
# api_responses: [],
|
54
|
-
# allow_raise_on_failure: true
|
55
|
-
# }
|
56
|
-
````
|
57
|
-
|
58
|
-
#### Useful methods
|
59
|
-
|
60
|
-
- `.add_params(**params)`
|
61
|
-
- Adds given args to context's `params` field
|
62
|
-
- e.g. `add_params(user_id: 1) # => { params: { user_id: 1 } }`
|
63
|
-
- `.add_errors(**errors)`
|
64
|
-
- Adds given args to to context's `errors` field
|
65
|
-
- Fails and returns from current action/organizer's context
|
66
|
-
- e.g. `add_to_errors(email: 'not found') # => { errors: { email: 'not found' } }`
|
67
|
-
|
68
|
-
|
69
26
|
## ApplicationOrganizer
|
70
|
-
|
71
27
|
> Adds the following support
|
72
28
|
|
73
|
-
### Useful methods
|
74
|
-
|
75
|
-
- `.reduce_if_success(<list of actions>)` prevents execution of action/step in the case of context failure or `:errors` present
|
76
|
-
- `.with_context(&block)` calls given block with `:ctx` argument
|
77
|
-
- `.execute_if` ~> Useful if you want the current `Organizer` to act as a `Orchestrator` and call another organizer
|
78
|
-
- *ONLY* modifies the current organizer/orchestrator's as a result of executing `organizer_or_action_class_or_proc` if manually applied by a given `result_callback` Proc
|
79
|
-
- Executed `steps` do modify the current organizer/orchestrator's context without the need for manual intervention
|
80
|
-
- Arguments:
|
81
|
-
- `condition_block` (required) ~> given block is called with current `context` argument
|
82
|
-
- `organizer_or_action_class_or_proc` (required) ~> only executed if `condition_block` evaluates to `true`
|
83
|
-
- must be one of `ApplicationOrganizer`, `ApplicationAction`, `Proc`
|
84
|
-
- `apply_ctx_transform` (optional)
|
85
|
-
- given block is called prior to `organizer_or_action_class_or_proc` being executed
|
86
|
-
- e.g. `apply_ctx_transform: -> (context) { context[:params][:user_id] = record(context)&.id }`
|
87
|
-
- returned value gets passed to `organizer_or_action_class_or_proc` call
|
88
|
-
- `result_callback` (optional)
|
89
|
-
- given block is called after `organizer_or_action_class_or_proc` has been executed
|
90
|
-
- Useful in the case where you want to augment the current organizer's context based on the context returned from the `organizer_or_action_class_or_proc` call
|
91
|
-
- e.g. `result_callback: -> (ctx:, result:) { ctx[:params] = result[:params] }`
|
92
|
-
- `ctx:` represents the main `organizer/orchestrator's` context
|
93
|
-
- `result:` represents the context returned from the executed `organizer_or_action_class_or_proc`
|
94
|
-
- `steps` (optional) ~> calls current `organizer/orchestrator's` actions/steps and called once `organizer_or_action_class_or_proc` has been processed
|
95
|
-
- *PLEASE NOTE* called regardless of the result from the `organizer_or_action_class_or_proc` call unless you *manually* fail the current context or add `:errors`
|
96
|
-
|
97
29
|
#### Error Handling
|
98
30
|
> Provided by `.with_error_handler`
|
99
31
|
|
@@ -126,25 +58,64 @@ LightServiceExt::ApplicationContext.make_with_defaults(input, overrides)
|
|
126
58
|
#### Retrieve Record
|
127
59
|
> Allows for a block to be defined on an organizer in order to retrieve the model record
|
128
60
|
|
129
|
-
|
61
|
+
#### Failing The Context
|
62
|
+
- Prevents further action's been executed in the following scenarios:
|
63
|
+
- All actions complete determined by organizer's `:outcome` context field set to `LightServiceExt::Outcome::COMPLETE`
|
64
|
+
|
65
|
+
#### Example
|
130
66
|
|
131
67
|
```ruby
|
132
68
|
class TaxCalculator < LightServiceExt::ApplicationOrganizer
|
133
69
|
self.retrieve_record = -> (ctx:) { User.find_by(email: ctx.params[:email]) }
|
134
70
|
|
135
|
-
def self.call(input
|
71
|
+
def self.call(input)
|
72
|
+
user = record(ctx: input) # `.record` method executes proc provided to `retrieve_record`
|
73
|
+
input = { user: user }.merge(input)
|
74
|
+
|
75
|
+
super(input)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.steps
|
79
|
+
[TaxValidator, CalcuateTaxAction]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
## ApplicationOrchestrator
|
85
|
+
> Useful if you want the current `Organizer` to act as a `Orchestrator` and call another organizer
|
86
|
+
|
87
|
+
- *ONLY* modifies the orchestrator context from executing `organizer_steps` if manually applied via `each_organizer_result` Proc
|
88
|
+
|
89
|
+
### method overrides
|
90
|
+
- `organizer_steps` ~ must be a list of organizers to be called prior to orchestrator's actions
|
91
|
+
|
92
|
+
### Example
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class TaxCalculatorReport < LightServiceExt::ApplicationOrchestrator
|
96
|
+
self.retrieve_record = -> (ctx:) { User.find_by(email: ctx.params[:email]) }
|
97
|
+
|
98
|
+
def self.call(input)
|
136
99
|
user = record(ctx: input) # `.record` method executes proc provided to `retrieve_record`
|
137
100
|
input = { user: user }.merge(user: user)
|
138
101
|
reduce_with({ input: input }, steps)
|
102
|
+
|
103
|
+
super(input.merge({ user: user })) do |current_organizer_ctx, orchestrator_ctx:|
|
104
|
+
orchestrator_ctx.add_params(current_organizer_ctx.params.slice(:user_id)) # manually add params from executed organizer(s)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def organizer_steps
|
109
|
+
[TaxCalculator]
|
110
|
+
end
|
111
|
+
|
112
|
+
def steps
|
113
|
+
[TaxReportAction]
|
139
114
|
end
|
140
115
|
end
|
141
116
|
```
|
142
117
|
|
143
|
-
|
144
|
-
- Prevents further action's been executed in the following scenarios:
|
145
|
-
- All actions complete determined by organizer's `:outcome` context field set to `LightServiceExt::Outcome::COMPLETE`
|
146
|
-
|
147
|
-
### ApplicationAction
|
118
|
+
## ApplicationAction
|
148
119
|
|
149
120
|
#### Useful methods
|
150
121
|
- TODO
|
@@ -181,6 +152,71 @@ end
|
|
181
152
|
- `.contract_class` ~> sets the [dry-validation](https://github.com/dry-rb/dry-validation) contract to be applied by the current validator action
|
182
153
|
- `.params_mapper_class` ~> sets the mapper class that must implement `.map_from(context)` and return mapped `:input` values
|
183
154
|
|
155
|
+
|
156
|
+
## ApplicationContext
|
157
|
+
|
158
|
+
> Adds useful defaults to the organizer/orchestrator context
|
159
|
+
- `:input` ~> values originally provided to organizer get moved here for better isolation
|
160
|
+
- `:params`
|
161
|
+
- stores values `filtered` and `mapped` from original `input`
|
162
|
+
- outcomes/return values provided by any action that implements `LightServiceExt::ApplicationAction`
|
163
|
+
- `:errors`
|
164
|
+
- validation errors processed by `LightServiceExt::ApplicationValidatorAction` [dry-validation](https://github.com/dry-rb/dry-validation) contract
|
165
|
+
- manually added by an action e.g. `{ errors: { email: 'not found' } }`
|
166
|
+
- `:successful_actions` ~> provides a list of actions processed mostly useful for debugging purposes e.g. `['SomeActionClassName']`
|
167
|
+
- `invoked_action` ~> instance of action to being called.
|
168
|
+
- `:current_api_response` ~> action issued api response
|
169
|
+
- `:api_responses` ~> contains a list of external API interactions mostly for recording/debugging purposes (**internal only**)
|
170
|
+
- `:allow_raise_on_failure` ~> determines whether or not to throw a `RaiseOnContextError` error up the stack in the case of validation errors and/or captured exceptions
|
171
|
+
- `:status` denotes the current status of the organizer with one of the following flags:
|
172
|
+
- `LightServiceExt::Status::COMPLETE`
|
173
|
+
- `LightServiceExt::Status::INCOMPLETE`
|
174
|
+
- `:last_failed_context` ~ copy of context that failed e.g. with `errors` field present
|
175
|
+
- `internal_only` ~ includes the likes of raised error summary and should never be passed to endpoint responses
|
176
|
+
Example
|
177
|
+
|
178
|
+
````ruby
|
179
|
+
input = { order: order }
|
180
|
+
overrides = {} # optionally override `params`, `errors` and `allow_raise_on_failure`
|
181
|
+
LightServiceExt::ApplicationContext.make_with_defaults(input, overrides)
|
182
|
+
|
183
|
+
# => { input: { order: order },
|
184
|
+
# errors: { email: ['not found'] },
|
185
|
+
# params: { user_id: 1 },
|
186
|
+
# status: Status::INCOMPLETE,
|
187
|
+
# invoked_action: SomeActionInstance,
|
188
|
+
# successful_actions: ['SomeActionClassName'],
|
189
|
+
# current_api_response: { user_id: 1, status: 'ACTIVE' },
|
190
|
+
# api_responses: [ { user_id: 1, status: 'ACTIVE' } ],
|
191
|
+
# last_failed_context: {input: { order: order }, params: {}, ...},
|
192
|
+
# allow_raise_on_failure: true,
|
193
|
+
# internal_only: { error_info: ErrorInfoInstance }
|
194
|
+
# }
|
195
|
+
````
|
196
|
+
|
197
|
+
#### Useful methods
|
198
|
+
- `.add_params(**params)`
|
199
|
+
- Adds given args to context's `params` field
|
200
|
+
- e.g. `add_params(user_id: 1) # => { params: { user_id: 1 } }`
|
201
|
+
|
202
|
+
- `add_errors!`
|
203
|
+
- Adds given args to to context's `errors` field
|
204
|
+
- Fails and returns from current action/organizer's context
|
205
|
+
- e.g. `add_to_errors!(email: 'not found') # => { errors: { email: 'not found' } }`
|
206
|
+
|
207
|
+
- `.add_errors(**errors)`
|
208
|
+
- Adds given args to to context's `errors` field
|
209
|
+
- DOES NOT fails current context
|
210
|
+
- e.g. `add_to_errors(email: 'not found') # => { errors: { email: 'not found' } }`
|
211
|
+
|
212
|
+
- `.add_status(status)`
|
213
|
+
- Should be one of Statuses e.g. `Status::COMPLETE`
|
214
|
+
- e.g. `add_status(Status::COMPLETE) # => { status: Status::COMPLETE }`
|
215
|
+
|
216
|
+
- `.add_internal_only(attrs)`
|
217
|
+
- e.g. `add_internal_only(request_id: 54) # => { internal_only: { error_info: nil, request_id: 54 } }`
|
218
|
+
- `add_to_successful_actions(action_name_or_names)` ~> adds action names successfully executed
|
219
|
+
|
184
220
|
## ContextError
|
185
221
|
|
186
222
|
> Provides all the information related to an exception/validation errors captured by the current organizer
|
@@ -2,40 +2,38 @@
|
|
2
2
|
|
3
3
|
module LightServiceExt
|
4
4
|
class ApplicationContext < LightService::Context
|
5
|
-
OVERRIDABLE_DEFAULT_KEYS = %i[errors params allow_raise_on_failure].freeze
|
6
|
-
|
7
5
|
class << self
|
8
6
|
def make_with_defaults(input = {}, overrides = {})
|
9
|
-
|
10
|
-
|
7
|
+
defaults = default_attrs
|
8
|
+
allowed_override_keys = defaults.keys.excluding(:input)
|
9
|
+
allowed_overrides = overrides.slice(*allowed_override_keys)
|
10
|
+
make({ input: input.symbolize_keys }.merge(defaults, allowed_overrides))
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def default_attrs
|
16
|
-
{
|
16
|
+
{
|
17
|
+
errors: {},
|
18
|
+
params: {},
|
19
|
+
status: Status::INCOMPLETE,
|
20
|
+
invoked_action: nil,
|
21
|
+
successful_actions: [],
|
22
|
+
current_api_response: nil,
|
23
|
+
api_responses: [],
|
24
|
+
last_failed_context: nil,
|
17
25
|
allow_raise_on_failure: LightServiceExt.config.allow_raise_on_failure?,
|
18
|
-
internal_only: { error_info: nil }
|
26
|
+
internal_only: { error_info: nil }
|
27
|
+
}.freeze
|
19
28
|
end
|
20
29
|
end
|
21
30
|
|
22
31
|
def add_params(**params)
|
23
|
-
|
24
|
-
|
25
|
-
self[:params].merge!(params.dup)
|
26
|
-
end
|
27
|
-
|
28
|
-
def add_internal_only(**attrs)
|
29
|
-
return if attrs.blank?
|
30
|
-
|
31
|
-
self[:internal_only].merge!(attrs.dup)
|
32
|
+
add_attrs_to_ctx(:params, params)
|
32
33
|
end
|
33
34
|
|
34
35
|
def add_errors(**errors)
|
35
|
-
|
36
|
-
|
37
|
-
self[:errors].merge!(errors.dup)
|
38
|
-
nil
|
36
|
+
add_attrs_to_ctx(:errors, errors)
|
39
37
|
end
|
40
38
|
|
41
39
|
def add_errors!(**errors)
|
@@ -45,12 +43,34 @@ module LightServiceExt
|
|
45
43
|
fail_and_return!
|
46
44
|
end
|
47
45
|
|
48
|
-
def
|
49
|
-
|
46
|
+
def add_status(status)
|
47
|
+
add_value_to_ctx(:status, status)
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_current_api_response(api_response)
|
51
|
+
add_value_to_ctx(:current_api_response, api_response)
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_last_failed_context(failed_context)
|
55
|
+
return if failed_context.nil? || failed_context.try(:success?)
|
56
|
+
|
57
|
+
add_value_to_ctx(:last_failed_context, failed_context)
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_internal_only(**attrs)
|
61
|
+
add_attrs_to_ctx(:internal_only, **attrs)
|
50
62
|
end
|
51
63
|
|
52
|
-
def
|
53
|
-
|
64
|
+
def add_to_api_responses(*api_response)
|
65
|
+
add_collection_to_ctx(:api_responses, *api_response)
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_to_successful_actions(*action_name)
|
69
|
+
add_collection_to_ctx(:successful_actions, *action_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_invoked_action(invoked_action)
|
73
|
+
add_value_to_ctx(:invoked_action, invoked_action)
|
54
74
|
end
|
55
75
|
|
56
76
|
def allow_raise_on_failure?
|
@@ -66,5 +86,26 @@ module LightServiceExt
|
|
66
86
|
def respond_to_missing?(method_name, include_private = false)
|
67
87
|
key?(method_name) || super
|
68
88
|
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def add_value_to_ctx(key, value)
|
93
|
+
self[key] = value
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_attrs_to_ctx(key, **attrs)
|
98
|
+
return if attrs.blank?
|
99
|
+
|
100
|
+
self[key].merge!(attrs)
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_collection_to_ctx(key, *values)
|
105
|
+
return if values.empty?
|
106
|
+
|
107
|
+
self[key] = self[key].concat(values).compact
|
108
|
+
nil
|
109
|
+
end
|
69
110
|
end
|
70
111
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module LightServiceExt
|
2
|
+
class ApplicationOrchestrator < ApplicationOrganizer
|
3
|
+
class << self
|
4
|
+
def call(context, &each_organizer_result)
|
5
|
+
orchestrator_ctx = process_each_organizer(ApplicationContext.make_with_defaults(context),
|
6
|
+
&each_organizer_result)
|
7
|
+
with(orchestrator_ctx).around_each(RecordActions).reduce(all_steps)
|
8
|
+
end
|
9
|
+
|
10
|
+
def organizer_steps
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def process_each_organizer(orchestrator_ctx, &each_organizer_result)
|
15
|
+
organizer_steps.each do |step_class|
|
16
|
+
exec_method = exec_method_for(step_class)
|
17
|
+
current_organizer_ctx = step_class.send(exec_method, orchestrator_ctx.input)
|
18
|
+
yield(current_organizer_ctx, orchestrator_ctx: orchestrator_ctx) if each_organizer_result
|
19
|
+
end
|
20
|
+
orchestrator_ctx
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def exec_method_for(klass)
|
26
|
+
return :call if klass.is_a?(LightService::Organizer) || klass.is_a?(Proc)
|
27
|
+
|
28
|
+
:execute
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -4,12 +4,28 @@ module LightServiceExt
|
|
4
4
|
class ApplicationOrganizer
|
5
5
|
extend LightService::Organizer
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
class << self
|
8
|
+
def call(context)
|
9
|
+
with(ApplicationContext.make_with_defaults(context))
|
10
|
+
.around_each(RecordActions)
|
11
|
+
.reduce(all_steps)
|
12
|
+
end
|
13
|
+
|
14
|
+
def steps
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def reduce_if_success(steps)
|
19
|
+
reduce_if(->(ctx) { ctx.success? && ctx[:errors].blank? }, steps)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def all_steps
|
25
|
+
return steps.push(AllActionsCompleteAction) if steps.is_a?(Array)
|
10
26
|
|
11
|
-
|
12
|
-
|
27
|
+
[steps].push(AllActionsCompleteAction)
|
28
|
+
end
|
13
29
|
end
|
14
30
|
end
|
15
31
|
end
|
@@ -8,7 +8,7 @@ module LightServiceExt
|
|
8
8
|
executed do |context|
|
9
9
|
validator = map_and_validate_inputs(context)
|
10
10
|
context.add_params(**validator.to_h)
|
11
|
-
context.add_errors!(**validator.errors.to_h
|
11
|
+
context.add_errors!(**validator.errors.to_h)
|
12
12
|
end
|
13
13
|
|
14
14
|
class << self
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LightServiceExt
|
4
|
+
class RecordActions
|
5
|
+
extend WithErrorHandler
|
6
|
+
|
7
|
+
def self.call(context)
|
8
|
+
with_error_handler(ctx: context) do
|
9
|
+
result = yield
|
10
|
+
return context if outcomes_complete?(ctx: context, result: result)
|
11
|
+
|
12
|
+
merge_api_responses!(ctx: context, result: result)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def merge_api_responses!(ctx:, result:)
|
18
|
+
invoked_action = result.invoked_action
|
19
|
+
return if invoked_action.nil?
|
20
|
+
|
21
|
+
ctx.add_to_successful_actions(invoked_action.name)
|
22
|
+
ctx.add_to_api_responses(result.current_api_response)
|
23
|
+
ctx
|
24
|
+
end
|
25
|
+
|
26
|
+
def outcomes_complete?(ctx:, result:)
|
27
|
+
if result.status == Status::COMPLETE
|
28
|
+
ctx.add_status(result.status)
|
29
|
+
|
30
|
+
if ctx.errors.present?
|
31
|
+
ctx.add_last_failed_context(result.to_h)
|
32
|
+
ctx.fail!
|
33
|
+
end
|
34
|
+
true
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/light-service-ext.rb
CHANGED
@@ -8,6 +8,7 @@ error_info
|
|
8
8
|
context_error
|
9
9
|
configuration
|
10
10
|
with_error_handler
|
11
|
+
record_actions
|
11
12
|
application_context
|
12
13
|
application_contract
|
13
14
|
around_action_execute_extension
|
@@ -15,6 +16,7 @@ application_action
|
|
15
16
|
all_actions_complete_action
|
16
17
|
application_validator_action
|
17
18
|
application_organizer
|
19
|
+
application_orchestrator
|
18
20
|
].each do |filename|
|
19
21
|
require File.expand_path("../light-service-ext/#{filename}", Pathname.new(__FILE__).realpath)
|
20
22
|
end
|
@@ -7,6 +7,102 @@ module LightServiceExt
|
|
7
7
|
|
8
8
|
subject(:ctx) { described_class.make_with_defaults(input) }
|
9
9
|
|
10
|
+
describe '#add_to_successful_actions' do
|
11
|
+
it 'adds successful action name to context' do
|
12
|
+
ctx.add_to_successful_actions(value)
|
13
|
+
|
14
|
+
expect(ctx.successful_actions).to match_array([value])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'preserves other successful action name' do
|
18
|
+
ctx.add_to_successful_actions(value)
|
19
|
+
|
20
|
+
ctx.add_to_successful_actions('other-value')
|
21
|
+
|
22
|
+
expect(ctx.successful_actions).to match_array([value, 'other-value'])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#add_to_api_responses' do
|
27
|
+
it 'adds last api response to context' do
|
28
|
+
ctx.add_to_api_responses(value)
|
29
|
+
|
30
|
+
expect(ctx.api_responses).to match_array([value])
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'preserves other api responses' do
|
34
|
+
ctx.add_to_api_responses(value)
|
35
|
+
|
36
|
+
ctx.add_to_api_responses('other-value')
|
37
|
+
|
38
|
+
expect(ctx.api_responses).to match_array([value, 'other-value'])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#add_last_failed_context' do
|
43
|
+
it 'adds last failed context' do
|
44
|
+
ctx.add_last_failed_context(value)
|
45
|
+
|
46
|
+
expect(ctx.last_failed_context).to eql(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'updates older values' do
|
50
|
+
ctx.add_last_failed_context(value)
|
51
|
+
|
52
|
+
ctx.add_last_failed_context('other-value')
|
53
|
+
|
54
|
+
expect(ctx.last_failed_context).to eql('other-value')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#add_status' do
|
59
|
+
it 'adds current api response to context' do
|
60
|
+
ctx.add_status(value)
|
61
|
+
|
62
|
+
expect(ctx.status).to eql(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'updates older values' do
|
66
|
+
ctx.add_status(value)
|
67
|
+
|
68
|
+
ctx.add_status('other-value')
|
69
|
+
|
70
|
+
expect(ctx.status).to eql('other-value')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#add_current_api_response' do
|
75
|
+
it 'adds current api response to context' do
|
76
|
+
ctx.add_current_api_response(value)
|
77
|
+
|
78
|
+
expect(ctx.current_api_response).to eql(value)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'updates older values' do
|
82
|
+
ctx.add_current_api_response(value)
|
83
|
+
|
84
|
+
ctx.add_current_api_response('other-value')
|
85
|
+
|
86
|
+
expect(ctx.current_api_response).to eql('other-value')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#add_invoked_action' do
|
91
|
+
it 'adds last api response to context' do
|
92
|
+
ctx.add_invoked_action(value)
|
93
|
+
|
94
|
+
expect(ctx.invoked_action).to eql(value)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'updates older values' do
|
98
|
+
ctx.add_invoked_action(value)
|
99
|
+
|
100
|
+
ctx.add_invoked_action('other-value')
|
101
|
+
|
102
|
+
expect(ctx.invoked_action).to eql('other-value')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
10
106
|
describe '#add_errors!' do
|
11
107
|
before { allow(ctx).to receive(:fail_and_return!) }
|
12
108
|
|
@@ -134,13 +230,19 @@ module LightServiceExt
|
|
134
230
|
end
|
135
231
|
|
136
232
|
it 'returns context with default attrs' do
|
137
|
-
expect(ctx_with_defaults.keys).to match_array(%i[
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
233
|
+
expect(ctx_with_defaults.keys).to match_array(%i[
|
234
|
+
input
|
235
|
+
params
|
236
|
+
errors
|
237
|
+
status
|
238
|
+
successful_actions
|
239
|
+
allow_raise_on_failure
|
240
|
+
api_responses
|
241
|
+
internal_only
|
242
|
+
invoked_action
|
243
|
+
current_api_response
|
244
|
+
last_failed_context
|
245
|
+
])
|
144
246
|
|
145
247
|
expect(ctx_with_defaults[:input]).to eql(input)
|
146
248
|
end
|
@@ -160,8 +262,8 @@ module LightServiceExt
|
|
160
262
|
described_class.make_with_defaults(input, successful_actions: ['some-action-class-name'])
|
161
263
|
end
|
162
264
|
|
163
|
-
it '
|
164
|
-
expect(ctx_with_defaults[:successful_actions]).to
|
265
|
+
it 'allows for overrides' do
|
266
|
+
expect(ctx_with_defaults[:successful_actions]).to match_array(['some-action-class-name'])
|
165
267
|
end
|
166
268
|
end
|
167
269
|
|
@@ -170,8 +272,8 @@ module LightServiceExt
|
|
170
272
|
described_class.make_with_defaults(input, api_responses: ['some-api-response'])
|
171
273
|
end
|
172
274
|
|
173
|
-
it '
|
174
|
-
expect(ctx_with_defaults[:api_responses]).to
|
275
|
+
it 'allows for overrides' do
|
276
|
+
expect(ctx_with_defaults[:api_responses]).to match_array(['some-api-response'])
|
175
277
|
end
|
176
278
|
end
|
177
279
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module LightServiceExt
|
2
|
+
RSpec.describe ApplicationOrchestrator do
|
3
|
+
describe '.call' do
|
4
|
+
let(:orchestrator_class) do
|
5
|
+
FakeOrganizer = Class.new(ApplicationOrganizer) do
|
6
|
+
class << self
|
7
|
+
FakeOrganizerAction = Class.new do
|
8
|
+
extend LightService::Action
|
9
|
+
|
10
|
+
expects :input
|
11
|
+
|
12
|
+
executed do |context|
|
13
|
+
context.dig(:input, :organizer_action_proc).call(context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def steps
|
18
|
+
[FakeOrganizerAction]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Class.new(described_class) do
|
24
|
+
class << self
|
25
|
+
FakeOrchestratorAction = Class.new do
|
26
|
+
extend LightService::Action
|
27
|
+
|
28
|
+
expects :input
|
29
|
+
|
30
|
+
executed do |context|
|
31
|
+
context.dig(:input, :orchestrator_action_proc).call(context)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def organizer_steps
|
36
|
+
[FakeOrganizer]
|
37
|
+
end
|
38
|
+
|
39
|
+
def steps
|
40
|
+
[FakeOrchestratorAction]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
let(:input) do
|
47
|
+
{ organizer_action_proc: ->(context) { context.add_params(x1: 'x1') },
|
48
|
+
orchestrator_action_proc: ->(context) { context.add_params(x2: 'x2') } }
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'adds orchestrator params without organizer params' do
|
52
|
+
ctx = orchestrator_class.call(input)
|
53
|
+
|
54
|
+
expect(ctx.keys).to include(:input)
|
55
|
+
expect(ctx[:input]).to eql(input)
|
56
|
+
expect(ctx[:params]).to eql(x2: 'x2')
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with each organizer block' do
|
60
|
+
let(:organizer_block) do
|
61
|
+
lambda { |organizer_ctx, orchestrator_ctx:|
|
62
|
+
orchestrator_ctx.add_params(x3: organizer_ctx.params[:x1])
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'adds organizer param to orchestrator' do
|
67
|
+
orchestrator_ctx = orchestrator_class.call(input, &organizer_block)
|
68
|
+
|
69
|
+
expect(orchestrator_ctx.keys).to include(:input)
|
70
|
+
expect(orchestrator_ctx[:input]).to eql(input)
|
71
|
+
expect(orchestrator_ctx[:params]).to include(x2: 'x2', x3: 'x1')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -27,15 +27,7 @@ module LightServiceExt
|
|
27
27
|
it 'adds inputted data as input key value pair' do
|
28
28
|
ctx = subject_class.call(input)
|
29
29
|
|
30
|
-
expect(ctx.keys).to
|
31
|
-
api_responses
|
32
|
-
errors
|
33
|
-
input
|
34
|
-
params
|
35
|
-
allow_raise_on_failure
|
36
|
-
successful_actions
|
37
|
-
internal_only
|
38
|
-
])
|
30
|
+
expect(ctx.keys).to include(:input)
|
39
31
|
expect(ctx[:input]).to eql(input)
|
40
32
|
end
|
41
33
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LightServiceExt
|
4
|
+
RSpec.describe RecordActions do
|
5
|
+
describe '.call' do
|
6
|
+
let(:input) { {} }
|
7
|
+
let(:action_name) { 'some-action-name' }
|
8
|
+
let(:current_api_response) { nil }
|
9
|
+
let(:invoked_action) { nil }
|
10
|
+
let(:status) { nil }
|
11
|
+
let(:errors) { {} }
|
12
|
+
let(:overrides) { { errors: errors } }
|
13
|
+
let(:ctx) { ApplicationContext.make_with_defaults(input, overrides) }
|
14
|
+
let(:result_ctx_overrides) do
|
15
|
+
{ status: status, errors: errors, invoked_action: invoked_action, current_api_response: current_api_response }
|
16
|
+
end
|
17
|
+
let(:result_ctx) { ApplicationContext.make_with_defaults(input, result_ctx_overrides) }
|
18
|
+
let(:proc) { -> { result_ctx } }
|
19
|
+
|
20
|
+
subject(:called_ctx) { described_class.call(ctx, &proc) }
|
21
|
+
|
22
|
+
it 'returns expected context' do
|
23
|
+
expect(called_ctx.success?).to be_truthy
|
24
|
+
expect(called_ctx.successful_actions).to match_array([])
|
25
|
+
expect(called_ctx.api_responses).to match_array([])
|
26
|
+
expect(called_ctx.status).to eql(Status::INCOMPLETE)
|
27
|
+
expect(called_ctx[:last_failed_context]).to be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with api response attrs' do
|
31
|
+
let(:invoked_action) { class_double(ApplicationAction, name: action_name) }
|
32
|
+
let(:current_api_response) { 'some-api-response' }
|
33
|
+
|
34
|
+
it 'adds api_response as last api response' do
|
35
|
+
expect(called_ctx.success?).to be_truthy
|
36
|
+
expect(called_ctx.successful_actions).to match_array([action_name])
|
37
|
+
expect(called_ctx.api_responses).to match_array([current_api_response])
|
38
|
+
expect(called_ctx.status).to eql(Status::INCOMPLETE)
|
39
|
+
expect(called_ctx[:last_failed_context]).to be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with completed status' do
|
43
|
+
let(:status) { :all_steps_complete }
|
44
|
+
|
45
|
+
it 'adds errors and last failed context' do
|
46
|
+
expect(called_ctx.success?).to be_truthy
|
47
|
+
expect(called_ctx.successful_actions).to be_empty
|
48
|
+
expect(called_ctx.api_responses).to be_empty
|
49
|
+
expect(called_ctx.status).to eql(status)
|
50
|
+
expect(called_ctx[:last_failed_context]).to be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'with errors' do
|
54
|
+
let(:errors) { { base: 'some-message' } }
|
55
|
+
|
56
|
+
it 'adds last failed context and fails the context' do
|
57
|
+
expect(called_ctx.success?).to be_falsey
|
58
|
+
expect(called_ctx.successful_actions).to be_empty
|
59
|
+
expect(called_ctx.api_responses).to be_empty
|
60
|
+
expect(called_ctx.status).to eql(status)
|
61
|
+
expect(called_ctx[:last_failed_context]).to be_present
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,33 +1,13 @@
|
|
1
1
|
module LightServiceExt
|
2
2
|
RSpec.describe WithErrorHandler do
|
3
|
-
unless defined? Rails
|
4
|
-
module Rails
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
unless defined? Rails::ActiveRecordError
|
9
|
-
module Rails
|
10
|
-
class ActiveRecordError < StandardError
|
11
|
-
def model; end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
3
|
let(:implementor_class) do
|
17
4
|
Class.new do
|
18
5
|
extend WithErrorHandler
|
19
6
|
end
|
20
7
|
end
|
21
8
|
|
22
|
-
let(:key) { :key }
|
23
|
-
let(:key_error) { 'invalid' }
|
24
|
-
let(:messages) { { key => [key_error] } }
|
25
9
|
let(:error_message) { 'some-error' }
|
26
|
-
let(:errors) { OpenStruct.new(messages: messages) }
|
27
|
-
let(:model) { OpenStruct.new(errors: errors) }
|
28
|
-
let(:results_ctx_proc) { -> { ApplicationContext.make_with_defaults } }
|
29
10
|
let(:ctx) { ApplicationContext.make_with_defaults }
|
30
|
-
let(:error) { Rails::ActiveRecordError.new(error_message) }
|
31
11
|
|
32
12
|
before { allow(LightServiceExt.config.logger).to receive(:error) }
|
33
13
|
|
@@ -36,7 +16,23 @@ module LightServiceExt
|
|
36
16
|
implementor_class.with_error_handler(ctx: ctx, &results_ctx_proc)
|
37
17
|
end
|
38
18
|
|
19
|
+
context 'with non validation error' do
|
20
|
+
let(:error) { ArgumentError.new(error_message) }
|
21
|
+
let(:results_ctx_proc) { -> { raise error } }
|
22
|
+
|
23
|
+
it 'logs errors' do
|
24
|
+
expect { errors_handled_ctx }.to raise_error ArgumentError, error_message
|
25
|
+
expect(LightServiceExt.config.logger).to have_received(:error)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
39
29
|
context 'with active record error' do
|
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) }
|
40
36
|
let(:results_ctx_proc) { -> { raise error } }
|
41
37
|
|
42
38
|
context 'with error including model' do
|
@@ -44,7 +40,7 @@ module LightServiceExt
|
|
44
40
|
|
45
41
|
it 'adds model errors' do
|
46
42
|
expect(errors_handled_ctx.failure?).to be_truthy
|
47
|
-
expect(errors_handled_ctx.errors).to eql({ key => key_error })
|
43
|
+
expect(errors_handled_ctx.errors).to eql({ key => [key_error] })
|
48
44
|
expect(errors_handled_ctx.internal_only[:error_info]).to be_an_instance_of(ErrorInfo)
|
49
45
|
expect(LightServiceExt.config.logger).to have_received(:error)
|
50
46
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,6 +5,19 @@ require Pathname.new(__dir__).realpath.join('coverage_helper').to_s
|
|
5
5
|
|
6
6
|
require 'light-service/testing'
|
7
7
|
|
8
|
+
unless defined? Rails
|
9
|
+
module Rails
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
unless defined? Rails::ActiveRecordError
|
14
|
+
module Rails
|
15
|
+
class ActiveRecordError < StandardError
|
16
|
+
def model; end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
RSpec.configure do |config|
|
9
22
|
config.before do
|
10
23
|
# rubocop:disable Style/ClassVars
|
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.3
|
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-02-
|
11
|
+
date: 2023-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: light-service
|
@@ -175,6 +175,7 @@ files:
|
|
175
175
|
- lib/light-service-ext/application_action.rb
|
176
176
|
- lib/light-service-ext/application_context.rb
|
177
177
|
- lib/light-service-ext/application_contract.rb
|
178
|
+
- lib/light-service-ext/application_orchestrator.rb
|
178
179
|
- lib/light-service-ext/application_organizer.rb
|
179
180
|
- lib/light-service-ext/application_validator_action.rb
|
180
181
|
- lib/light-service-ext/around_action_execute_extension.rb
|
@@ -182,6 +183,7 @@ files:
|
|
182
183
|
- lib/light-service-ext/constants.rb
|
183
184
|
- lib/light-service-ext/context_error.rb
|
184
185
|
- lib/light-service-ext/error_info.rb
|
186
|
+
- lib/light-service-ext/record_actions.rb
|
185
187
|
- lib/light-service-ext/regex.rb
|
186
188
|
- lib/light-service-ext/version.rb
|
187
189
|
- lib/light-service-ext/with_error_handler.rb
|
@@ -192,12 +194,14 @@ files:
|
|
192
194
|
- spec/light-service-ext/application_action_spec.rb
|
193
195
|
- spec/light-service-ext/application_context_spec.rb
|
194
196
|
- spec/light-service-ext/application_contract_spec.rb
|
197
|
+
- spec/light-service-ext/application_orchestrator_spec.rb
|
195
198
|
- spec/light-service-ext/application_organizer_spec.rb
|
196
199
|
- spec/light-service-ext/application_validator_action_spec.rb
|
197
200
|
- spec/light-service-ext/around_action_execute_extension_spec.rb
|
198
201
|
- spec/light-service-ext/configuration_spec.rb
|
199
202
|
- spec/light-service-ext/context_error_spec.rb
|
200
203
|
- spec/light-service-ext/error_info_spec.rb
|
204
|
+
- spec/light-service-ext/record_actions_spec.rb
|
201
205
|
- spec/light-service-ext/regex_spec.rb
|
202
206
|
- spec/light-service-ext/with_error_handler_spec.rb
|
203
207
|
- spec/light_service_ext_spec.rb
|
@@ -234,12 +238,14 @@ test_files:
|
|
234
238
|
- spec/light-service-ext/application_action_spec.rb
|
235
239
|
- spec/light-service-ext/application_context_spec.rb
|
236
240
|
- spec/light-service-ext/application_contract_spec.rb
|
241
|
+
- spec/light-service-ext/application_orchestrator_spec.rb
|
237
242
|
- spec/light-service-ext/application_organizer_spec.rb
|
238
243
|
- spec/light-service-ext/application_validator_action_spec.rb
|
239
244
|
- spec/light-service-ext/around_action_execute_extension_spec.rb
|
240
245
|
- spec/light-service-ext/configuration_spec.rb
|
241
246
|
- spec/light-service-ext/context_error_spec.rb
|
242
247
|
- spec/light-service-ext/error_info_spec.rb
|
248
|
+
- spec/light-service-ext/record_actions_spec.rb
|
243
249
|
- spec/light-service-ext/regex_spec.rb
|
244
250
|
- spec/light-service-ext/with_error_handler_spec.rb
|
245
251
|
- spec/light_service_ext_spec.rb
|