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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a72952c78aab0b82bbce081bd1c2ab955ef264a9fbb5186a9ead3a429eaa504e
4
- data.tar.gz: 43cb1863bac420a7150573a0e24cc141bd2384e36cfc4440dbd12f2fce276e93
3
+ metadata.gz: 962aaba417524bf102f137c7f62dd622b440701dc106c6afa8372bcb4c2c36ec
4
+ data.tar.gz: 71430081be864aee8f985f67adc936ce46c078a8e19f9e476e4337f5d1d89ca0
5
5
  SHA512:
6
- metadata.gz: cabc217b43f33aa0771de27613eeecd83f425786e93946ed14d716523814a4af7045d464ae16841b6d3e2db20a8b0f4d780190363ad130bf934c100b962ba1f1
7
- data.tar.gz: 5ab3b2151ebb98e80b65a24717a628d7e9f60f206b25c258a05d32e3d57c85e832d99b92905f4883e2d770e9240da85e611771b2497d17f7c87687e670b82838
6
+ metadata.gz: d05157f2a3b4f92591046ef6a51b5fdd55dc9f8d7eeb3fd314529b71a6981bd71bb1d382c58bdd3c3d8bf71cf46095637682b94c35aad37dc9770fba9caef935
7
+ data.tar.gz: 52144674f23c819c5680d4ab53d54260b1919abaf2bd46ed3fe05351bf169056981c8c89ce762f1a4ad837605ba96b9ab12dc2ff9d63f51e074525c2ae728cb2
data/.rubocop.yml CHANGED
@@ -242,3 +242,6 @@ RSpec/FilePath:
242
242
 
243
243
  RSpec/SubjectStub:
244
244
  Enabled: false
245
+
246
+ RSpec/VerifiedDoubles:
247
+ Enabled: false
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] - YYYY-MM-DD
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
- Example
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
- #### Failing The Context
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
@@ -9,7 +9,7 @@ module LightServiceExt
9
9
 
10
10
  raise ContextError.new(ctx: context) if raise_error
11
11
 
12
- context[:outcome] = Outcome::COMPLETE
12
+ context.add_status(Status::COMPLETE)
13
13
  end
14
14
  end
15
15
  end
@@ -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
- allowed_overrides = overrides.slice(*OVERRIDABLE_DEFAULT_KEYS)
10
- make({ input: input.symbolize_keys }.merge(default_attrs, allowed_overrides))
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
- { errors: {}, params: {}, successful_actions: [], api_responses: [],
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 } }.freeze
26
+ internal_only: { error_info: nil }
27
+ }.freeze
19
28
  end
20
29
  end
21
30
 
22
31
  def add_params(**params)
23
- return if params.blank?
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
- return if errors.blank?
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 invoked_action
49
- self[:invoked_action]
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 validation_errors
53
- self[:errors]
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
- def self.call(context)
8
- with(ApplicationContext.make_with_defaults(context)).reduce(steps)
9
- end
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
- def self.steps
12
- raise NotImplementedError
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.transform_values(&:first))
11
+ context.add_errors!(**validator.errors.to_h)
12
12
  end
13
13
 
14
14
  class << self
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LightServiceExt
4
- module Outcome
4
+ module Status
5
5
  COMPLETE = :all_steps_complete
6
+ INCOMPLETE = :steps_incomplete
6
7
  end
7
8
  end
@@ -31,7 +31,7 @@ module LightServiceExt
31
31
 
32
32
  def errors
33
33
  model = error && (error.try(:model) || error.try(:record))
34
- return model.errors.messages.transform_values(&:first) if model.present?
34
+ return model.errors.messages if model.present?
35
35
 
36
36
  { base: message }
37
37
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LightServiceExt
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
@@ -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
@@ -23,7 +23,7 @@ module LightServiceExt
23
23
  it 'does not raise error' do
24
24
  expect { executed_ctx }.not_to raise_error
25
25
 
26
- expect(executed_ctx.keys).to include(:outcome)
26
+ expect(executed_ctx.keys).to include(:status)
27
27
  end
28
28
 
29
29
  context 'with raising of errors allowed' do
@@ -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[input
138
- errors
139
- params
140
- successful_actions
141
- api_responses
142
- allow_raise_on_failure
143
- internal_only])
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 'prevents successful_actions to change from default' do
164
- expect(ctx_with_defaults[:successful_actions]).to be_empty
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 'prevents successful_actions to change from default' do
174
- expect(ctx_with_defaults[:api_responses]).to be_empty
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 match_array(%i[
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
 
@@ -27,7 +27,7 @@ module LightServiceExt
27
27
 
28
28
  it 'returns promised params and filled in errors' do
29
29
  expect(context).to be_failure
30
- expect(context.errors).to eql(key: 'must be filled')
30
+ expect(context.errors).to eql(key: ['must be filled'])
31
31
  end
32
32
  end
33
33
  end
@@ -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.2
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-24 00:00:00.000000000 Z
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