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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/lib/workflow_template/action/abstract.rb +40 -0
  3. data/lib/workflow_template/action/description/validated.rb +58 -0
  4. data/lib/workflow_template/action/description/wrapper.rb +28 -0
  5. data/lib/workflow_template/action/nested.rb +59 -0
  6. data/lib/workflow_template/action/simple.rb +28 -0
  7. data/lib/workflow_template/action/validated.rb +104 -0
  8. data/lib/workflow_template/action/wrapper.rb +32 -0
  9. data/lib/workflow_template/action.rb +20 -0
  10. data/lib/workflow_template/adapters/state/default.rb +72 -0
  11. data/lib/workflow_template/adapters/state/dry_monads.rb +70 -0
  12. data/lib/workflow_template/adapters/validation/active_model/builder.rb +18 -0
  13. data/lib/workflow_template/adapters/validation/active_model/failure.rb +17 -0
  14. data/lib/workflow_template/adapters/validation/active_model/null_model.rb +24 -0
  15. data/lib/workflow_template/adapters/validation/active_model/validator.rb +35 -0
  16. data/lib/workflow_template/adapters/validation/active_model.rb +24 -0
  17. data/lib/workflow_template/adapters/validation/dry_validation/builder.rb +18 -0
  18. data/lib/workflow_template/adapters/validation/dry_validation/failure.rb +19 -0
  19. data/lib/workflow_template/adapters/validation/dry_validation/validator.rb +46 -0
  20. data/lib/workflow_template/adapters/validation/dry_validation.rb +24 -0
  21. data/lib/workflow_template/adapters/validation/generic/block.rb +24 -0
  22. data/lib/workflow_template/adapters/validation/generic/builder.rb +28 -0
  23. data/lib/workflow_template/adapters/validation/generic/object.rb +30 -0
  24. data/lib/workflow_template/adapters/validation/generic/validator.rb +25 -0
  25. data/lib/workflow_template/adapters/validation/generic.rb +24 -0
  26. data/lib/workflow_template/definer/has_actions/actions.rb +90 -0
  27. data/lib/workflow_template/definer/has_actions/description.rb +48 -0
  28. data/lib/workflow_template/definer/has_actions/dsl.rb +64 -0
  29. data/lib/workflow_template/definer/has_actions/insert.rb +28 -0
  30. data/lib/workflow_template/definer/has_actions/inside.rb +27 -0
  31. data/lib/workflow_template/definer/has_actions/nested.rb +65 -0
  32. data/lib/workflow_template/definer/has_actions/position.rb +83 -0
  33. data/lib/workflow_template/definer/has_actions/redefine.rb +28 -0
  34. data/lib/workflow_template/definer/has_actions/root.rb +22 -0
  35. data/lib/workflow_template/definer/has_actions.rb +45 -0
  36. data/lib/workflow_template/definer/has_default_state_transition_strategy.rb +34 -0
  37. data/lib/workflow_template/definer/has_output_normalizer.rb +35 -0
  38. data/lib/workflow_template/definer/has_state_adapter.rb +30 -0
  39. data/lib/workflow_template/definer/has_validations/action.rb +20 -0
  40. data/lib/workflow_template/definer/has_validations/description.rb +38 -0
  41. data/lib/workflow_template/definer/has_validations/validations.rb +162 -0
  42. data/lib/workflow_template/definer/has_validations.rb +70 -0
  43. data/lib/workflow_template/error.rb +99 -0
  44. data/lib/workflow_template/outcome/block.rb +58 -0
  45. data/lib/workflow_template/outcome/handler.rb +106 -0
  46. data/lib/workflow_template/outcome/status.rb +33 -0
  47. data/lib/workflow_template/outcome/statuses.rb +60 -0
  48. data/lib/workflow_template/outcome.rb +53 -0
  49. data/lib/workflow_template/performer.rb +46 -0
  50. data/lib/workflow_template/state/abstract.rb +33 -0
  51. data/lib/workflow_template/state/adapter.rb +67 -0
  52. data/lib/workflow_template/state/class_methods.rb +43 -0
  53. data/lib/workflow_template/state/final.rb +74 -0
  54. data/lib/workflow_template/state/intermediate.rb +102 -0
  55. data/lib/workflow_template/state/meta.rb +35 -0
  56. data/lib/workflow_template/state/normalizer.rb +34 -0
  57. data/lib/workflow_template/state/validation.rb +32 -0
  58. data/lib/workflow_template/state.rb +41 -0
  59. data/lib/workflow_template/validation/adapter/abstract/builder.rb +28 -0
  60. data/lib/workflow_template/validation/adapter/abstract/result/failure.rb +25 -0
  61. data/lib/workflow_template/validation/adapter/abstract/validator.rb +23 -0
  62. data/lib/workflow_template/validation/adapter.rb +35 -0
  63. data/lib/workflow_template/validation/builder/error.rb +11 -0
  64. data/lib/workflow_template/validation/builder/proxy.rb +129 -0
  65. data/lib/workflow_template/validation/error.rb +16 -0
  66. data/lib/workflow_template/validation/evaluation/abstract.rb +57 -0
  67. data/lib/workflow_template/validation/evaluation/boolean.rb +28 -0
  68. data/lib/workflow_template/validation/evaluation/canonical.rb +21 -0
  69. data/lib/workflow_template/validation/evaluation.rb +4 -0
  70. data/lib/workflow_template/validation/result/failure/abstract.rb +15 -0
  71. data/lib/workflow_template/validation/result/failure.rb +52 -0
  72. data/lib/workflow_template/validation/result/success.rb +19 -0
  73. data/lib/workflow_template/validation/validator/result/failure.rb +36 -0
  74. data/lib/workflow_template/validation/validator/result/success.rb +21 -0
  75. data/lib/workflow_template/workflow/description.rb +56 -0
  76. data/lib/workflow_template/workflow.rb +69 -0
  77. data/lib/workflow_template.rb +11 -0
  78. metadata +126 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkflowTemplate
4
+ module Validation
5
+ module Adapter
6
+ module Abstract
7
+ module Result
8
+ module Failure
9
+ def code
10
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
11
+ end
12
+
13
+ def message(*)
14
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
15
+ end
16
+
17
+ def data
18
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkflowTemplate
4
+ module Validation
5
+ module Adapter
6
+ module Abstract
7
+ module Validator
8
+ def initialize
9
+ freeze
10
+ end
11
+
12
+ def validate(_data)
13
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
14
+ end
15
+
16
+ def describe
17
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'adapter/abstract/builder'
4
+ require_relative 'adapter/abstract/validator'
5
+ require_relative 'adapter/abstract/result/failure'
6
+ require_relative 'validator/result/success'
7
+
8
+ module WorkflowTemplate
9
+ module Validation
10
+ module Adapter
11
+ def self.registry
12
+ @registry ||= {}
13
+ end
14
+
15
+ def self.register(name, adapter)
16
+ registry[name.to_sym] = adapter
17
+ end
18
+
19
+ def self.fetch(name)
20
+ name = name.to_sym
21
+ raise Error, "Not a registered validation adapter: #{name}" unless registry.key? name
22
+
23
+ registry[name]
24
+ end
25
+
26
+ def builder_class
27
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
28
+ end
29
+
30
+ def register(name)
31
+ Adapter.register(name, self)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../error'
4
+
5
+ module WorkflowTemplate
6
+ module Validation
7
+ module Builder
8
+ class Error < WorkflowTemplate::Error; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'error'
4
+ require_relative '../evaluation'
5
+
6
+ module WorkflowTemplate
7
+ module Validation
8
+ module Builder
9
+ class Proxy
10
+ def self.instance(adapter, collection, name: nil, context: nil)
11
+ new adapter, collection, normalize_name(name), normalize_context(context)
12
+ end
13
+
14
+ def self.normalize_name(name)
15
+ return if name.nil?
16
+
17
+ raise Error, 'Empty name' if name.empty?
18
+
19
+ name.to_sym
20
+ end
21
+
22
+ def self.normalize_context(context)
23
+ return if context.nil? || context.empty?
24
+
25
+ raise Error, "Context contains invalid keys: #{context}" if context.any?(&:empty?)
26
+
27
+ context.map(&:to_sym)
28
+ end
29
+
30
+ private_class_method :new
31
+
32
+ def initialize(adapter, collection, name, context)
33
+ @adapter = adapter
34
+ @collection = collection
35
+ @name = name&.to_sym
36
+ @context = context.freeze
37
+ freeze
38
+ end
39
+
40
+ attr_reader :name, :context
41
+
42
+ def key?
43
+ false
44
+ end
45
+
46
+ def key
47
+ nil
48
+ end
49
+
50
+ def named(name)
51
+ raise Error, "Name already defined: #{self.name}" if self.name
52
+
53
+ self.class.instance(adapter, collection, name: name, context: context)
54
+ end
55
+
56
+ def with_context(*context)
57
+ raise Error, "Context already defined: #{self.context}" if self.context
58
+
59
+ self.class.instance(adapter, collection, name: name, context: context)
60
+ end
61
+
62
+ def validate(key = nil, **opts, &block)
63
+ proxy = if key
64
+ Keyed.instance(key, adapter, collection, name: name, context: context)
65
+ else
66
+ raise Error, 'Key must be supplied if name is missing' if name.nil?
67
+ raise Error, 'Context is not available if key is not specified' unless context.nil?
68
+
69
+ self
70
+ end
71
+
72
+ adapter.builder_class
73
+ .new(proxy)
74
+ .validate(**opts, &block)
75
+ end
76
+
77
+ def declare(validation)
78
+ collection.add(evaluation(validation))
79
+ end
80
+
81
+ private
82
+
83
+ attr_reader :adapter, :collection
84
+
85
+ def evaluation(validation)
86
+ evaluation_class.new(name, key, context, validation)
87
+ end
88
+
89
+ def evaluation_class
90
+ case adapter.result_type
91
+ when :boolean then Evaluation::Boolean
92
+ when :canonical then Evaluation::Canonical
93
+ else raise Error, "Unexpected result type: #{adapter.result_type}"
94
+ end
95
+ end
96
+
97
+ class Keyed < Proxy
98
+ def self.instance(key, adapter, collection, name: nil, context: nil)
99
+ key = normalize_key(key)
100
+ new key, adapter, collection, normalize_name(name) || key, normalize_context(context)
101
+ end
102
+
103
+ def self.normalize_key(key)
104
+ return if key.nil?
105
+
106
+ raise Error, 'Empty key' if key.empty?
107
+
108
+ key.to_sym
109
+ end
110
+
111
+ def initialize(key, *args)
112
+ @key = key.to_sym
113
+ super(*args)
114
+ end
115
+
116
+ attr_reader :key
117
+
118
+ def key?
119
+ true
120
+ end
121
+
122
+ def validate(**opts, &block)
123
+ adapter.builder_class.new(self).validate(**opts, &block)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module WorkflowTemplate
6
+ module Validation
7
+ class Error < WorkflowTemplate::Error
8
+ attr_reader :failure
9
+
10
+ def initialize(failure)
11
+ @failure = failure
12
+ super(failure.describe)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+ require_relative '../adapter/abstract/result/failure'
5
+ require_relative '../validator/result/success'
6
+ require_relative '../validator/result/failure'
7
+ require_relative '../result/failure'
8
+ require_relative '../result/success'
9
+
10
+ module WorkflowTemplate
11
+ module Validation
12
+ module Evaluation
13
+ class Abstract
14
+ attr_reader :name, :key, :context, :validator
15
+
16
+ def initialize(name, key, context, validator)
17
+ @name = name.to_sym
18
+ @key = key&.to_sym
19
+ @context = context.freeze
20
+ @validator = validator
21
+ freeze
22
+ end
23
+
24
+ def call(data)
25
+ result = run_validator(data)
26
+ evaluate(result)
27
+ end
28
+
29
+ def evaluate(_result)
30
+ raise NotImplementedError, "#{self.class.name}##{__method__} unimplemented"
31
+ end
32
+
33
+ def describe
34
+ validator.describe
35
+ end
36
+
37
+ private
38
+
39
+ def run_validator(data)
40
+ input = key.nil? ? data : data[key]
41
+ case context
42
+ when nil then validator.call(input)
43
+ else validator.call(input, context: data&.slice(*context))
44
+ end
45
+ end
46
+
47
+ def to_failure(result)
48
+ if result.is_a? Adapter::Abstract::Result::Failure
49
+ Result::Failure.instance(name, key, result)
50
+ else
51
+ to_failure(Validator::Result::Failure.new(validator, data: result))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'abstract'
4
+
5
+ module WorkflowTemplate
6
+ module Validation
7
+ module Evaluation
8
+ class Boolean < Abstract
9
+ def evaluate(result)
10
+ result, detail = result
11
+ if result
12
+ case detail
13
+ when nil then Result::Success
14
+ when Hash, Array
15
+ raise Fatal::BadValidationReturn, name unless detail.empty?
16
+
17
+ Result::Success
18
+ else
19
+ raise Fatal::BadValidationReturn, name
20
+ end
21
+ else
22
+ to_failure(detail)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'abstract'
4
+
5
+ module WorkflowTemplate
6
+ module Validation
7
+ module Evaluation
8
+ class Canonical < Abstract
9
+ def evaluate(result)
10
+ case result
11
+ when Validator::Result::Success then Result::Success
12
+ when Adapter::Abstract::Result::Failure
13
+ to_failure(result)
14
+ else
15
+ raise Fatal::BadValidationReturn, name
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'evaluation/boolean'
4
+ require_relative 'evaluation/canonical'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkflowTemplate
4
+ module Validation
5
+ module Result
6
+ class Failure
7
+ module Abstract
8
+ def success?
9
+ false
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'failure/abstract'
4
+ require 'forwardable'
5
+
6
+ module WorkflowTemplate
7
+ module Validation
8
+ module Result
9
+ class Failure
10
+ include Abstract
11
+ extend Forwardable
12
+
13
+ attr_reader :name, :key, :failure
14
+
15
+ def_delegator :failure, :data
16
+
17
+ def self.instance(name, key, failure)
18
+ new name, key, failure
19
+ end
20
+
21
+ def initialize(name, key, failure)
22
+ @name = name.to_sym
23
+ @key = key&.to_sym
24
+ @failure = failure
25
+ freeze
26
+ end
27
+
28
+ def code
29
+ failure.code || :"#{name}_failed"
30
+ end
31
+
32
+ def describe
33
+ "#{code}: #{name} #{failure.describe}"
34
+ end
35
+
36
+ def message(*args, **opts)
37
+ failure.message(*args, **opts)
38
+ end
39
+
40
+ def eql?(other)
41
+ return false unless other.is_a?(self.class)
42
+
43
+ name == other.name
44
+ end
45
+
46
+ def hash
47
+ name.hash
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkflowTemplate
4
+ module Validation
5
+ module Result
6
+ module Success
7
+ def self.success?
8
+ true
9
+ end
10
+
11
+ def self.===(other)
12
+ other.equal?(self)
13
+ end
14
+
15
+ freeze
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../adapter/abstract/result/failure'
4
+
5
+ module WorkflowTemplate
6
+ module Validation
7
+ module Validator
8
+ module Result
9
+ class Failure
10
+ include Adapter::Abstract::Result::Failure
11
+
12
+ attr_reader :validator, :code, :data
13
+
14
+ def initialize(validator, code: nil, data: nil)
15
+ @validator = validator
16
+ @code = code&.to_sym || :validation_failed
17
+ @data = data
18
+ freeze
19
+ end
20
+
21
+ def success?
22
+ false
23
+ end
24
+
25
+ def describe
26
+ validator.describe
27
+ end
28
+
29
+ def message(*)
30
+ describe
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorkflowTemplate
4
+ module Validation
5
+ module Validator
6
+ module Result
7
+ module Success
8
+ def self.success?
9
+ true
10
+ end
11
+
12
+ def self.===(other)
13
+ other.equal?(self)
14
+ end
15
+
16
+ freeze
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module WorkflowTemplate
6
+ class Workflow
7
+ class Description
8
+ def self.register(group, key, &block)
9
+ registry[group][key] = block
10
+ end
11
+
12
+ def self.describe(workflow)
13
+ description = new
14
+ registry.each do |group, blocks|
15
+ blocks.each_value do |block|
16
+ result = block.call(workflow)
17
+ next if result.nil?
18
+
19
+ description.add!(result, group)
20
+ end
21
+ end
22
+ description.format
23
+ end
24
+
25
+ def self.registry
26
+ @registry ||= { initialize: {}, perform: {}, finalize: {} }
27
+ end
28
+
29
+ def initialize
30
+ @initialize = []
31
+ @perform = []
32
+ @finalize = []
33
+ end
34
+
35
+ def add!(description, group)
36
+ case group
37
+ when :initialize then @initialize << description
38
+ when :perform then @perform << description
39
+ when :finalize then @finalize << description
40
+ else raise Error, "Unexpected group: #{group}"
41
+ end
42
+ end
43
+
44
+ def format
45
+ [@initialize, @perform, @finalize].filter_map do |group|
46
+ next if group.empty?
47
+
48
+ formatted = group.map do |element|
49
+ element.is_a?(String) ? element : element.format(level: 0)
50
+ end
51
+ formatted.join("\n")
52
+ end.join("\n\n") << "\n"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'definer/has_actions'
4
+ require_relative 'definer/has_actions/root'
5
+ require_relative 'definer/has_default_state_transition_strategy'
6
+ require_relative 'definer/has_output_normalizer'
7
+ require_relative 'definer/has_state_adapter'
8
+ require_relative 'definer/has_validations'
9
+ require_relative 'definer/has_validations/description'
10
+ require_relative 'outcome'
11
+
12
+ require_relative 'performer'
13
+ require_relative 'workflow/description'
14
+
15
+ module WorkflowTemplate
16
+ class Workflow
17
+ module ModuleMethods
18
+ include Definer::HasActions::Root
19
+ include Definer::HasDefaultStateTransitionStrategy
20
+ include Definer::HasOutputNormalizer
21
+ include Definer::HasStateAdapter
22
+ include Definer::HasValidations
23
+ include Performer
24
+
25
+ def perform(input, receiver, trace: false) # rubocop:disable Metrics/AbcSize
26
+ state_class = State.state_class(state_adapter)
27
+ state = state_class.initial(
28
+ input,
29
+ default_state_transition_strategy,
30
+ validations.input_validations,
31
+ trace: trace
32
+ )
33
+ state = super(state, receiver) if state.continue?
34
+ state = state.normalize_data(output_normalizer)
35
+ state = state.apply_validations(validations.output_validations)
36
+ Outcome.new(state.final)
37
+ rescue Fatal
38
+ raise
39
+ rescue StandardError => e
40
+ raise Fatal, "Unable to initialize state class: #{e}" unless state_class
41
+ raise Fatal, "Unable to initialize state: #{e}" unless state
42
+
43
+ raise Fatal, "Unexpected error: #{e}"
44
+ end
45
+
46
+ def describe
47
+ raise Error, "Can't describe unfrozen workflow" unless frozen?
48
+
49
+ Description.describe(self)
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ attr_reader :impl
55
+
56
+ def freeze
57
+ @impl = new.freeze
58
+ super
59
+ end
60
+ end
61
+
62
+ extend ModuleMethods
63
+ extend ClassMethods
64
+
65
+ def perform(inputs, trace: false)
66
+ self.class.perform(inputs, self, trace: trace)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'workflow_template/workflow'
4
+
5
+ module WorkflowTemplate
6
+ VERSION = '0.0.4'
7
+
8
+ def self.gem_version
9
+ ::Gem::Version.new(VERSION)
10
+ end
11
+ end