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,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../performer'
|
|
4
|
+
require_relative 'has_actions/actions'
|
|
5
|
+
require_relative 'has_actions/nested'
|
|
6
|
+
|
|
7
|
+
module WorkflowTemplate
|
|
8
|
+
module Definer
|
|
9
|
+
module HasActions
|
|
10
|
+
def actions
|
|
11
|
+
return @actions if frozen?
|
|
12
|
+
|
|
13
|
+
@own_actions ||= Actions::Unprepared.instance
|
|
14
|
+
return @own_actions.prepared unless defined?(superclass) && superclass.respond_to?(:actions)
|
|
15
|
+
|
|
16
|
+
superclass.actions.merge(@own_actions)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def freeze
|
|
20
|
+
unless frozen?
|
|
21
|
+
@actions = prepare_actions
|
|
22
|
+
@own_actions = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
def prepare_actions
|
|
31
|
+
actions
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def store_action(action, position)
|
|
35
|
+
@own_actions ||= Actions::Unprepared.instance
|
|
36
|
+
@own_actions.add_simple(action, position)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def store_wrapper_action(action)
|
|
40
|
+
@own_actions ||= Actions::Unprepared.instance
|
|
41
|
+
@own_actions.add_wrapper(action)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../state'
|
|
4
|
+
require_relative '../workflow/description'
|
|
5
|
+
|
|
6
|
+
module WorkflowTemplate
|
|
7
|
+
module Definer
|
|
8
|
+
module HasDefaultStateTransitionStrategy
|
|
9
|
+
Workflow::Description.register(:initialize, :state) do |workflow|
|
|
10
|
+
"state: #{workflow.default_state_transition_strategy}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def default_state_transition_strategy(*args)
|
|
14
|
+
case args.length
|
|
15
|
+
when 0
|
|
16
|
+
return @default_state_transition_strategy if defined? @default_state_transition_strategy
|
|
17
|
+
if defined?(superclass) && superclass.respond_to?(:default_state_transition_strategy)
|
|
18
|
+
return superclass.default_state_transition_strategy
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
:merge
|
|
22
|
+
when 1 then @default_state_transition_strategy = State.normalize_transition_strategy(args[0])
|
|
23
|
+
else raise Error, "Unexpected arguments for state transition strategy: '#{args}'"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def freeze
|
|
28
|
+
@default_state_transition_strategy = default_state_transition_strategy unless frozen?
|
|
29
|
+
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../state'
|
|
4
|
+
require_relative '../workflow/description'
|
|
5
|
+
|
|
6
|
+
module WorkflowTemplate
|
|
7
|
+
module Definer
|
|
8
|
+
module HasOutputNormalizer
|
|
9
|
+
Workflow::Description.register(:finalize, :output_normalizer) do |workflow|
|
|
10
|
+
next if workflow.send(:output_normalizer).keys.nil?
|
|
11
|
+
|
|
12
|
+
"normalizes output: #{workflow.send(:output_normalizer).keys.join(', ')}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def normalize_output(*keys)
|
|
16
|
+
@output_normalizer = State::Normalizer.instance(keys)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def freeze
|
|
20
|
+
@output_normalizer = output_normalizer unless frozen?
|
|
21
|
+
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def output_normalizer
|
|
28
|
+
return @output_normalizer if defined? @output_normalizer
|
|
29
|
+
return State::Normalizer.instance unless defined?(superclass) && superclass.respond_to?(:output_normalizer)
|
|
30
|
+
|
|
31
|
+
superclass.output_normalizer
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../adapters/state/default'
|
|
4
|
+
require_relative '../workflow/description'
|
|
5
|
+
|
|
6
|
+
module WorkflowTemplate
|
|
7
|
+
module Definer
|
|
8
|
+
module HasStateAdapter
|
|
9
|
+
def state_adapter(*args)
|
|
10
|
+
case args.length
|
|
11
|
+
when 0
|
|
12
|
+
return @state_adapter if defined? @state_adapter
|
|
13
|
+
if defined?(superclass) && superclass.respond_to?(:state_adapter)
|
|
14
|
+
return superclass.state_adapter
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
State::Adapter.fetch(:default)
|
|
18
|
+
when 1 then @state_adapter = State::Adapter.fetch(args[0])
|
|
19
|
+
else raise Error, "Unexpected arguments for state adapter: '#{args}'"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def freeze
|
|
24
|
+
@state_adapter = state_adapter unless frozen?
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WorkflowTemplate
|
|
4
|
+
module Definer
|
|
5
|
+
module HasValidations
|
|
6
|
+
class Action
|
|
7
|
+
def initialize(action, validations)
|
|
8
|
+
@action = action
|
|
9
|
+
@validations = validations
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :validations
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :action
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../workflow/description'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
module Definer
|
|
7
|
+
module HasValidations
|
|
8
|
+
module Description
|
|
9
|
+
Workflow::Description.register(:initialize, :input_validations) do |workflow|
|
|
10
|
+
next if void?(workflow.send(:validations).input)
|
|
11
|
+
|
|
12
|
+
"validates input: #{describe_entry(workflow.send(:validations).input)}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Workflow::Description.register(:finalize, :output_validations) do |workflow|
|
|
16
|
+
next if void?(workflow.send(:validations).output)
|
|
17
|
+
|
|
18
|
+
"validates output: #{describe_entry(workflow.send(:validations).output)}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.describe_entry(entry)
|
|
22
|
+
case entry
|
|
23
|
+
when Array then "{ #{entry.join(', ')} }"
|
|
24
|
+
else entry
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.void?(entry)
|
|
29
|
+
case entry
|
|
30
|
+
when nil then true
|
|
31
|
+
when Symbol then false
|
|
32
|
+
else entry.empty?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
require_relative '../../error'
|
|
6
|
+
require_relative '../../validation/evaluation/abstract'
|
|
7
|
+
|
|
8
|
+
module WorkflowTemplate
|
|
9
|
+
module Definer
|
|
10
|
+
module HasValidations
|
|
11
|
+
module Validations
|
|
12
|
+
def self.merge_entries(current_entry, new_entry)
|
|
13
|
+
new_entry = normalize_entry(new_entry)
|
|
14
|
+
return new_entry if current_entry.nil?
|
|
15
|
+
return current_entry if new_entry.nil?
|
|
16
|
+
|
|
17
|
+
entry_to_set(current_entry) | entry_to_set(new_entry)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.entry_to_set(entry)
|
|
21
|
+
case entry
|
|
22
|
+
when Set then entry
|
|
23
|
+
when Array then entry.to_set
|
|
24
|
+
else Set.new([entry])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.normalize_entry(entry)
|
|
29
|
+
case entry
|
|
30
|
+
when nil then nil
|
|
31
|
+
when Array, Set then entry.to_a.to_set(&:to_sym)
|
|
32
|
+
else entry.to_sym
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Unprepared
|
|
37
|
+
def self.instance
|
|
38
|
+
new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attr_reader :declarations
|
|
42
|
+
|
|
43
|
+
def initialize(declarations: {}, input_set: nil, output_set: nil)
|
|
44
|
+
@declarations = declarations.freeze
|
|
45
|
+
@input_set = Validations.normalize_entry(input_set)
|
|
46
|
+
@output_set = Validations.normalize_entry(output_set)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private_class_method :new
|
|
50
|
+
|
|
51
|
+
def add(declaration)
|
|
52
|
+
raise Error, "Declaration name taken: '#{declaration.name}'" if @declarations.key? declaration.name
|
|
53
|
+
raise Error, "Validation '#{declaration.name}' not properly declared" unless valid_declaration?(declaration)
|
|
54
|
+
|
|
55
|
+
@declarations = declarations.merge(declaration.name => declaration).freeze
|
|
56
|
+
declaration
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate_input(entry)
|
|
60
|
+
@input_set = Validations.merge_entries(@input_set, entry)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_output(entry)
|
|
64
|
+
@output_set = Validations.merge_entries(@output_set, entry)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prepared
|
|
68
|
+
Prepared.instance declarations: declarations, input: input_set, output: output_set
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def freeze
|
|
72
|
+
raise Error, "Freeze is unimplemented for #{self.class.name}, call #prepared instead"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
protected
|
|
76
|
+
|
|
77
|
+
attr_reader :input_set, :output_set
|
|
78
|
+
|
|
79
|
+
def valid_declaration?(declaration)
|
|
80
|
+
declaration.is_a?(Validation::Evaluation::Abstract)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class Prepared
|
|
85
|
+
attr_reader :declarations, :input, :output
|
|
86
|
+
|
|
87
|
+
def self.instance(declarations:, input:, output:)
|
|
88
|
+
new(declarations, normalize_entry(input), normalize_entry(output))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.normalize_entry(entry)
|
|
92
|
+
case entry
|
|
93
|
+
when nil then nil
|
|
94
|
+
when Array, Set then entry.to_a.map(&:to_sym)
|
|
95
|
+
else entry.to_sym
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private_class_method :new
|
|
100
|
+
|
|
101
|
+
def initialize(declarations, input, output)
|
|
102
|
+
@declarations = declarations.freeze
|
|
103
|
+
@input = input.freeze
|
|
104
|
+
@output = output.freeze
|
|
105
|
+
freeze
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def merge(other)
|
|
109
|
+
merged_validations = declarations.merge other.declarations
|
|
110
|
+
merged_input = Validations.merge_entries(input, other.send(:input_set))
|
|
111
|
+
merged_output = Validations.merge_entries(output, other.send(:output_set))
|
|
112
|
+
|
|
113
|
+
Prepared.instance(
|
|
114
|
+
declarations: merged_validations,
|
|
115
|
+
input: merged_input,
|
|
116
|
+
output: merged_output
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def resolve_validations(option)
|
|
121
|
+
case option
|
|
122
|
+
when nil then nil
|
|
123
|
+
when Array then option.map { |element| resolve_validation(element) }.freeze
|
|
124
|
+
else resolve_validation(option).freeze
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def resolve_validation(name)
|
|
129
|
+
name = name.to_sym
|
|
130
|
+
raise Error, "Validation not declared: #{name}" unless declarations.key? name
|
|
131
|
+
|
|
132
|
+
declarations[name]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def resolved
|
|
136
|
+
Resolved.new(self)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class Resolved
|
|
141
|
+
extend Forwardable
|
|
142
|
+
|
|
143
|
+
def_delegators :@prepared, :declarations
|
|
144
|
+
def_delegators :@prepared, :input
|
|
145
|
+
def_delegators :@prepared, :output
|
|
146
|
+
|
|
147
|
+
def_delegators :@prepared, :merge
|
|
148
|
+
def_delegators :@prepared, :resolve_validations
|
|
149
|
+
|
|
150
|
+
def initialize(prepared)
|
|
151
|
+
@prepared = prepared
|
|
152
|
+
@input_validations = prepared.resolve_validations(prepared.input)
|
|
153
|
+
@output_validations = prepared.resolve_validations(prepared.output)
|
|
154
|
+
freeze
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
attr_reader :input_validations, :output_validations
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../validation/adapter'
|
|
4
|
+
require_relative '../validation/builder/proxy'
|
|
5
|
+
require_relative '../adapters/validation/generic'
|
|
6
|
+
require_relative 'has_validations/validations'
|
|
7
|
+
|
|
8
|
+
module WorkflowTemplate
|
|
9
|
+
module Definer
|
|
10
|
+
module HasValidations
|
|
11
|
+
def default_validation_adapter(*args)
|
|
12
|
+
case args.length
|
|
13
|
+
when 0
|
|
14
|
+
return @default_validation_adapter if defined? @default_validation_adapter
|
|
15
|
+
if defined?(superclass) && superclass.respond_to?(:default_validation_adapter)
|
|
16
|
+
return superclass.default_validation_adapter
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
:default
|
|
20
|
+
when 1 then @default_validation_adapter = args[0]
|
|
21
|
+
else raise Error, "Unexpected arguments for default validation adapter: '#{args}'"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def declare_validation(name = nil, using: default_validation_adapter)
|
|
26
|
+
adapter = Validation::Adapter.fetch(using)
|
|
27
|
+
|
|
28
|
+
Validation::Builder::Proxy
|
|
29
|
+
.instance(adapter, own_validations, name: name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_input(keys)
|
|
33
|
+
own_validations.validate_input(keys)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def validate_output(keys)
|
|
37
|
+
own_validations.validate_output(keys)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def freeze
|
|
41
|
+
unless frozen?
|
|
42
|
+
@default_validation_adapter = default_validation_adapter
|
|
43
|
+
@validations = validations.resolved
|
|
44
|
+
@own_validations = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
def prepare_action(action)
|
|
53
|
+
super.prepare(validations)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validations
|
|
57
|
+
return @validations if defined? @validations
|
|
58
|
+
return own_validations.prepared unless defined?(superclass) && superclass.respond_to?(:validations, true)
|
|
59
|
+
|
|
60
|
+
superclass.validations.merge(own_validations)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def own_validations
|
|
66
|
+
@own_validations ||= Validations::Unprepared.instance
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WorkflowTemplate
|
|
4
|
+
class Error < RuntimeError; end
|
|
5
|
+
|
|
6
|
+
class Fatal < Error
|
|
7
|
+
class ArgumentError < Fatal
|
|
8
|
+
def initialize(action_name, message)
|
|
9
|
+
full_message = "Argument mismatch in '#{action_name}': #{message}"
|
|
10
|
+
|
|
11
|
+
super(full_message)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.depth(argument_error, method)
|
|
15
|
+
path, = method.source_location
|
|
16
|
+
label = method.name.to_s
|
|
17
|
+
_, index = argument_error.backtrace_locations.each_with_index.find do |location, _index|
|
|
18
|
+
location.label == label && location.path == path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
index
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class InconsistentState < Fatal
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class BadActionReturn < Fatal
|
|
29
|
+
def initialize(action_name, value)
|
|
30
|
+
full_message = <<~ERR.squish
|
|
31
|
+
Invalid return from '#{action_name}' action, nil or Hash expected,
|
|
32
|
+
got '#{value.inspect}' (#{value.class.name})
|
|
33
|
+
ERR
|
|
34
|
+
|
|
35
|
+
super(full_message)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class BadReturnKeys < Fatal
|
|
40
|
+
def initialize(action_name, keys)
|
|
41
|
+
keys = keys.map { |key| "'#{key.inspect}' (#{key.class.name})" }
|
|
42
|
+
full_message = "Invalid return from '#{action_name}' action, symbolic keys expected, got #{keys.join(', ')}"
|
|
43
|
+
super(full_message)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class BadValidationReturn < Fatal
|
|
48
|
+
def initialize(action_name)
|
|
49
|
+
full_message = "Invalid return from '#{action_name}' validation"
|
|
50
|
+
super(full_message)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class BadInput < Fatal
|
|
55
|
+
def initialize(value)
|
|
56
|
+
full_message = "Invalid input, Hash expected, got '#{value.inspect}' (#{value.class.name})"
|
|
57
|
+
super(full_message)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class Detailed < Fatal
|
|
62
|
+
def initialize(action_name, input, message)
|
|
63
|
+
full_message = "Fatal error in '#{action_name}': #{message}, input: #{input.inspect}"
|
|
64
|
+
super(full_message)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class ForeignCodeError < Fatal
|
|
69
|
+
attr_reader :foreign_error
|
|
70
|
+
|
|
71
|
+
def initialize(foreign_error)
|
|
72
|
+
@foreign_error = foreign_error
|
|
73
|
+
full_message = "Foreign error #{foreign_error.class.name}: #{foreign_error.message}"
|
|
74
|
+
super(full_message)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class UnexpectedNil < Fatal
|
|
79
|
+
def initialize(action_name)
|
|
80
|
+
full_message = "Nil return from '#{action_name}', nil is not allowed for 'update' state transition strategy"
|
|
81
|
+
super(full_message)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class Unimplemented < Fatal
|
|
86
|
+
def initialize(action_name)
|
|
87
|
+
super("Unimplemented method: '#{action_name}'")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class ExecutionError < Error
|
|
93
|
+
def initialize(original_error)
|
|
94
|
+
@original_error = original_error
|
|
95
|
+
super(original_error.to_s)
|
|
96
|
+
freeze
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../validation/result/failure'
|
|
4
|
+
|
|
5
|
+
module WorkflowTemplate
|
|
6
|
+
class Outcome
|
|
7
|
+
class Block
|
|
8
|
+
attr_reader :handler
|
|
9
|
+
|
|
10
|
+
def initialize(handler, binding)
|
|
11
|
+
@handler = handler
|
|
12
|
+
@binding = binding
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ok(&block)
|
|
16
|
+
handle_status(:ok, &block)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def invalid(&block)
|
|
20
|
+
handle_status(:error, matcher: Validation::Result::Failure, &block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def error(match = nil, &block)
|
|
24
|
+
handle_status(:error, matcher: match, &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def otherwise(&block)
|
|
28
|
+
self.handler = handler.otherwise(binding: binding, &block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def otherwise_unwrap(slice: nil, fetch: nil)
|
|
32
|
+
self.handler = handler.otherwise_unwrap(slice: slice, fetch: fetch)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def otherwise_raise(error: nil)
|
|
36
|
+
handler = handler()
|
|
37
|
+
self.handler = handler.otherwise(binding: binding) do |_|
|
|
38
|
+
error ||= Error.new("Unhandled workflow status: '#{handler.final_state.status}'")
|
|
39
|
+
raise error
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def delegate_to(proc)
|
|
44
|
+
instance_eval(&proc)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :binding
|
|
50
|
+
attr_writer :handler
|
|
51
|
+
|
|
52
|
+
def handle_status(status, **opts, &block)
|
|
53
|
+
self.handler = handler.handle_status(status, binding: binding, **opts, &block)
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require_relative 'statuses'
|
|
5
|
+
require_relative '../validation/error'
|
|
6
|
+
|
|
7
|
+
module WorkflowTemplate
|
|
8
|
+
class Outcome
|
|
9
|
+
class Handler
|
|
10
|
+
extend Forwardable
|
|
11
|
+
|
|
12
|
+
attr_reader :final_state, :retval
|
|
13
|
+
|
|
14
|
+
def_delegator :@statuses, :unhandled?
|
|
15
|
+
def_delegator :@statuses, :default_missing?
|
|
16
|
+
def_delegator :@statuses, :default_missing
|
|
17
|
+
|
|
18
|
+
def initialize(final_state, statuses: Outcome::Statuses.new, retval: nil)
|
|
19
|
+
@final_state = final_state
|
|
20
|
+
@statuses = statuses
|
|
21
|
+
@retval = retval
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def handle_status(status, binding: nil, **opts, &block) # rubocop:disable Metrics/AbcSize
|
|
26
|
+
status = status.to_sym
|
|
27
|
+
raise WorkflowTemplate::Error, "Handler for '#{status}' unexpected" if @statuses[status].default?
|
|
28
|
+
|
|
29
|
+
statuses = @statuses.on_default_detected(status, self.class.default?(status, **opts))
|
|
30
|
+
if unhandled?(final_state.status) && self.class.run_handler?(status, final_state, **opts)
|
|
31
|
+
self.class.run_handler(status, statuses, final_state, binding: binding, &block)
|
|
32
|
+
else
|
|
33
|
+
Handler.new(final_state, statuses: statuses, retval: retval)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def otherwise(binding: nil, &block)
|
|
38
|
+
statuses = @statuses.all_defaults!
|
|
39
|
+
if unhandled?(final_state.status)
|
|
40
|
+
self.class.run_handler(final_state.status, statuses, final_state, binding: binding, &block)
|
|
41
|
+
else
|
|
42
|
+
Handler.new(final_state, statuses: statuses, retval: retval)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def otherwise_unwrap(slice: nil, fetch: nil)
|
|
47
|
+
statuses = @statuses.all_defaults!
|
|
48
|
+
if unhandled?(final_state.status)
|
|
49
|
+
Handler.new(
|
|
50
|
+
final_state,
|
|
51
|
+
statuses: statuses.handled!(final_state.status),
|
|
52
|
+
retval: final_state.unwrap(slice: slice, fetch: fetch)
|
|
53
|
+
)
|
|
54
|
+
else
|
|
55
|
+
Handler.new(final_state, statuses: statuses, retval: retval)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.run_handler(status, statuses, final_state, binding: nil, &block)
|
|
60
|
+
statuses = statuses.handled!(status)
|
|
61
|
+
retval = final_state.state_class.run_handler!(final_state, binding, &block)
|
|
62
|
+
Handler.new(final_state, statuses: statuses, retval: retval)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.default?(status, **opts)
|
|
66
|
+
handler_strategy(status).default?(**opts)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.run_handler?(status, final_state, **opts)
|
|
70
|
+
return false unless status == final_state.status
|
|
71
|
+
|
|
72
|
+
handler_strategy(status).run_handler?(final_state, **opts)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.handler_strategy(status)
|
|
76
|
+
case status
|
|
77
|
+
when :ok then Ok
|
|
78
|
+
when :error then Error
|
|
79
|
+
else raise WorkflowTemplate::Error, "Unexpected status '#{status}'"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Ok
|
|
84
|
+
def self.default?
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.run_handler?(_final_state)
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
module Error
|
|
94
|
+
def self.default?(matcher: nil)
|
|
95
|
+
matcher.nil?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.run_handler?(final_state, matcher: nil)
|
|
99
|
+
# rubocop:disable Style/CaseEquality
|
|
100
|
+
matcher.nil? || matcher === final_state.error
|
|
101
|
+
# rubocop:enable Style/CaseEquality
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|