igniter-contracts 0.5.2
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/README.md +239 -0
- data/lib/igniter/contracts/api.rb +92 -0
- data/lib/igniter/contracts/assembly/baseline_pack.rb +141 -0
- data/lib/igniter/contracts/assembly/const_pack.rb +29 -0
- data/lib/igniter/contracts/assembly/dsl_keyword.rb +21 -0
- data/lib/igniter/contracts/assembly/hook_result_policies.rb +47 -0
- data/lib/igniter/contracts/assembly/hook_spec.rb +73 -0
- data/lib/igniter/contracts/assembly/hook_specs.rb +74 -0
- data/lib/igniter/contracts/assembly/kernel.rb +220 -0
- data/lib/igniter/contracts/assembly/node_type.rb +26 -0
- data/lib/igniter/contracts/assembly/ordered_registry.rb +55 -0
- data/lib/igniter/contracts/assembly/pack.rb +13 -0
- data/lib/igniter/contracts/assembly/pack_manifest.rb +131 -0
- data/lib/igniter/contracts/assembly/path_access.rb +76 -0
- data/lib/igniter/contracts/assembly/profile.rb +133 -0
- data/lib/igniter/contracts/assembly/project_pack.rb +42 -0
- data/lib/igniter/contracts/assembly/registry.rb +57 -0
- data/lib/igniter/contracts/assembly/step_result_pack.rb +42 -0
- data/lib/igniter/contracts/assembly.rb +18 -0
- data/lib/igniter/contracts/contract.rb +135 -0
- data/lib/igniter/contracts/contractable.rb +288 -0
- data/lib/igniter/contracts/environment.rb +51 -0
- data/lib/igniter/contracts/errors.rb +47 -0
- data/lib/igniter/contracts/execution/baseline_normalizers.rb +23 -0
- data/lib/igniter/contracts/execution/baseline_runtime.rb +55 -0
- data/lib/igniter/contracts/execution/baseline_validators.rb +113 -0
- data/lib/igniter/contracts/execution/builder.rb +43 -0
- data/lib/igniter/contracts/execution/compilation_report.rb +46 -0
- data/lib/igniter/contracts/execution/compiled_graph.rb +21 -0
- data/lib/igniter/contracts/execution/compiler.rb +66 -0
- data/lib/igniter/contracts/execution/const_runtime.rb +15 -0
- data/lib/igniter/contracts/execution/diagnostics.rb +24 -0
- data/lib/igniter/contracts/execution/diagnostics_report.rb +40 -0
- data/lib/igniter/contracts/execution/diagnostics_section.rb +37 -0
- data/lib/igniter/contracts/execution/effect_invocation.rb +26 -0
- data/lib/igniter/contracts/execution/execution_request.rb +28 -0
- data/lib/igniter/contracts/execution/execution_result.rb +32 -0
- data/lib/igniter/contracts/execution/inline_executor.rb +19 -0
- data/lib/igniter/contracts/execution/mutable_named_values.rb +52 -0
- data/lib/igniter/contracts/execution/named_values.rb +48 -0
- data/lib/igniter/contracts/execution/operation.rb +42 -0
- data/lib/igniter/contracts/execution/runtime.rb +43 -0
- data/lib/igniter/contracts/execution/step_result.rb +51 -0
- data/lib/igniter/contracts/execution/step_result_diagnostics.rb +35 -0
- data/lib/igniter/contracts/execution/step_result_runtime.rb +51 -0
- data/lib/igniter/contracts/execution/step_result_validators.rb +44 -0
- data/lib/igniter/contracts/execution/structured_dump.rb +49 -0
- data/lib/igniter/contracts/execution/validation_finding.rb +28 -0
- data/lib/igniter/contracts/execution/validation_report.rb +46 -0
- data/lib/igniter/contracts/execution.rb +28 -0
- data/lib/igniter/contracts.rb +54 -0
- data/lib/igniter/lang/backend.rb +19 -0
- data/lib/igniter/lang/backends/ruby.rb +42 -0
- data/lib/igniter/lang/diagnostic_payload.rb +174 -0
- data/lib/igniter/lang/metadata_carrier_manifest.rb +112 -0
- data/lib/igniter/lang/metadata_manifest.rb +128 -0
- data/lib/igniter/lang/receipt_payload.rb +152 -0
- data/lib/igniter/lang/schema_compatibility_diagnostic.rb +300 -0
- data/lib/igniter/lang/types.rb +84 -0
- data/lib/igniter/lang/verification_report.rb +226 -0
- data/lib/igniter/lang.rb +27 -0
- data/lib/igniter-contracts.rb +3 -0
- metadata +103 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class Compiler
|
|
7
|
+
class << self
|
|
8
|
+
def compile(profile:, &block)
|
|
9
|
+
compilation_report(profile: profile, &block).to_compiled_graph
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def compilation_report(profile:, &block)
|
|
13
|
+
builder = Builder.build(profile: profile, &block)
|
|
14
|
+
operations = normalize(builder.operations, profile: profile)
|
|
15
|
+
findings = validate(operations, profile: profile)
|
|
16
|
+
validation_report = ValidationReport.new(
|
|
17
|
+
operations: operations,
|
|
18
|
+
findings: findings,
|
|
19
|
+
profile_fingerprint: profile.fingerprint
|
|
20
|
+
)
|
|
21
|
+
compiled_graph = if validation_report.ok?
|
|
22
|
+
CompiledGraph.new(operations: operations,
|
|
23
|
+
profile_fingerprint: profile.fingerprint)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
CompilationReport.new(
|
|
27
|
+
operations: operations,
|
|
28
|
+
validation_report: validation_report,
|
|
29
|
+
compiled_graph: compiled_graph,
|
|
30
|
+
profile_fingerprint: profile.fingerprint
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validation_report(profile:, &block)
|
|
35
|
+
compilation_report(profile: profile, &block).validation_report
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def normalize(operations, profile:)
|
|
41
|
+
hook_spec = Assembly::HookSpecs.fetch(:normalizers)
|
|
42
|
+
|
|
43
|
+
profile.normalizers.each do |entry|
|
|
44
|
+
operations = entry.value.call(operations: operations, profile: profile)
|
|
45
|
+
operations = hook_spec.validate_result!(entry.key, operations)
|
|
46
|
+
end
|
|
47
|
+
operations
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate(operations, profile:)
|
|
51
|
+
hook_spec = Assembly::HookSpecs.fetch(:validators)
|
|
52
|
+
findings = []
|
|
53
|
+
|
|
54
|
+
profile.validators.each do |entry|
|
|
55
|
+
validator_findings = entry.value.call(operations: operations, profile: profile)
|
|
56
|
+
validated_findings = hook_spec.validate_result!(entry.key, validator_findings)
|
|
57
|
+
findings.concat(validated_findings || [])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
findings
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
module Diagnostics
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def build_report(result:, profile:)
|
|
10
|
+
report = DiagnosticsReport.new
|
|
11
|
+
|
|
12
|
+
profile.diagnostics_contributors.each do |entry|
|
|
13
|
+
contributor = entry.value
|
|
14
|
+
next unless contributor.respond_to?(:augment)
|
|
15
|
+
|
|
16
|
+
contributor.augment(report: report, result: result, profile: profile)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
report
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class DiagnosticsReport
|
|
7
|
+
attr_reader :sections
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@sections = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_section(name, value)
|
|
14
|
+
section = DiagnosticsSection.new(name: name, value: value)
|
|
15
|
+
sections[section.name] = section
|
|
16
|
+
section
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def section(name)
|
|
20
|
+
value = section_object(name).value
|
|
21
|
+
value.is_a?(NamedValues) ? value.to_h : value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def section_object(name)
|
|
25
|
+
sections.fetch(name.to_sym)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def section_names
|
|
29
|
+
sections.keys
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_h
|
|
33
|
+
{
|
|
34
|
+
sections: sections.transform_values(&:to_h)
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class DiagnosticsSection
|
|
7
|
+
attr_reader :name, :value
|
|
8
|
+
|
|
9
|
+
def initialize(name:, value:)
|
|
10
|
+
@name = name.to_sym
|
|
11
|
+
@value = normalize_value(value)
|
|
12
|
+
freeze
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{
|
|
17
|
+
name: name,
|
|
18
|
+
value: StructuredDump.dump(value)
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def normalize_value(value)
|
|
25
|
+
case value
|
|
26
|
+
when NamedValues
|
|
27
|
+
value
|
|
28
|
+
when Hash
|
|
29
|
+
NamedValues.new(value)
|
|
30
|
+
else
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class EffectInvocation
|
|
7
|
+
attr_reader :payload, :context, :profile
|
|
8
|
+
|
|
9
|
+
def initialize(payload:, context:, profile:)
|
|
10
|
+
@payload = payload
|
|
11
|
+
@context = context.is_a?(NamedValues) ? context : NamedValues.new(context)
|
|
12
|
+
@profile = profile
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
{
|
|
18
|
+
payload: StructuredDump.dump(payload),
|
|
19
|
+
context: context.to_h,
|
|
20
|
+
profile_fingerprint: profile.fingerprint
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class ExecutionRequest
|
|
7
|
+
attr_reader :compiled_graph, :inputs, :profile, :runtime
|
|
8
|
+
|
|
9
|
+
def initialize(compiled_graph:, inputs:, profile:, runtime:)
|
|
10
|
+
@compiled_graph = compiled_graph
|
|
11
|
+
@inputs = inputs.is_a?(NamedValues) ? inputs : NamedValues.new(inputs)
|
|
12
|
+
@profile = profile
|
|
13
|
+
@runtime = runtime
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
{
|
|
19
|
+
compiled_graph: StructuredDump.dump(compiled_graph),
|
|
20
|
+
inputs: inputs.to_h,
|
|
21
|
+
profile_fingerprint: profile.fingerprint,
|
|
22
|
+
runtime: runtime.name
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class ExecutionResult
|
|
7
|
+
attr_reader :state, :outputs, :profile_fingerprint, :compiled_graph
|
|
8
|
+
|
|
9
|
+
def initialize(state:, outputs:, profile_fingerprint:, compiled_graph:)
|
|
10
|
+
@state = state
|
|
11
|
+
@outputs = outputs
|
|
12
|
+
@profile_fingerprint = profile_fingerprint
|
|
13
|
+
@compiled_graph = compiled_graph
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def output(name)
|
|
18
|
+
outputs.fetch(name.to_sym)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_h
|
|
22
|
+
{
|
|
23
|
+
state: state.to_h,
|
|
24
|
+
outputs: outputs.to_h,
|
|
25
|
+
profile_fingerprint: profile_fingerprint,
|
|
26
|
+
compiled_graph: compiled_graph.to_h
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
module InlineExecutor
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def call(invocation:)
|
|
10
|
+
invocation.runtime.execute(
|
|
11
|
+
invocation.compiled_graph,
|
|
12
|
+
inputs: invocation.inputs,
|
|
13
|
+
profile: invocation.profile
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class MutableNamedValues
|
|
7
|
+
def initialize(values = {})
|
|
8
|
+
normalized_values =
|
|
9
|
+
case values
|
|
10
|
+
when NamedValues
|
|
11
|
+
values.to_h
|
|
12
|
+
when MutableNamedValues
|
|
13
|
+
values.snapshot.to_h
|
|
14
|
+
else
|
|
15
|
+
values
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@values = normalized_values.transform_keys(&:to_sym)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def write(name, value)
|
|
22
|
+
@values[name.to_sym] = value
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def fetch(name)
|
|
27
|
+
@values.fetch(name.to_sym)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def [](name)
|
|
31
|
+
@values[name.to_sym]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key?(name)
|
|
35
|
+
@values.key?(name.to_sym)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def keys
|
|
39
|
+
@values.keys
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def length
|
|
43
|
+
@values.length
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def snapshot
|
|
47
|
+
NamedValues.new(@values)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class NamedValues
|
|
7
|
+
def initialize(values = {})
|
|
8
|
+
normalized_values =
|
|
9
|
+
case values
|
|
10
|
+
when NamedValues
|
|
11
|
+
values.to_h
|
|
12
|
+
when MutableNamedValues
|
|
13
|
+
values.snapshot.to_h
|
|
14
|
+
else
|
|
15
|
+
values
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@values = normalized_values.transform_keys(&:to_sym).freeze
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fetch(name)
|
|
23
|
+
@values.fetch(name.to_sym)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def [](name)
|
|
27
|
+
@values[name.to_sym]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def key?(name)
|
|
31
|
+
@values.key?(name.to_sym)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def keys
|
|
35
|
+
@values.keys
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def length
|
|
39
|
+
@values.length
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
@values.to_h { |key, value| [key, StructuredDump.dump(value)] }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class Operation
|
|
7
|
+
attr_reader :kind, :name, :attributes
|
|
8
|
+
|
|
9
|
+
def initialize(kind:, name:, attributes: {})
|
|
10
|
+
@kind = kind.to_sym
|
|
11
|
+
@name = name.to_sym
|
|
12
|
+
@attributes = attributes.transform_keys(&:to_sym).freeze
|
|
13
|
+
freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def attribute(key)
|
|
17
|
+
attributes.fetch(key.to_sym)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def attribute?(key)
|
|
21
|
+
attributes.key?(key.to_sym)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def with_attributes(updated_attributes)
|
|
25
|
+
self.class.new(kind: kind, name: name, attributes: updated_attributes)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output?
|
|
29
|
+
kind == :output
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_h
|
|
33
|
+
{
|
|
34
|
+
kind: kind,
|
|
35
|
+
name: name,
|
|
36
|
+
attributes: StructuredDump.dump(attributes)
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class Runtime
|
|
7
|
+
class << self
|
|
8
|
+
def execute(compiled_graph, inputs:, profile:)
|
|
9
|
+
validate_profile!(compiled_graph, profile: profile)
|
|
10
|
+
|
|
11
|
+
normalized_inputs = NamedValues.new(inputs)
|
|
12
|
+
state = MutableNamedValues.new
|
|
13
|
+
outputs = MutableNamedValues.new
|
|
14
|
+
|
|
15
|
+
compiled_graph.operations.each do |operation|
|
|
16
|
+
handler = profile.runtime_handler(operation.kind)
|
|
17
|
+
value = handler.call(operation: operation, state: state, outputs: outputs, inputs: normalized_inputs,
|
|
18
|
+
profile: profile)
|
|
19
|
+
state.write(operation.name, value) unless operation.output?
|
|
20
|
+
outputs.write(operation.name, value) if operation.output?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
ExecutionResult.new(
|
|
24
|
+
state: state.snapshot,
|
|
25
|
+
outputs: outputs.snapshot,
|
|
26
|
+
profile_fingerprint: profile.fingerprint,
|
|
27
|
+
compiled_graph: compiled_graph
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def validate_profile!(compiled_graph, profile:)
|
|
34
|
+
return if compiled_graph.profile_fingerprint == profile.fingerprint
|
|
35
|
+
|
|
36
|
+
raise ProfileMismatchError,
|
|
37
|
+
"compiled graph fingerprint #{compiled_graph.profile_fingerprint} does not match profile #{profile.fingerprint}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
class StepResult
|
|
7
|
+
attr_reader :value, :failure, :metadata
|
|
8
|
+
|
|
9
|
+
def self.success(value, metadata: {})
|
|
10
|
+
new(value: value, failure: nil, metadata: metadata)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.failure(code:, message:, details: {}, metadata: {})
|
|
14
|
+
new(
|
|
15
|
+
value: nil,
|
|
16
|
+
failure: {
|
|
17
|
+
code: code.to_sym,
|
|
18
|
+
message: message,
|
|
19
|
+
details: details
|
|
20
|
+
},
|
|
21
|
+
metadata: metadata
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(value:, failure:, metadata: {})
|
|
26
|
+
@value = value
|
|
27
|
+
@failure = failure&.transform_keys(&:to_sym)
|
|
28
|
+
@metadata = metadata.transform_keys(&:to_sym).freeze
|
|
29
|
+
freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def success?
|
|
33
|
+
failure.nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failure?
|
|
37
|
+
!success?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_h
|
|
41
|
+
{
|
|
42
|
+
success: success?,
|
|
43
|
+
value: StructuredDump.dump(value),
|
|
44
|
+
failure: StructuredDump.dump(failure),
|
|
45
|
+
metadata: StructuredDump.dump(metadata)
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
module StepResultDiagnostics
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def augment(report:, result:, profile:) # rubocop:disable Lint/UnusedMethodArgument
|
|
10
|
+
report.add_section(:step_trace, step_trace(result))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def step_trace(result)
|
|
14
|
+
result.compiled_graph.operations.filter_map do |operation|
|
|
15
|
+
next unless operation.kind == :step
|
|
16
|
+
|
|
17
|
+
step_result = result.state[operation.name]
|
|
18
|
+
next unless step_result.is_a?(StepResult)
|
|
19
|
+
|
|
20
|
+
trace_entry(operation, step_result)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def trace_entry(operation, step_result)
|
|
25
|
+
{
|
|
26
|
+
name: operation.name,
|
|
27
|
+
status: step_result.success? ? :success : :failed,
|
|
28
|
+
dependencies: Array(operation.attributes[:depends_on]).map(&:to_sym),
|
|
29
|
+
failure: StructuredDump.dump(step_result.failure)
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
module StepResultRuntime
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def handle_step(operation:, state:, **)
|
|
10
|
+
failed_dependency = failed_step_dependency(operation, state: state)
|
|
11
|
+
return halted_dependency_result(operation, failed_dependency) if failed_dependency
|
|
12
|
+
|
|
13
|
+
callable = operation.attributes[:callable]
|
|
14
|
+
normalize_step_result(callable.call(**step_dependency_values(operation, state: state)))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def failed_step_dependency(operation, state:)
|
|
18
|
+
Array(operation.attributes[:depends_on]).filter_map do |dependency|
|
|
19
|
+
dependency_name = dependency.to_sym
|
|
20
|
+
value = state.fetch(dependency_name)
|
|
21
|
+
|
|
22
|
+
{ name: dependency_name, result: value } if value.is_a?(StepResult) && value.failure?
|
|
23
|
+
end.first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def halted_dependency_result(operation, dependency)
|
|
27
|
+
StepResult.failure(
|
|
28
|
+
code: :halted_dependency,
|
|
29
|
+
message: "step #{operation.name} halted because dependency #{dependency.fetch(:name)} failed",
|
|
30
|
+
details: {
|
|
31
|
+
dependency: dependency.fetch(:name),
|
|
32
|
+
failure: dependency.fetch(:result).failure
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def step_dependency_values(operation, state:)
|
|
38
|
+
Array(operation.attributes[:depends_on]).each_with_object({}) do |dependency, memo|
|
|
39
|
+
dependency_name = dependency.to_sym
|
|
40
|
+
value = state.fetch(dependency_name)
|
|
41
|
+
memo[dependency_name] = value.is_a?(StepResult) ? value.value : value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def normalize_step_result(value)
|
|
46
|
+
value.is_a?(StepResult) ? value : StepResult.success(value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Execution
|
|
6
|
+
module StepResultValidators
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def validate_step_dependencies(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
10
|
+
available = operations.reject(&:output?).map(&:name)
|
|
11
|
+
missing = step_operations(operations)
|
|
12
|
+
.flat_map { |operation| Array(operation.attributes[:depends_on]) }
|
|
13
|
+
.map(&:to_sym)
|
|
14
|
+
.reject { |name| available.include?(name) }
|
|
15
|
+
.uniq
|
|
16
|
+
return [] if missing.empty?
|
|
17
|
+
|
|
18
|
+
[ValidationFinding.new(
|
|
19
|
+
code: :missing_step_dependencies,
|
|
20
|
+
message: "step dependencies are not defined: #{missing.map(&:to_s).join(", ")}",
|
|
21
|
+
subjects: missing
|
|
22
|
+
)]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate_step_callables(operations:, profile: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
26
|
+
missing = step_operations(operations)
|
|
27
|
+
.reject { |operation| operation.attributes[:callable].respond_to?(:call) }
|
|
28
|
+
.map(&:name)
|
|
29
|
+
return [] if missing.empty?
|
|
30
|
+
|
|
31
|
+
[ValidationFinding.new(
|
|
32
|
+
code: :missing_step_callable,
|
|
33
|
+
message: "step nodes require a callable: #{missing.map(&:to_s).join(", ")}",
|
|
34
|
+
subjects: missing
|
|
35
|
+
)]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def step_operations(operations)
|
|
39
|
+
operations.select { |operation| operation.kind == :step }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|