igniter 0.2.0
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/CHANGELOG.md +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +264 -0
- data/docs/API_V2.md +242 -0
- data/docs/ARCHITECTURE_V2.md +317 -0
- data/docs/EXECUTION_MODEL_V2.md +245 -0
- data/docs/IGNITER_CONCEPTS.md +81 -0
- data/examples/README.md +77 -0
- data/examples/basic_pricing.rb +27 -0
- data/examples/composition.rb +39 -0
- data/examples/diagnostics.rb +28 -0
- data/lib/igniter/compiler/compiled_graph.rb +78 -0
- data/lib/igniter/compiler/graph_compiler.rb +60 -0
- data/lib/igniter/compiler/validator.rb +205 -0
- data/lib/igniter/compiler.rb +10 -0
- data/lib/igniter/contract.rb +117 -0
- data/lib/igniter/diagnostics/report.rb +174 -0
- data/lib/igniter/diagnostics.rb +8 -0
- data/lib/igniter/dsl/contract_builder.rb +95 -0
- data/lib/igniter/dsl.rb +8 -0
- data/lib/igniter/errors.rb +53 -0
- data/lib/igniter/events/bus.rb +39 -0
- data/lib/igniter/events/event.rb +53 -0
- data/lib/igniter/events.rb +9 -0
- data/lib/igniter/extensions/auditing/timeline.rb +99 -0
- data/lib/igniter/extensions/auditing.rb +10 -0
- data/lib/igniter/extensions/introspection/graph_formatter.rb +73 -0
- data/lib/igniter/extensions/introspection/runtime_formatter.rb +102 -0
- data/lib/igniter/extensions/introspection.rb +11 -0
- data/lib/igniter/extensions/reactive/engine.rb +36 -0
- data/lib/igniter/extensions/reactive/matcher.rb +21 -0
- data/lib/igniter/extensions/reactive/reaction.rb +17 -0
- data/lib/igniter/extensions/reactive.rb +12 -0
- data/lib/igniter/extensions.rb +10 -0
- data/lib/igniter/model/composition_node.rb +22 -0
- data/lib/igniter/model/compute_node.rb +21 -0
- data/lib/igniter/model/graph.rb +15 -0
- data/lib/igniter/model/input_node.rb +27 -0
- data/lib/igniter/model/node.rb +22 -0
- data/lib/igniter/model/output_node.rb +21 -0
- data/lib/igniter/model.rb +13 -0
- data/lib/igniter/runtime/cache.rb +58 -0
- data/lib/igniter/runtime/execution.rb +142 -0
- data/lib/igniter/runtime/input_validator.rb +145 -0
- data/lib/igniter/runtime/invalidator.rb +52 -0
- data/lib/igniter/runtime/node_state.rb +31 -0
- data/lib/igniter/runtime/resolver.rb +114 -0
- data/lib/igniter/runtime/result.rb +105 -0
- data/lib/igniter/runtime.rb +14 -0
- data/lib/igniter/version.rb +5 -0
- data/lib/igniter.rb +20 -0
- data/sig/igniter.rbs +4 -0
- metadata +126 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Introspection
|
|
6
|
+
class RuntimeFormatter
|
|
7
|
+
def self.states(execution)
|
|
8
|
+
new(execution).states
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.explain_output(execution, output_name)
|
|
12
|
+
new(execution).explain_output(output_name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(execution)
|
|
16
|
+
@execution = execution
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def states
|
|
20
|
+
@execution.cache.to_h.each_with_object({}) do |(node_name, state), memo|
|
|
21
|
+
memo[node_name] = serialize_state(state)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def explain_output(output_name)
|
|
26
|
+
output = @execution.compiled_graph.fetch_output(output_name)
|
|
27
|
+
source = @execution.compiled_graph.fetch_node(output.source)
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
output_id: output.id,
|
|
31
|
+
output: output.name,
|
|
32
|
+
path: output.path,
|
|
33
|
+
source_id: source.id,
|
|
34
|
+
source: source.name,
|
|
35
|
+
source_path: source.path,
|
|
36
|
+
dependencies: dependency_tree(source)
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def dependency_tree(node)
|
|
43
|
+
state = @execution.cache.fetch(node.name)
|
|
44
|
+
{
|
|
45
|
+
id: node.id,
|
|
46
|
+
name: node.name,
|
|
47
|
+
path: node.path,
|
|
48
|
+
kind: node.kind,
|
|
49
|
+
source_location: node.source_location,
|
|
50
|
+
status: state&.status,
|
|
51
|
+
invalidated_by: invalidation_details(state),
|
|
52
|
+
value: serialize_value(state&.value),
|
|
53
|
+
error: state&.error&.message,
|
|
54
|
+
dependencies: node.dependencies.map do |dependency_name|
|
|
55
|
+
dependency_tree(@execution.compiled_graph.fetch_node(dependency_name))
|
|
56
|
+
end
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def serialize_state(state)
|
|
61
|
+
{
|
|
62
|
+
id: state.node.id,
|
|
63
|
+
path: state.node.path,
|
|
64
|
+
kind: state.node.kind,
|
|
65
|
+
source_location: state.node.source_location,
|
|
66
|
+
status: state.status,
|
|
67
|
+
version: state.version,
|
|
68
|
+
invalidated_by: invalidation_details(state),
|
|
69
|
+
value: serialize_value(state.value),
|
|
70
|
+
error: state.error&.message
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def invalidation_details(state)
|
|
75
|
+
return nil unless state&.invalidated_by
|
|
76
|
+
|
|
77
|
+
invalidating_node = @execution.compiled_graph.fetch_node(state.invalidated_by)
|
|
78
|
+
{
|
|
79
|
+
node_id: invalidating_node.id,
|
|
80
|
+
node_name: invalidating_node.name,
|
|
81
|
+
node_path: invalidating_node.path
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def serialize_value(value)
|
|
86
|
+
case value
|
|
87
|
+
when Igniter::Runtime::Result
|
|
88
|
+
{
|
|
89
|
+
type: :result,
|
|
90
|
+
graph: value.execution.compiled_graph.name,
|
|
91
|
+
execution_id: value.execution.events.execution_id
|
|
92
|
+
}
|
|
93
|
+
when Array
|
|
94
|
+
value.map { |item| serialize_value(item) }
|
|
95
|
+
else
|
|
96
|
+
value
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Reactive
|
|
6
|
+
class Engine
|
|
7
|
+
attr_reader :execution, :contract, :reactions, :errors
|
|
8
|
+
|
|
9
|
+
def initialize(execution:, contract:, reactions:)
|
|
10
|
+
@execution = execution
|
|
11
|
+
@contract = contract
|
|
12
|
+
@reactions = reactions
|
|
13
|
+
@errors = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(event)
|
|
17
|
+
reactions.each do |reaction|
|
|
18
|
+
next unless Matcher.new(reaction, event).match?
|
|
19
|
+
|
|
20
|
+
reaction.action.call(
|
|
21
|
+
event: event,
|
|
22
|
+
contract: contract,
|
|
23
|
+
execution: execution
|
|
24
|
+
)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
@errors << {
|
|
27
|
+
event: event,
|
|
28
|
+
reaction: reaction,
|
|
29
|
+
error: e
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Reactive
|
|
6
|
+
class Matcher
|
|
7
|
+
def initialize(reaction, event)
|
|
8
|
+
@reaction = reaction
|
|
9
|
+
@event = event
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def match?
|
|
13
|
+
return false unless @reaction.event_type == @event.type
|
|
14
|
+
return true unless @reaction.path
|
|
15
|
+
|
|
16
|
+
@reaction.path == @event.path
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Extensions
|
|
5
|
+
module Reactive
|
|
6
|
+
class Reaction
|
|
7
|
+
attr_reader :event_type, :path, :action
|
|
8
|
+
|
|
9
|
+
def initialize(event_type:, path: nil, action:)
|
|
10
|
+
@event_type = event_type.to_sym
|
|
11
|
+
@path = path&.to_s
|
|
12
|
+
@action = action
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Model
|
|
5
|
+
class CompositionNode < Node
|
|
6
|
+
attr_reader :contract_class, :input_mapping
|
|
7
|
+
|
|
8
|
+
def initialize(id:, name:, contract_class:, input_mapping:, metadata: {})
|
|
9
|
+
super(
|
|
10
|
+
id: id,
|
|
11
|
+
kind: :composition,
|
|
12
|
+
name: name,
|
|
13
|
+
path: name,
|
|
14
|
+
dependencies: input_mapping.values,
|
|
15
|
+
metadata: metadata
|
|
16
|
+
)
|
|
17
|
+
@contract_class = contract_class
|
|
18
|
+
@input_mapping = input_mapping.transform_keys(&:to_sym).transform_values(&:to_sym).freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Model
|
|
5
|
+
class ComputeNode < Node
|
|
6
|
+
attr_reader :callable
|
|
7
|
+
|
|
8
|
+
def initialize(id:, name:, dependencies:, callable:, metadata: {})
|
|
9
|
+
super(
|
|
10
|
+
id: id,
|
|
11
|
+
kind: :compute,
|
|
12
|
+
name: name,
|
|
13
|
+
path: name,
|
|
14
|
+
dependencies: dependencies,
|
|
15
|
+
metadata: metadata
|
|
16
|
+
)
|
|
17
|
+
@callable = callable
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Model
|
|
5
|
+
class Graph
|
|
6
|
+
attr_reader :name, :nodes, :metadata
|
|
7
|
+
|
|
8
|
+
def initialize(name:, nodes:, metadata: {})
|
|
9
|
+
@name = name
|
|
10
|
+
@nodes = nodes.freeze
|
|
11
|
+
@metadata = metadata.freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Model
|
|
5
|
+
class InputNode < Node
|
|
6
|
+
def initialize(id:, name:, metadata: {})
|
|
7
|
+
super(id: id, kind: :input, name: name, path: name, metadata: metadata)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def type
|
|
11
|
+
metadata[:type]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def required?
|
|
15
|
+
metadata.fetch(:required, !metadata.key?(:default))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def default?
|
|
19
|
+
metadata.key?(:default)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def default
|
|
23
|
+
metadata[:default]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Model
|
|
5
|
+
class Node
|
|
6
|
+
attr_reader :id, :kind, :name, :path, :dependencies, :metadata
|
|
7
|
+
|
|
8
|
+
def initialize(id:, kind:, name:, path:, dependencies: [], metadata: {})
|
|
9
|
+
@id = id
|
|
10
|
+
@kind = kind
|
|
11
|
+
@name = name.to_sym
|
|
12
|
+
@path = path.to_s
|
|
13
|
+
@dependencies = dependencies.map(&:to_sym).freeze
|
|
14
|
+
@metadata = metadata.freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def source_location
|
|
18
|
+
metadata[:source_location]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Model
|
|
5
|
+
class OutputNode < Node
|
|
6
|
+
attr_reader :source
|
|
7
|
+
|
|
8
|
+
def initialize(id:, name:, source:, metadata: {})
|
|
9
|
+
super(
|
|
10
|
+
id: id,
|
|
11
|
+
kind: :output,
|
|
12
|
+
name: name,
|
|
13
|
+
path: "output.#{name}",
|
|
14
|
+
dependencies: [source],
|
|
15
|
+
metadata: metadata
|
|
16
|
+
)
|
|
17
|
+
@source = source.to_sym
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "model/node"
|
|
4
|
+
require_relative "model/graph"
|
|
5
|
+
require_relative "model/input_node"
|
|
6
|
+
require_relative "model/compute_node"
|
|
7
|
+
require_relative "model/composition_node"
|
|
8
|
+
require_relative "model/output_node"
|
|
9
|
+
|
|
10
|
+
module Igniter
|
|
11
|
+
module Model
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Runtime
|
|
5
|
+
class Cache
|
|
6
|
+
def initialize
|
|
7
|
+
@states = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def fetch(node_name)
|
|
11
|
+
@states[node_name.to_sym]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def write(state)
|
|
15
|
+
current = fetch(state.node.name)
|
|
16
|
+
version = state.version || next_version(current)
|
|
17
|
+
@states[state.node.name] = NodeState.new(
|
|
18
|
+
node: state.node,
|
|
19
|
+
status: state.status,
|
|
20
|
+
value: state.value,
|
|
21
|
+
error: state.error,
|
|
22
|
+
version: version,
|
|
23
|
+
resolved_at: state.resolved_at,
|
|
24
|
+
invalidated_by: state.invalidated_by
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stale!(node, invalidated_by:)
|
|
29
|
+
current = fetch(node.name)
|
|
30
|
+
return unless current
|
|
31
|
+
|
|
32
|
+
@states[node.name] = NodeState.new(
|
|
33
|
+
node: node,
|
|
34
|
+
status: :stale,
|
|
35
|
+
value: current.value,
|
|
36
|
+
error: current.error,
|
|
37
|
+
version: current.version + 1,
|
|
38
|
+
resolved_at: current.resolved_at,
|
|
39
|
+
invalidated_by: invalidated_by
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def values
|
|
44
|
+
@states.values
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
@states.dup
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def next_version(current)
|
|
54
|
+
current ? current.version + 1 : 1
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Runtime
|
|
5
|
+
class Execution
|
|
6
|
+
attr_reader :compiled_graph, :contract_instance, :inputs, :cache, :events, :audit
|
|
7
|
+
|
|
8
|
+
def initialize(compiled_graph:, contract_instance:, inputs:)
|
|
9
|
+
@compiled_graph = compiled_graph
|
|
10
|
+
@contract_instance = contract_instance
|
|
11
|
+
@input_validator = InputValidator.new(compiled_graph)
|
|
12
|
+
@inputs = @input_validator.normalize_initial_inputs(inputs)
|
|
13
|
+
@cache = Cache.new
|
|
14
|
+
@events = Events::Bus.new
|
|
15
|
+
@audit = Extensions::Auditing::Timeline.new(self)
|
|
16
|
+
@events.subscribe(@audit)
|
|
17
|
+
@resolver = Resolver.new(self)
|
|
18
|
+
@invalidator = Invalidator.new(self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resolve_output(name)
|
|
22
|
+
output = compiled_graph.fetch_output(name)
|
|
23
|
+
with_execution_lifecycle([output.source]) do
|
|
24
|
+
state = @resolver.resolve(output.source)
|
|
25
|
+
raise state.error if state.failed?
|
|
26
|
+
|
|
27
|
+
state.value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolve(name)
|
|
32
|
+
@resolver.resolve(name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_all
|
|
36
|
+
output_sources = compiled_graph.outputs.map(&:source)
|
|
37
|
+
|
|
38
|
+
with_execution_lifecycle(output_sources) do
|
|
39
|
+
compiled_graph.outputs.each { |output_node| resolve(output_node.source) }
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update_inputs(new_inputs)
|
|
45
|
+
symbolize_keys(new_inputs).each do |name, value|
|
|
46
|
+
@input_validator.validate_update!(name, value)
|
|
47
|
+
|
|
48
|
+
@inputs[name] = value
|
|
49
|
+
input_node = compiled_graph.fetch_node(name)
|
|
50
|
+
cache.write(NodeState.new(node: input_node, status: :succeeded, value: value, invalidated_by: name))
|
|
51
|
+
@events.emit(:input_updated, node: input_node, status: :succeeded, payload: { value: value })
|
|
52
|
+
@invalidator.invalidate_from(name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def success?
|
|
59
|
+
resolve_all
|
|
60
|
+
!cache.values.any?(&:failed?)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def failed?
|
|
64
|
+
resolve_all
|
|
65
|
+
cache.values.any?(&:failed?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def states
|
|
69
|
+
Extensions::Introspection::RuntimeFormatter.states(self)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def explain_output(name)
|
|
73
|
+
Extensions::Introspection::RuntimeFormatter.explain_output(self, name)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def diagnostics
|
|
77
|
+
Diagnostics::Report.new(self)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def to_h
|
|
81
|
+
{
|
|
82
|
+
graph: compiled_graph.name,
|
|
83
|
+
execution_id: events.execution_id,
|
|
84
|
+
inputs: inputs.dup,
|
|
85
|
+
success: !cache.values.any?(&:failed?),
|
|
86
|
+
failed: cache.values.any?(&:failed?),
|
|
87
|
+
states: states,
|
|
88
|
+
event_count: events.events.size
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def as_json(*)
|
|
93
|
+
to_h.merge(
|
|
94
|
+
events: events.events.map(&:as_json)
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def with_execution_lifecycle(node_names)
|
|
101
|
+
if resolution_required_for_any?(node_names)
|
|
102
|
+
@events.emit(:execution_started, payload: { graph: compiled_graph.name, targets: node_names.map(&:to_sym) })
|
|
103
|
+
begin
|
|
104
|
+
result = yield
|
|
105
|
+
@events.emit(:execution_finished, payload: { graph: compiled_graph.name, targets: node_names.map(&:to_sym) })
|
|
106
|
+
result
|
|
107
|
+
rescue StandardError => e
|
|
108
|
+
@events.emit(
|
|
109
|
+
:execution_failed,
|
|
110
|
+
status: :failed,
|
|
111
|
+
payload: {
|
|
112
|
+
graph: compiled_graph.name,
|
|
113
|
+
targets: node_names.map(&:to_sym),
|
|
114
|
+
error: e.message
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
raise
|
|
118
|
+
end
|
|
119
|
+
else
|
|
120
|
+
yield
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolution_required_for_any?(node_names)
|
|
125
|
+
node_names.any? do |node_name|
|
|
126
|
+
state = cache.fetch(node_name)
|
|
127
|
+
state.nil? || state.stale?
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def symbolize_keys(hash)
|
|
132
|
+
hash.each_with_object({}) { |(key, value), memo| memo[key.to_sym] = value }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
public
|
|
136
|
+
|
|
137
|
+
def fetch_input!(name)
|
|
138
|
+
@input_validator.fetch_value!(name, @inputs)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|