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,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Assembly
|
|
6
|
+
module HookSpecs
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
REGISTRY_SPECS = {
|
|
10
|
+
dsl_keywords: HookSpec.new(
|
|
11
|
+
registry: :dsl_keywords,
|
|
12
|
+
method_name: :call,
|
|
13
|
+
required_keywords: %i[builder],
|
|
14
|
+
role: :dsl_keyword,
|
|
15
|
+
return_policy: :opaque
|
|
16
|
+
),
|
|
17
|
+
normalizers: HookSpec.new(
|
|
18
|
+
registry: :normalizers,
|
|
19
|
+
method_name: :call,
|
|
20
|
+
required_keywords: %i[operations profile],
|
|
21
|
+
role: :graph_transformer,
|
|
22
|
+
return_policy: :operations_array,
|
|
23
|
+
result_validator: HookResultPolicies.method(:operations_array)
|
|
24
|
+
),
|
|
25
|
+
validators: HookSpec.new(
|
|
26
|
+
registry: :validators,
|
|
27
|
+
method_name: :call,
|
|
28
|
+
required_keywords: %i[operations profile],
|
|
29
|
+
role: :validator,
|
|
30
|
+
return_policy: :validation_findings,
|
|
31
|
+
result_validator: HookResultPolicies.method(:validation_findings)
|
|
32
|
+
),
|
|
33
|
+
runtime_handlers: HookSpec.new(
|
|
34
|
+
registry: :runtime_handlers,
|
|
35
|
+
method_name: :call,
|
|
36
|
+
required_keywords: %i[operation state outputs inputs profile],
|
|
37
|
+
role: :runtime_handler,
|
|
38
|
+
return_policy: :value
|
|
39
|
+
),
|
|
40
|
+
diagnostics_contributors: HookSpec.new(
|
|
41
|
+
registry: :diagnostics_contributors,
|
|
42
|
+
method_name: :augment,
|
|
43
|
+
required_keywords: %i[report result profile],
|
|
44
|
+
role: :diagnostics_contributor,
|
|
45
|
+
return_policy: :ignored
|
|
46
|
+
),
|
|
47
|
+
effects: HookSpec.new(
|
|
48
|
+
registry: :effects,
|
|
49
|
+
method_name: :call,
|
|
50
|
+
required_keywords: %i[invocation],
|
|
51
|
+
role: :effect_adapter,
|
|
52
|
+
return_policy: :opaque
|
|
53
|
+
),
|
|
54
|
+
executors: HookSpec.new(
|
|
55
|
+
registry: :executors,
|
|
56
|
+
method_name: :call,
|
|
57
|
+
required_keywords: %i[invocation],
|
|
58
|
+
role: :executor,
|
|
59
|
+
return_policy: :execution_result,
|
|
60
|
+
result_validator: HookResultPolicies.method(:execution_result)
|
|
61
|
+
)
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
def fetch(registry_name)
|
|
65
|
+
REGISTRY_SPECS.fetch(registry_name.to_sym)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def registry_names
|
|
69
|
+
REGISTRY_SPECS.keys
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Assembly
|
|
6
|
+
class Kernel
|
|
7
|
+
attr_reader :nodes,
|
|
8
|
+
:dsl_keywords,
|
|
9
|
+
:validators,
|
|
10
|
+
:normalizers,
|
|
11
|
+
:runtime_handlers,
|
|
12
|
+
:diagnostics_contributors,
|
|
13
|
+
:pack_manifests,
|
|
14
|
+
:effects,
|
|
15
|
+
:executors
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
nodes: Registry.new(name: :nodes),
|
|
19
|
+
dsl_keywords: Registry.new(name: :dsl_keywords),
|
|
20
|
+
validators: OrderedRegistry.new(name: :validators),
|
|
21
|
+
normalizers: OrderedRegistry.new(name: :normalizers),
|
|
22
|
+
runtime_handlers: Registry.new(name: :runtime_handlers),
|
|
23
|
+
diagnostics_contributors: OrderedRegistry.new(name: :diagnostics_contributors),
|
|
24
|
+
effects: Registry.new(name: :effects),
|
|
25
|
+
executors: Registry.new(name: :executors)
|
|
26
|
+
)
|
|
27
|
+
@nodes = nodes
|
|
28
|
+
@dsl_keywords = dsl_keywords
|
|
29
|
+
@validators = validators
|
|
30
|
+
@normalizers = normalizers
|
|
31
|
+
@runtime_handlers = runtime_handlers
|
|
32
|
+
@diagnostics_contributors = diagnostics_contributors
|
|
33
|
+
@pack_manifests = []
|
|
34
|
+
@effects = effects
|
|
35
|
+
@executors = executors
|
|
36
|
+
@finalized = false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def install(pack, installing: [])
|
|
40
|
+
raise FrozenKernelError, "kernel already finalized" if finalized?
|
|
41
|
+
|
|
42
|
+
manifest = pack.respond_to?(:manifest) ? pack.manifest : nil
|
|
43
|
+
pack_name = manifest&.name
|
|
44
|
+
|
|
45
|
+
return self if pack_name && pack_installed?(pack_name)
|
|
46
|
+
|
|
47
|
+
if pack_name && installing.include?(pack_name)
|
|
48
|
+
cycle = (installing + [pack_name]).join(" -> ")
|
|
49
|
+
raise CircularPackDependencyError, "circular pack dependency detected: #{cycle}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
install_required_packs(manifest, installing: [*installing, pack_name].compact) if manifest
|
|
53
|
+
register_pack_manifest(pack, manifest: manifest)
|
|
54
|
+
pack.install_into(self)
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def finalize
|
|
59
|
+
validate_completeness!
|
|
60
|
+
validate_hook_implementations!
|
|
61
|
+
freeze_registries!
|
|
62
|
+
@finalized = true
|
|
63
|
+
Profile.build_from(self)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def finalized?
|
|
67
|
+
@finalized
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
REGISTRY_LABELS = {
|
|
73
|
+
dsl_keywords: "DSL keywords",
|
|
74
|
+
validators: "validators",
|
|
75
|
+
normalizers: "normalizers",
|
|
76
|
+
runtime_handlers: "runtime handlers",
|
|
77
|
+
diagnostics_contributors: "diagnostics contributors",
|
|
78
|
+
effects: "effects",
|
|
79
|
+
executors: "executors"
|
|
80
|
+
}.freeze
|
|
81
|
+
|
|
82
|
+
def register_pack_manifest(pack, manifest: nil)
|
|
83
|
+
manifest ||= pack.respond_to?(:manifest) ? pack.manifest : nil
|
|
84
|
+
return unless manifest
|
|
85
|
+
|
|
86
|
+
pack_manifests << manifest
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def pack_installed?(name)
|
|
90
|
+
pack_manifests.any? { |manifest| manifest.name == name.to_sym }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def install_required_packs(manifest, installing:)
|
|
94
|
+
manifest.requires_packs.each do |dependency|
|
|
95
|
+
next if pack_installed?(dependency.name)
|
|
96
|
+
|
|
97
|
+
unless dependency.pack
|
|
98
|
+
raise UnknownPackDependencyError,
|
|
99
|
+
"pack #{manifest.name} requires pack #{dependency.name}, but no pack implementation was provided"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
install(dependency.pack, installing: installing)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate_completeness!
|
|
107
|
+
manifest_contracts = pack_manifests.flat_map(&:node_contracts)
|
|
108
|
+
undeclared_nodes = nodes.to_h.values.reject do |node|
|
|
109
|
+
manifest_contracts.any? do |contract|
|
|
110
|
+
contract.kind == node.kind
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
undeclared_contracts = undeclared_nodes.map do |node|
|
|
114
|
+
PackManifest.node(
|
|
115
|
+
node.kind,
|
|
116
|
+
requires_dsl: node.requires_dsl?,
|
|
117
|
+
requires_runtime: node.requires_runtime?
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
contracts = manifest_contracts + undeclared_contracts
|
|
121
|
+
|
|
122
|
+
missing_node_definitions = manifest_contracts.map(&:kind).reject { |kind| nodes.registered?(kind) }
|
|
123
|
+
missing_dsl = contracts.select(&:requires_dsl).map(&:kind).reject { |kind| dsl_keywords.registered?(kind) }
|
|
124
|
+
missing_runtime = contracts.select(&:requires_runtime).map(&:kind).reject do |kind|
|
|
125
|
+
runtime_handlers.registered?(kind)
|
|
126
|
+
end
|
|
127
|
+
missing_registry_contracts = collect_missing_registry_contracts
|
|
128
|
+
return if missing_node_definitions.empty? &&
|
|
129
|
+
missing_dsl.empty? &&
|
|
130
|
+
missing_runtime.empty? &&
|
|
131
|
+
missing_registry_contracts.empty?
|
|
132
|
+
|
|
133
|
+
parts = []
|
|
134
|
+
parts << "missing node definitions for: #{missing_node_definitions.map(&:to_s).join(", ")}" unless missing_node_definitions.empty?
|
|
135
|
+
parts << "missing DSL keywords for: #{missing_dsl.map(&:to_s).join(", ")}" unless missing_dsl.empty?
|
|
136
|
+
parts << "missing runtime handlers for: #{missing_runtime.map(&:to_s).join(", ")}" unless missing_runtime.empty?
|
|
137
|
+
parts.concat(missing_registry_contracts)
|
|
138
|
+
|
|
139
|
+
raise IncompletePackError, parts.join("; ")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def validate_hook_implementations!
|
|
143
|
+
HookSpecs.registry_names.each do |registry_name|
|
|
144
|
+
hook_spec = HookSpecs.fetch(registry_name)
|
|
145
|
+
|
|
146
|
+
each_registry_entry(registry_name) do |key, implementation|
|
|
147
|
+
hook_spec.validate!(key, implementation)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def collect_missing_registry_contracts
|
|
153
|
+
pack_manifests
|
|
154
|
+
.flat_map(&:registry_contracts)
|
|
155
|
+
.group_by(&:registry)
|
|
156
|
+
.filter_map do |registry_name, contracts|
|
|
157
|
+
registry = registry_for(registry_name)
|
|
158
|
+
next if registry.nil?
|
|
159
|
+
|
|
160
|
+
missing = contracts.map(&:key).reject { |key| registry.registered?(key) }
|
|
161
|
+
next if missing.empty?
|
|
162
|
+
|
|
163
|
+
missing_registry_message(registry_name, missing)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def missing_registry_message(registry_name, missing)
|
|
168
|
+
label = REGISTRY_LABELS.fetch(registry_name, registry_name.to_s.tr("_", " "))
|
|
169
|
+
"missing #{label} for: #{missing.map(&:to_s).join(", ")}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def registry_for(name)
|
|
173
|
+
case name.to_sym
|
|
174
|
+
when :dsl_keywords
|
|
175
|
+
dsl_keywords
|
|
176
|
+
when :validators
|
|
177
|
+
validators
|
|
178
|
+
when :normalizers
|
|
179
|
+
normalizers
|
|
180
|
+
when :runtime_handlers
|
|
181
|
+
runtime_handlers
|
|
182
|
+
when :diagnostics_contributors
|
|
183
|
+
diagnostics_contributors
|
|
184
|
+
when :effects
|
|
185
|
+
effects
|
|
186
|
+
when :executors
|
|
187
|
+
executors
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def each_registry_entry(registry_name, &block)
|
|
192
|
+
registry = registry_for(registry_name)
|
|
193
|
+
return unless registry
|
|
194
|
+
|
|
195
|
+
case registry
|
|
196
|
+
when Registry
|
|
197
|
+
registry.to_h.each(&block)
|
|
198
|
+
when OrderedRegistry
|
|
199
|
+
registry.entries.each do |entry|
|
|
200
|
+
yield(entry.key, entry.value)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def freeze_registries!
|
|
206
|
+
[
|
|
207
|
+
nodes,
|
|
208
|
+
dsl_keywords,
|
|
209
|
+
validators,
|
|
210
|
+
normalizers,
|
|
211
|
+
runtime_handlers,
|
|
212
|
+
diagnostics_contributors,
|
|
213
|
+
effects,
|
|
214
|
+
executors
|
|
215
|
+
].each(&:freeze!)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Assembly
|
|
6
|
+
NodeType = Struct.new(:kind, :metadata, keyword_init: true) do
|
|
7
|
+
def initialize(kind:, metadata: {})
|
|
8
|
+
normalized_metadata = {
|
|
9
|
+
requires_dsl: true,
|
|
10
|
+
requires_runtime: true
|
|
11
|
+
}.merge(metadata).freeze
|
|
12
|
+
|
|
13
|
+
super(kind: kind.to_sym, metadata: normalized_metadata)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def requires_dsl?
|
|
17
|
+
metadata.fetch(:requires_dsl, true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def requires_runtime?
|
|
21
|
+
metadata.fetch(:requires_runtime, true)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Assembly
|
|
6
|
+
class OrderedRegistry
|
|
7
|
+
Entry = Struct.new(:key, :value, keyword_init: true)
|
|
8
|
+
|
|
9
|
+
def initialize(name:)
|
|
10
|
+
@name = name
|
|
11
|
+
@entries = []
|
|
12
|
+
@keys = {}
|
|
13
|
+
@frozen = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(key, value)
|
|
17
|
+
raise FrozenRegistryError, "#{@name} is frozen" if frozen?
|
|
18
|
+
|
|
19
|
+
normalized_key = normalize_key(key)
|
|
20
|
+
raise DuplicateRegistrationError, "#{@name} already has #{normalized_key}" if @keys.key?(normalized_key)
|
|
21
|
+
|
|
22
|
+
entry = Entry.new(key: normalized_key, value: value)
|
|
23
|
+
@entries << entry
|
|
24
|
+
@keys[normalized_key] = true
|
|
25
|
+
entry
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def registered?(key)
|
|
29
|
+
@keys.key?(normalize_key(key))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def entries
|
|
33
|
+
@entries.dup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def freeze!
|
|
37
|
+
@frozen = true
|
|
38
|
+
@entries.freeze
|
|
39
|
+
@keys.freeze
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def frozen?
|
|
44
|
+
@frozen
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def normalize_key(key)
|
|
50
|
+
key.to_sym
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Assembly
|
|
6
|
+
class PackManifest
|
|
7
|
+
RegistryContract = Struct.new(:registry, :key, keyword_init: true) do
|
|
8
|
+
def initialize(registry:, key:)
|
|
9
|
+
super(
|
|
10
|
+
registry: registry.to_sym,
|
|
11
|
+
key: key.to_sym
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
NodeContract = Struct.new(:kind, :requires_dsl, :requires_runtime, keyword_init: true) do
|
|
17
|
+
def initialize(kind:, requires_dsl: true, requires_runtime: true)
|
|
18
|
+
super(
|
|
19
|
+
kind: kind.to_sym,
|
|
20
|
+
requires_dsl: requires_dsl,
|
|
21
|
+
requires_runtime: requires_runtime
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
PackDependency = Struct.new(:name, :pack, keyword_init: true) do
|
|
27
|
+
def initialize(name:, pack: nil)
|
|
28
|
+
super(
|
|
29
|
+
name: name.to_sym,
|
|
30
|
+
pack: pack
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def node(kind, requires_dsl: true, requires_runtime: true)
|
|
37
|
+
NodeContract.new(
|
|
38
|
+
kind: kind,
|
|
39
|
+
requires_dsl: requires_dsl,
|
|
40
|
+
requires_runtime: requires_runtime
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def registry(registry, key)
|
|
45
|
+
RegistryContract.new(registry: registry, key: key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def dsl_keyword(key)
|
|
49
|
+
registry(:dsl_keywords, key)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def runtime_handler(key)
|
|
53
|
+
registry(:runtime_handlers, key)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validator(key)
|
|
57
|
+
registry(:validators, key)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def normalizer(key)
|
|
61
|
+
registry(:normalizers, key)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def diagnostic(key)
|
|
65
|
+
registry(:diagnostics_contributors, key)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def effect(key)
|
|
69
|
+
registry(:effects, key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def executor(key)
|
|
73
|
+
registry(:executors, key)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def pack_dependency(pack_or_name, pack: nil)
|
|
77
|
+
return PackDependency.new(name: pack_or_name, pack: pack) if pack
|
|
78
|
+
|
|
79
|
+
if pack_or_name.respond_to?(:manifest)
|
|
80
|
+
manifest = pack_or_name.manifest
|
|
81
|
+
return PackDependency.new(name: manifest.name, pack: pack_or_name)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
PackDependency.new(name: pack_or_name)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
attr_reader :name, :node_contracts, :registry_contracts, :metadata,
|
|
89
|
+
:requires_packs, :provides_capabilities, :requires_capabilities
|
|
90
|
+
|
|
91
|
+
def initialize(name:, node_contracts: [], registry_contracts: [], diagnostics: [], metadata: {},
|
|
92
|
+
requires_packs: [], provides_capabilities: [], requires_capabilities: [])
|
|
93
|
+
@name = name.to_sym
|
|
94
|
+
@node_contracts = node_contracts.freeze
|
|
95
|
+
@registry_contracts = (
|
|
96
|
+
registry_contracts +
|
|
97
|
+
diagnostics.map { |key| self.class.diagnostic(key) }
|
|
98
|
+
).uniq.freeze
|
|
99
|
+
@metadata = metadata.freeze
|
|
100
|
+
@requires_packs = normalize_pack_dependencies(requires_packs)
|
|
101
|
+
@provides_capabilities = normalize_capabilities(provides_capabilities)
|
|
102
|
+
@requires_capabilities = normalize_capabilities(requires_capabilities)
|
|
103
|
+
freeze
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def declared_keys_for(registry)
|
|
107
|
+
registry_contracts
|
|
108
|
+
.select { |contract| contract.registry == registry.to_sym }
|
|
109
|
+
.map(&:key)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def diagnostics
|
|
113
|
+
declared_keys_for(:diagnostics_contributors)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def normalize_pack_dependencies(dependencies)
|
|
119
|
+
Array(dependencies)
|
|
120
|
+
.map { |entry| entry.is_a?(PackDependency) ? entry : self.class.pack_dependency(entry) }
|
|
121
|
+
.uniq(&:name)
|
|
122
|
+
.freeze
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def normalize_capabilities(capabilities)
|
|
126
|
+
Array(capabilities).flatten.compact.map(&:to_sym).uniq.freeze
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Igniter
|
|
4
|
+
module Contracts
|
|
5
|
+
module Assembly
|
|
6
|
+
module PathAccess
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
NO_DEFAULT = Object.new.freeze
|
|
10
|
+
|
|
11
|
+
def normalize_path(keyword_name:, key: nil, dig: nil)
|
|
12
|
+
raise ArgumentError, "#{keyword_name} accepts either key: or dig:, not both" if !key.nil? && !dig.nil?
|
|
13
|
+
|
|
14
|
+
raw_path =
|
|
15
|
+
if !key.nil?
|
|
16
|
+
[key]
|
|
17
|
+
elsif !dig.nil?
|
|
18
|
+
Array(dig)
|
|
19
|
+
else
|
|
20
|
+
raise ArgumentError, "#{keyword_name} requires key: or dig:"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
raise ArgumentError, "#{keyword_name} dig: path cannot be empty" if raw_path.empty?
|
|
24
|
+
|
|
25
|
+
raw_path.map { |segment| normalize_segment(segment) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def fetch_path(source, path, source_name:, keyword_name:, default: NO_DEFAULT)
|
|
29
|
+
current = source
|
|
30
|
+
|
|
31
|
+
path.each do |segment|
|
|
32
|
+
if segment_present?(current, segment)
|
|
33
|
+
current = fetch_segment(current, segment)
|
|
34
|
+
else
|
|
35
|
+
return default unless default.equal?(NO_DEFAULT)
|
|
36
|
+
|
|
37
|
+
raise KeyError, "#{keyword_name} path #{format_path(path)} not present in #{source_name}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
current
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def normalize_segment(segment)
|
|
45
|
+
return segment if segment.is_a?(Integer)
|
|
46
|
+
|
|
47
|
+
segment.to_sym
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def segment_present?(value, segment)
|
|
51
|
+
if value.respond_to?(:key?)
|
|
52
|
+
value.key?(segment) ||
|
|
53
|
+
(segment.is_a?(Symbol) && value.key?(segment.to_s)) ||
|
|
54
|
+
(segment.is_a?(String) && value.key?(segment.to_sym))
|
|
55
|
+
elsif value.is_a?(Array) && segment.is_a?(Integer)
|
|
56
|
+
segment >= 0 && segment < value.length
|
|
57
|
+
else
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fetch_segment(value, segment)
|
|
63
|
+
return value.fetch(segment) if value.respond_to?(:key?) && value.key?(segment)
|
|
64
|
+
return value.fetch(segment.to_s) if value.respond_to?(:key?) && segment.is_a?(Symbol) && value.key?(segment.to_s)
|
|
65
|
+
return value.fetch(segment.to_sym) if value.respond_to?(:key?) && segment.is_a?(String) && value.key?(segment.to_sym)
|
|
66
|
+
|
|
67
|
+
value.fetch(segment)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def format_path(path)
|
|
71
|
+
path.map(&:inspect).join(" -> ")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|