Module: Igniter::Extensions::Contracts::CapabilitiesPack

Defined in:
lib/igniter/extensions/contracts/capabilities_pack.rb

Class Method Summary collapse

Class Method Details

.capabilities_for(compiled_graph, node_name) ⇒ Object



52
53
54
55
56
57
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 52

def capabilities_for(compiled_graph, node_name)
  operation = compiled_graph.operations.find { |entry| entry.name == node_name.to_sym }
  return [] unless operation

  capabilities_for_operation(operation)
end

.capabilities_for_operation(operation) ⇒ Object



89
90
91
92
93
94
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 89

def capabilities_for_operation(operation)
  capabilities = Array(operation.attributes[:capabilities]).map(&:to_sym)
  callable = operation.attributes[:callable]
  capabilities.concat(Array(callable.declared_capabilities).map(&:to_sym)) if callable.respond_to?(:declared_capabilities)
  capabilities.uniq
end

.check!(compiled_graph, policy:, profile: nil) ⇒ Object



82
83
84
85
86
87
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 82

def check!(compiled_graph, policy:, profile: nil)
  result = report(compiled_graph, profile: profile, policy: policy)
  raise Capabilities::CapabilityViolationError.new(nil, report: result) if result.invalid?

  result
end

.declare(*capabilities, callable: nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


26
27
28
29
30
31
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 26

def declare(*capabilities, callable: nil, &block)
  target = callable || block
  raise ArgumentError, "capability declaration requires a callable or block" unless target

  Capabilities::Declaration.new(callable: target, capabilities: capabilities)
end

.install_into(kernel) ⇒ Object



22
23
24
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 22

def install_into(kernel)
  kernel
end

.manifestObject



15
16
17
18
19
20
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 15

def manifest
  Igniter::Contracts::PackManifest.new(
    name: :extensions_capabilities,
    metadata: { category: :validation }
  )
end

.maybe_warn_about_undeclared(policy, undeclared_nodes) ⇒ Object



137
138
139
140
141
142
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 137

def maybe_warn_about_undeclared(policy, undeclared_nodes)
  return unless policy&.on_undeclared == :warn
  return if undeclared_nodes.empty?

  Warning.warn("WARNING: undeclared capabilities for nodes: #{undeclared_nodes.join(", ")}\n")
end

.policy(denied: [], required: [], on_undeclared: :ignore) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 37

def policy(denied: [], required: [], on_undeclared: :ignore)
  Capabilities::Policy.new(
    denied: denied,
    required: required,
    on_undeclared: on_undeclared
  )
end

.profile_capabilities(profile) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 59

def profile_capabilities(profile)
  profile.pack_manifests
         .flat_map do |manifest_entry|
           manifest_entry.provides_capabilities.empty? ? Array(manifest_entry.[:capabilities]) : manifest_entry.provides_capabilities
         end
         .map(&:to_sym)
         .uniq
end

.pure(callable: nil, &block) ⇒ Object



33
34
35
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 33

def pure(callable: nil, &block)
  declare(:pure, callable: callable, &block)
end

.report(compiled_graph, profile: nil, policy: nil) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 68

def report(compiled_graph, profile: nil, policy: nil)
  requirements = required_capabilities(compiled_graph)
  undeclared_nodes = compiled_graph.operations.reject(&:output?).map(&:name) - requirements.keys
  violations = violations_for(requirements, policy: policy, undeclared_nodes: undeclared_nodes)
  maybe_warn_about_undeclared(policy, undeclared_nodes)

  Capabilities::Report.new(
    required_capabilities: requirements,
    profile_capabilities: profile ? profile_capabilities(profile) : [],
    violations: violations,
    undeclared_nodes: undeclared_nodes
  )
end

.required_capabilities(compiled_graph) ⇒ Object



45
46
47
48
49
50
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 45

def required_capabilities(compiled_graph)
  compiled_graph.operations.reject(&:output?).each_with_object({}) do |operation, memo|
    capabilities = capabilities_for_operation(operation)
    memo[operation.name] = capabilities unless capabilities.empty?
  end
end

.violations_for(requirements, policy:, undeclared_nodes:) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/igniter/extensions/contracts/capabilities_pack.rb', line 96

def violations_for(requirements, policy:, undeclared_nodes:)
  return [] unless policy

  violations = []

  requirements.each do |node_name, capabilities|
    denied = capabilities & policy.denied
    if denied.any?
      violations << Capabilities::Violation.new(
        kind: :denied_capability,
        node_name: node_name,
        capabilities: denied,
        message: "node #{node_name} uses denied capabilities: #{denied.join(", ")}"
      )
    end

    missing = policy.required - capabilities
    next unless missing.any?

    violations << Capabilities::Violation.new(
      kind: :missing_required_capability,
      node_name: node_name,
      capabilities: missing,
      message: "node #{node_name} is missing required capabilities: #{missing.join(", ")}"
    )
  end

  if policy.on_undeclared == :error
    undeclared_nodes.each do |node_name|
      violations << Capabilities::Violation.new(
        kind: :undeclared_capabilities,
        node_name: node_name,
        capabilities: [],
        message: "node #{node_name} does not declare capabilities"
      )
    end
  end

  violations
end