workflow_template 0.0.4
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 +7 -0
- data/lib/workflow_template/action/abstract.rb +40 -0
- data/lib/workflow_template/action/description/validated.rb +58 -0
- data/lib/workflow_template/action/description/wrapper.rb +28 -0
- data/lib/workflow_template/action/nested.rb +59 -0
- data/lib/workflow_template/action/simple.rb +28 -0
- data/lib/workflow_template/action/validated.rb +104 -0
- data/lib/workflow_template/action/wrapper.rb +32 -0
- data/lib/workflow_template/action.rb +20 -0
- data/lib/workflow_template/adapters/state/default.rb +72 -0
- data/lib/workflow_template/adapters/state/dry_monads.rb +70 -0
- data/lib/workflow_template/adapters/validation/active_model/builder.rb +18 -0
- data/lib/workflow_template/adapters/validation/active_model/failure.rb +17 -0
- data/lib/workflow_template/adapters/validation/active_model/null_model.rb +24 -0
- data/lib/workflow_template/adapters/validation/active_model/validator.rb +35 -0
- data/lib/workflow_template/adapters/validation/active_model.rb +24 -0
- data/lib/workflow_template/adapters/validation/dry_validation/builder.rb +18 -0
- data/lib/workflow_template/adapters/validation/dry_validation/failure.rb +19 -0
- data/lib/workflow_template/adapters/validation/dry_validation/validator.rb +46 -0
- data/lib/workflow_template/adapters/validation/dry_validation.rb +24 -0
- data/lib/workflow_template/adapters/validation/generic/block.rb +24 -0
- data/lib/workflow_template/adapters/validation/generic/builder.rb +28 -0
- data/lib/workflow_template/adapters/validation/generic/object.rb +30 -0
- data/lib/workflow_template/adapters/validation/generic/validator.rb +25 -0
- data/lib/workflow_template/adapters/validation/generic.rb +24 -0
- data/lib/workflow_template/definer/has_actions/actions.rb +90 -0
- data/lib/workflow_template/definer/has_actions/description.rb +48 -0
- data/lib/workflow_template/definer/has_actions/dsl.rb +64 -0
- data/lib/workflow_template/definer/has_actions/insert.rb +28 -0
- data/lib/workflow_template/definer/has_actions/inside.rb +27 -0
- data/lib/workflow_template/definer/has_actions/nested.rb +65 -0
- data/lib/workflow_template/definer/has_actions/position.rb +83 -0
- data/lib/workflow_template/definer/has_actions/redefine.rb +28 -0
- data/lib/workflow_template/definer/has_actions/root.rb +22 -0
- data/lib/workflow_template/definer/has_actions.rb +45 -0
- data/lib/workflow_template/definer/has_default_state_transition_strategy.rb +34 -0
- data/lib/workflow_template/definer/has_output_normalizer.rb +35 -0
- data/lib/workflow_template/definer/has_state_adapter.rb +30 -0
- data/lib/workflow_template/definer/has_validations/action.rb +20 -0
- data/lib/workflow_template/definer/has_validations/description.rb +38 -0
- data/lib/workflow_template/definer/has_validations/validations.rb +162 -0
- data/lib/workflow_template/definer/has_validations.rb +70 -0
- data/lib/workflow_template/error.rb +99 -0
- data/lib/workflow_template/outcome/block.rb +58 -0
- data/lib/workflow_template/outcome/handler.rb +106 -0
- data/lib/workflow_template/outcome/status.rb +33 -0
- data/lib/workflow_template/outcome/statuses.rb +60 -0
- data/lib/workflow_template/outcome.rb +53 -0
- data/lib/workflow_template/performer.rb +46 -0
- data/lib/workflow_template/state/abstract.rb +33 -0
- data/lib/workflow_template/state/adapter.rb +67 -0
- data/lib/workflow_template/state/class_methods.rb +43 -0
- data/lib/workflow_template/state/final.rb +74 -0
- data/lib/workflow_template/state/intermediate.rb +102 -0
- data/lib/workflow_template/state/meta.rb +35 -0
- data/lib/workflow_template/state/normalizer.rb +34 -0
- data/lib/workflow_template/state/validation.rb +32 -0
- data/lib/workflow_template/state.rb +41 -0
- data/lib/workflow_template/validation/adapter/abstract/builder.rb +28 -0
- data/lib/workflow_template/validation/adapter/abstract/result/failure.rb +25 -0
- data/lib/workflow_template/validation/adapter/abstract/validator.rb +23 -0
- data/lib/workflow_template/validation/adapter.rb +35 -0
- data/lib/workflow_template/validation/builder/error.rb +11 -0
- data/lib/workflow_template/validation/builder/proxy.rb +129 -0
- data/lib/workflow_template/validation/error.rb +16 -0
- data/lib/workflow_template/validation/evaluation/abstract.rb +57 -0
- data/lib/workflow_template/validation/evaluation/boolean.rb +28 -0
- data/lib/workflow_template/validation/evaluation/canonical.rb +21 -0
- data/lib/workflow_template/validation/evaluation.rb +4 -0
- data/lib/workflow_template/validation/result/failure/abstract.rb +15 -0
- data/lib/workflow_template/validation/result/failure.rb +52 -0
- data/lib/workflow_template/validation/result/success.rb +19 -0
- data/lib/workflow_template/validation/validator/result/failure.rb +36 -0
- data/lib/workflow_template/validation/validator/result/success.rb +21 -0
- data/lib/workflow_template/workflow/description.rb +56 -0
- data/lib/workflow_template/workflow.rb +69 -0
- data/lib/workflow_template.rb +11 -0
- metadata +126 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WorkflowTemplate
|
|
4
|
+
class Outcome
|
|
5
|
+
class Status
|
|
6
|
+
def initialize(handled: false, default: false)
|
|
7
|
+
@handled = handled
|
|
8
|
+
@default = default
|
|
9
|
+
freeze
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def handled?
|
|
13
|
+
@handled
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def handled!
|
|
17
|
+
self.class.new(handled: true, default: default?)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def default?
|
|
21
|
+
@default
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def default!
|
|
25
|
+
self.class.new(handled: handled?, default: true)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def inspect
|
|
29
|
+
"handled=#{handled?} default=#{default?}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'status'
|
|
4
|
+
require_relative '../error'
|
|
5
|
+
|
|
6
|
+
module WorkflowTemplate
|
|
7
|
+
class Outcome
|
|
8
|
+
class Statuses
|
|
9
|
+
def initialize(ok: Status.new, error: Status.new)
|
|
10
|
+
@statuses = { ok: ok, error: error }.freeze
|
|
11
|
+
freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def status(status)
|
|
15
|
+
status = status.to_sym
|
|
16
|
+
raise Error, "Unsupported status: '#{status}'" unless @statuses.key? status
|
|
17
|
+
|
|
18
|
+
@statuses[status]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def unhandled?(status)
|
|
22
|
+
!status(status).handled?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def default_missing?
|
|
26
|
+
@statuses.none? { |_, status| status.default? }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def default_missing
|
|
30
|
+
@statuses.reject { |_, status| status.default? }.keys
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias [] status
|
|
34
|
+
|
|
35
|
+
def handled!(status)
|
|
36
|
+
updated = status(status).handled!
|
|
37
|
+
self.class.new(**@statuses, status => updated)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_default_detected(status, detected)
|
|
41
|
+
return self unless detected
|
|
42
|
+
|
|
43
|
+
default!(status)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def default!(status)
|
|
47
|
+
updated = status(status).default!
|
|
48
|
+
self.class.new(**@statuses, status => updated)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def all_defaults!
|
|
52
|
+
self.class.new(**@statuses.transform_values(&:default!))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def inspect
|
|
56
|
+
"#<#{self.class.name} #{@statuses.map { |name, status| "#{name}: #{status.inspect}" }}>"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require_relative 'outcome/handler'
|
|
5
|
+
require_relative 'outcome/block'
|
|
6
|
+
|
|
7
|
+
module WorkflowTemplate
|
|
8
|
+
class Outcome
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
def initialize(state)
|
|
12
|
+
@handler = Handler.new(state)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def_delegator :handler, :retval
|
|
16
|
+
def_delegator :handler, :final_state
|
|
17
|
+
|
|
18
|
+
def_delegator :final_state, :meta
|
|
19
|
+
def_delegator :final_state, :status
|
|
20
|
+
def_delegator :final_state, :unwrap
|
|
21
|
+
def_delegator :final_state, :fetch
|
|
22
|
+
def_delegator :final_state, :slice
|
|
23
|
+
def_delegator :final_state, :to_result
|
|
24
|
+
|
|
25
|
+
def handle(&block)
|
|
26
|
+
binding = block.binding unless block.source_location.nil?
|
|
27
|
+
handler_block = Outcome::Block.new(handler, binding)
|
|
28
|
+
|
|
29
|
+
handler_block.instance_eval(&block)
|
|
30
|
+
|
|
31
|
+
self.handler = handler_block.handler
|
|
32
|
+
ensure_default_handled!
|
|
33
|
+
|
|
34
|
+
freeze
|
|
35
|
+
|
|
36
|
+
handler.retval
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def data
|
|
40
|
+
final_state.bare_state
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def ensure_default_handled!
|
|
46
|
+
return unless handler.default_missing?
|
|
47
|
+
|
|
48
|
+
raise Error, "Default handler missing for statuses: '#{handler.default_missing.join(', ')}'"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_accessor :handler
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'error'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module Performer
|
|
7
|
+
def perform(state, receiver)
|
|
8
|
+
raise WorkflowTemplate::Fatal, 'Workflow is not frozen' unless frozen?
|
|
9
|
+
|
|
10
|
+
Performer.perform_wrapper_actions(state, actions, receiver)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.perform_wrapper_actions(state, actions, receiver)
|
|
14
|
+
if actions.wrapper.empty?
|
|
15
|
+
perform_actions(state, actions, receiver)
|
|
16
|
+
else
|
|
17
|
+
action, container = actions.next_wrapper_action
|
|
18
|
+
perform_wrapper_action(action, container, state, receiver)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.perform_wrapper_action(action, container, state, receiver)
|
|
23
|
+
inner_state = state
|
|
24
|
+
result = action.perform(state, receiver) do |yielded = {}|
|
|
25
|
+
inner_state = state.process_bare_result(yielded, action, trace: true, validate: false)
|
|
26
|
+
inner_state = perform_wrapper_actions(inner_state, container, receiver)
|
|
27
|
+
inner_state.wrapped_state
|
|
28
|
+
end
|
|
29
|
+
inner_state.process_wrapped_result(result, action, trace: false, validate: true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.perform_actions(state, actions, receiver)
|
|
33
|
+
actions.simple.reduce(state) do |intermediate, action|
|
|
34
|
+
intermediate = perform_action(action, intermediate, receiver)
|
|
35
|
+
|
|
36
|
+
break intermediate unless intermediate.continue?
|
|
37
|
+
|
|
38
|
+
intermediate
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.perform_action(action, state, *args)
|
|
43
|
+
action.perform(state, *args)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'meta'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module State
|
|
7
|
+
module Abstract
|
|
8
|
+
attr_reader :wrapped_state, :meta
|
|
9
|
+
|
|
10
|
+
def initialize(wrapped_state, meta)
|
|
11
|
+
@wrapped_state = wrapped_state.freeze
|
|
12
|
+
@meta = meta.freeze
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def bare_state
|
|
17
|
+
state_class.unwrap_success(wrapped_state)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def error?
|
|
21
|
+
state_class.error?(wrapped_state)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def error
|
|
25
|
+
state_class.unwrap_error(wrapped_state)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def state_class
|
|
29
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../error'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module State
|
|
7
|
+
module Adapter
|
|
8
|
+
def self.adapters
|
|
9
|
+
@adapters ||= {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.register(name, adapter)
|
|
13
|
+
adapters[name.to_sym] = adapter
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.fetch(name)
|
|
17
|
+
name = name.to_sym
|
|
18
|
+
raise Error, "Not a registered state adapter: #{name}" unless adapters.key? name
|
|
19
|
+
|
|
20
|
+
adapters[name]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Abstract
|
|
24
|
+
module InstanceMethods
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassMethods
|
|
28
|
+
def canonical?(_wrapped_state)
|
|
29
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def error?(_wrapped_state)
|
|
33
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def wrap_success(_bare_state)
|
|
37
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unwrap_success(_wrapped_state)
|
|
41
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def wrap_error_with_bare_state(_error, _bare_state)
|
|
45
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unwrap_error(_wrapped_state)
|
|
49
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def map_unwrappable_state(_wrapped_state)
|
|
53
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def flat_map_unwrappable_state(_wrapped_state)
|
|
57
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def run_handler!(state, binding, &block)
|
|
61
|
+
raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'validation'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module State
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def initial(input, default_strategy, validations, trace: false)
|
|
9
|
+
wrapped_state = wrap_success(input)
|
|
10
|
+
wrapped_state = validate(wrapped_state, validations)
|
|
11
|
+
new wrapped_state, State.normalize_transition_strategy(default_strategy), Meta.instance(trace)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def validate(wrapped_state, validations)
|
|
15
|
+
raise Fatal::BadInput, wrapped_state unless canonical? wrapped_state
|
|
16
|
+
|
|
17
|
+
return wrapped_state if !Validation.applicable?(validations) || error?(wrapped_state)
|
|
18
|
+
|
|
19
|
+
flat_map_unwrappable_state(wrapped_state) do |bare_state|
|
|
20
|
+
result = Validation.validate(bare_state, validations)
|
|
21
|
+
result.success? ? wrapped_state : wrap_error_with_bare_state(result, bare_state)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def merge_bare_result(bare_state, bare_result, strategy, action_name)
|
|
26
|
+
ensure_canonical_bare_result!(bare_result, action_name)
|
|
27
|
+
|
|
28
|
+
if strategy == :merge
|
|
29
|
+
bare_state.merge(bare_result)
|
|
30
|
+
else
|
|
31
|
+
bare_result
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ensure_canonical_bare_result!(bare_result, action_name)
|
|
36
|
+
raise Fatal::BadActionReturn.new(action_name, bare_result) unless bare_result.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
faulty_keys = bare_result.keys.reject { _1.is_a?(Symbol) }
|
|
39
|
+
raise Fatal::BadReturnKeys.new(action_name, faulty_keys) unless faulty_keys.empty?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'abstract'
|
|
4
|
+
require_relative 'normalizer'
|
|
5
|
+
require_relative '../validation/result/failure'
|
|
6
|
+
require_relative '../validation/error'
|
|
7
|
+
require_relative '../error'
|
|
8
|
+
|
|
9
|
+
module WorkflowTemplate
|
|
10
|
+
module State
|
|
11
|
+
class Final
|
|
12
|
+
include Abstract
|
|
13
|
+
|
|
14
|
+
attr_reader :wrapped_state, :meta, :state_class
|
|
15
|
+
|
|
16
|
+
def initialize(wrapped_state, meta, state_class)
|
|
17
|
+
@state_class = state_class
|
|
18
|
+
super(wrapped_state, meta)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def status
|
|
22
|
+
error? ? :error : :ok
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def unwrap(slice: nil, fetch: nil)
|
|
26
|
+
raise_error! if error?
|
|
27
|
+
return bare_state if slice.nil? && fetch.nil?
|
|
28
|
+
|
|
29
|
+
normalized_bare_state = Normalizer.instance(slice).normalize(bare_state)
|
|
30
|
+
fetch.nil? ? normalized_bare_state.values : normalized_bare_state.fetch(fetch, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fetch(key)
|
|
34
|
+
unwrap(fetch: key)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def slice(*keys)
|
|
38
|
+
unwrap(slice: keys)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_result(adapter = nil, **opts)
|
|
42
|
+
error? ? rewrap_error(adapter) : rewrap_success(adapter, **opts)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def raise_error!
|
|
46
|
+
error = state_class.unwrap_error(wrapped_state)
|
|
47
|
+
|
|
48
|
+
case error
|
|
49
|
+
when Exception then raise error
|
|
50
|
+
when WorkflowTemplate::Validation::Result::Failure then raise WorkflowTemplate::Validation::Error, error
|
|
51
|
+
else raise ExecutionError, error
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def rewrap_error(adapter)
|
|
58
|
+
return wrapped_state if adapter.nil? || target_class(adapter).canonical?(wrapped_state)
|
|
59
|
+
|
|
60
|
+
target_class(adapter).wrap_error_with_bare_state(state_class.unwrap_error(wrapped_state), {})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def rewrap_success(adapter, **opts)
|
|
64
|
+
target_class(adapter).wrap_success(unwrap(**opts))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def target_class(adapter)
|
|
68
|
+
return state_class if adapter.nil?
|
|
69
|
+
|
|
70
|
+
State.state_class(State::Adapter.fetch(adapter))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'class_methods'
|
|
4
|
+
require_relative 'final'
|
|
5
|
+
|
|
6
|
+
module WorkflowTemplate
|
|
7
|
+
module State
|
|
8
|
+
module Intermediate
|
|
9
|
+
include Abstract
|
|
10
|
+
|
|
11
|
+
def initialize(wrapped_state, default_strategy, meta)
|
|
12
|
+
@default_strategy = default_strategy
|
|
13
|
+
super(wrapped_state, meta)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :default_strategy
|
|
17
|
+
|
|
18
|
+
def continue?
|
|
19
|
+
!(error? || halted?)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def halted?
|
|
23
|
+
!!bare_state[:halted]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def final
|
|
27
|
+
Final.new(wrapped_state, meta, self.class)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def apply_validations(validations)
|
|
31
|
+
return self if error?
|
|
32
|
+
|
|
33
|
+
wrapped_state = self.class.validate(self.wrapped_state, validations)
|
|
34
|
+
self.class.send :new, wrapped_state, default_strategy, meta
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def merge_error(error, action)
|
|
38
|
+
return wrapped_state if error?
|
|
39
|
+
|
|
40
|
+
wrapped_state = self.class.wrap_error_with_bare_state(error, {})
|
|
41
|
+
process_wrapped_result(wrapped_state, action, trace: true, validate: false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def normalize_data(normalizer)
|
|
45
|
+
wrapped_state = normalize(normalizer)
|
|
46
|
+
self.class.send :new, wrapped_state, default_strategy, meta
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def process_bare_result(bare_result, action, trace:, validate:)
|
|
50
|
+
process_result(action, trace, validate) do |strategy, action_name|
|
|
51
|
+
self.class.map_unwrappable_state(wrapped_state) do |bare_state|
|
|
52
|
+
self.class.merge_bare_result(bare_state, bare_result, strategy, action_name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def process_wrapped_result(wrapped_result, action, trace:, validate:)
|
|
58
|
+
process_result(action, trace, validate) do |strategy, action_name|
|
|
59
|
+
if wrapped_result.nil?
|
|
60
|
+
raise Fatal::UnexpectedNil, action_name if strategy == :update
|
|
61
|
+
|
|
62
|
+
wrapped_state
|
|
63
|
+
else
|
|
64
|
+
raise Fatal::BadActionReturn.new(action_name, wrapped_result) unless self.class.canonical?(wrapped_result)
|
|
65
|
+
|
|
66
|
+
self.class.flat_map_unwrappable_state(wrapped_state) do |bare_state|
|
|
67
|
+
self.class.map_unwrappable_state(wrapped_result) do |bare_result|
|
|
68
|
+
self.class.merge_bare_result(bare_state, bare_result, strategy, action_name)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def state_class
|
|
76
|
+
self.class
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def process_result(action, trace, validate, &block)
|
|
82
|
+
meta = trace ? self.meta.add(action.name) : self.meta
|
|
83
|
+
|
|
84
|
+
strategy = action.state_transition_strategy || default_strategy
|
|
85
|
+
wrapped_result = block.call(strategy, action.name)
|
|
86
|
+
validations = action.validations if validate
|
|
87
|
+
wrapped_state = self.class.validate(wrapped_result, validations)
|
|
88
|
+
|
|
89
|
+
self.class.send :new, wrapped_state, default_strategy, meta
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def normalize(normalizer)
|
|
93
|
+
self.class.map_unwrappable_state(wrapped_state) do |bare_state|
|
|
94
|
+
normalized = normalizer.normalize(bare_state)
|
|
95
|
+
next normalized unless error?
|
|
96
|
+
|
|
97
|
+
self.class.wrap_error_with_bare_state(self.class.unwrap_error(wrapped_state), normalized)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WorkflowTemplate
|
|
4
|
+
module State
|
|
5
|
+
class Meta
|
|
6
|
+
module Null
|
|
7
|
+
def self.add(*)
|
|
8
|
+
self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.trace
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :trace
|
|
19
|
+
|
|
20
|
+
def self.instance(trace)
|
|
21
|
+
trace ? new : Null
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private_class_method :new
|
|
25
|
+
|
|
26
|
+
def initialize(trace: [])
|
|
27
|
+
@trace = trace.freeze
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add(name)
|
|
31
|
+
self.class.send :new, trace: [*@trace, name]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WorkflowTemplate
|
|
4
|
+
module State
|
|
5
|
+
class Normalizer
|
|
6
|
+
attr_reader :keys
|
|
7
|
+
|
|
8
|
+
def initialize(keys)
|
|
9
|
+
@keys = keys&.map(&:to_sym).freeze
|
|
10
|
+
freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def normalize(hash)
|
|
14
|
+
return hash if keys.nil?
|
|
15
|
+
|
|
16
|
+
keys.each_with_object({}) do |key, normalized|
|
|
17
|
+
normalized[key] = hash[key]
|
|
18
|
+
end.freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private_class_method :new
|
|
22
|
+
|
|
23
|
+
def self.instance(keys = nil)
|
|
24
|
+
return null_object if keys.nil?
|
|
25
|
+
|
|
26
|
+
new keys
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.null_object
|
|
30
|
+
@null_object ||= new(nil)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../validation/result/success'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module State
|
|
7
|
+
module Validation
|
|
8
|
+
def self.validate(data, validations)
|
|
9
|
+
case validations
|
|
10
|
+
when Array then validate_multiple(data, validations)
|
|
11
|
+
else validations.call(data)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.validate_multiple(data, validations)
|
|
16
|
+
validations.reduce(WorkflowTemplate::Validation::Result::Success) do |result, validation|
|
|
17
|
+
break result unless result.success?
|
|
18
|
+
|
|
19
|
+
validation.call(data)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.applicable?(validations)
|
|
24
|
+
case validations
|
|
25
|
+
when nil then false
|
|
26
|
+
when Array then !validations.empty?
|
|
27
|
+
else true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'state/intermediate'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module State
|
|
7
|
+
STRATEGIES = %i[merge update].freeze
|
|
8
|
+
|
|
9
|
+
def self.normalize_transition_strategy(strategy)
|
|
10
|
+
strategy = strategy.to_sym
|
|
11
|
+
raise Error, "Unimplemented strategy: '#{strategy}'" unless STRATEGIES.include?(strategy)
|
|
12
|
+
|
|
13
|
+
strategy
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.state_class(adapter)
|
|
17
|
+
state_classes[adapter] ||= Class.new do
|
|
18
|
+
include State::Intermediate
|
|
19
|
+
extend State::ClassMethods
|
|
20
|
+
|
|
21
|
+
raise Error, "Invalid adapter: #{adapter}" unless State.valid_adapter?(adapter)
|
|
22
|
+
|
|
23
|
+
include adapter::InstanceMethods
|
|
24
|
+
extend adapter::ClassMethods
|
|
25
|
+
|
|
26
|
+
private_class_method :new
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.valid_adapter?(adapter)
|
|
31
|
+
adapter.constants.include?(:InstanceMethods) &&
|
|
32
|
+
adapter::InstanceMethods.included_modules.include?(Adapter::Abstract::InstanceMethods) &&
|
|
33
|
+
adapter.constants.include?(:ClassMethods) &&
|
|
34
|
+
adapter::ClassMethods.included_modules.include?(Adapter::Abstract::ClassMethods)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.state_classes
|
|
38
|
+
@state_classes ||= {}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../builder/error'
|
|
4
|
+
require_relative '../../evaluation'
|
|
5
|
+
|
|
6
|
+
module WorkflowTemplate
|
|
7
|
+
module Validation
|
|
8
|
+
module Adapter
|
|
9
|
+
module Abstract
|
|
10
|
+
class Builder
|
|
11
|
+
Error = Validation::Builder::Error
|
|
12
|
+
|
|
13
|
+
def initialize(proxy)
|
|
14
|
+
@proxy = proxy
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :proxy
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|