Module: Hecks::Runtime::Registry::Verification
- Included in:
- Hecks::Runtime::Registry
- Defined in:
- lib/hecks/runtime/registry/verification.rb
Overview
The wiring gate: every bind names a declared aggregate, every
adapter satisfies the verb its port declares, every world setting is
a field the adapter admits, and the default adapter is usable at
all. Included into Registry — verify! is what a boot calls after
loading, and the smaller checks are also called piecemeal by the
repository factory.
Constant Summary collapse
- PER_AGGREGATE_PORTS =
THE NINE SINGLETON PORTS' OWN GAP —
persistence,projectionandloadingare per-aggregate bindings, checked above through every realbinda hexagon declares; a singleton port (clock,authorization, …) is never bound to an aggregate at all, so nothing above ever resolves one and nothing above ever rancheck_answersagainst it. Each one's ownPorts::*.adapteralready refuses zero or multiple implementations, live, at first dispatch — that stays exactly as-is here (0 or 2+ is ambiguity, not a method-contract question, and asserting every declared port MUST have exactly one adapter would wrongly refuse a boot that simply never wires a port it doesn't use). This only ever tightens the ONE case those checks don't cover: exactly one adapter, wired, missing a methodanswersnames. %w[persistence projection loading].freeze
Instance Method Summary collapse
- #adapter_class(name) ⇒ Object
-
#check_answers(port, adapter_name) ⇒ Object
THE METHOD CONTRACT A
.portFILE'Sverb/signalNEVER CARRIED — an adapter can name the right port, satisfy the right verb, and admit every.worldsettingcheck_settingschecks, and still be missing the one method a live dispatch will actually call. - #check_settings(bind, settings) ⇒ Object
- #check_verb(bind) ⇒ Object
- #port_for(bind) ⇒ Object
- #verify! ⇒ Object
- #verify_default_adapter! ⇒ Object
- #verify_singleton_port_answers! ⇒ Object
Instance Method Details
#adapter_class(name) ⇒ Object
151 152 153 154 155 156 |
# File 'lib/hecks/runtime/registry/verification.rb', line 151 def adapter_class(name) Adapters.const_get(name) rescue NameError raise WiringError, "no Ruby adapter implementation for #{name.inspect} " \ "(expected Hecks::Adapters::#{name})" end |
#check_answers(port, adapter_name) ⇒ Object
THE METHOD CONTRACT A .port FILE'S verb/signal NEVER
CARRIED — an adapter can name the right port, satisfy the right
verb, and admit every .world setting check_settings checks,
and still be missing the one method a live dispatch will
actually call. answers is optional per port (an empty list is
today's pre-existing behavior, unchecked), so this only ever
tightens a port that opted in.
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/hecks/runtime/registry/verification.rb', line 87 def check_answers(port, adapter_name) answers = Array(port.answers) return if answers.empty? klass = adapter_class(adapter_name) missing = answers.reject { |method_name| klass.respond_to?(method_name) } return if missing.empty? raise WiringError, "#{adapter_name} declares the #{port.name} port but does not respond to " \ "#{missing.map(&:inspect).join(', ')} — #{port.name}.port declares answers " \ "#{answers.map(&:inspect).join(', ')}" end |
#check_settings(bind, settings) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/hecks/runtime/registry/verification.rb', line 129 def check_settings(bind, settings) adapter = @adapters[bind.adapter] return unless adapter declared = settings.keys - [:adapter] unknown = declared.reject { |field| adapter.declares?(field) } return if unknown.empty? raise WiringError, "#{bind.adapter} does not declare #{unknown.map(&:inspect).join(', ')} — " \ "it declares #{adapter.all_fields.map(&:inspect).join(', ')}. " \ "Add the field to the adapter, or remove it from the world." end |
#check_verb(bind) ⇒ Object
70 71 72 73 74 75 76 77 78 |
# File 'lib/hecks/runtime/registry/verification.rb', line 70 def check_verb(bind) port = port_for(bind) check_answers(port, bind.adapter) return if port.verb.to_s == bind.verb.to_s raise WiringError, "#{bind.adapter} implements the #{port.name} port (verb #{port.verb}) " \ "and cannot satisfy #{bind.verb}" end |
#port_for(bind) ⇒ Object
143 144 145 146 147 148 149 |
# File 'lib/hecks/runtime/registry/verification.rb', line 143 def port_for(bind) adapter = @adapters[bind.adapter] raise WiringError, "unknown adapter #{bind.adapter.inspect}" unless adapter @ports[adapter.port] || raise(WiringError, "adapter #{bind.adapter} declares unknown port #{adapter.port.inspect}") end |
#verify! ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/hecks/runtime/registry/verification.rb', line 13 def verify! verify_default_adapter! verify_singleton_port_answers! @hecksagons.each_value do |hexagon| refuse_ungoverned_roles!(hexagon) hexagon.binds.each do |bind| # A domain-level default (§0) — `persisted_by "Heki"` bare, # applying to whichever aggregates don't override it — names # no aggregate of its own, so there's nothing to look up in # the bluebook for THIS row specifically. Still validate its # own adapter/verb shape (the same reason # `verify_default_adapter!` checks the framework-wide # default the same way, aggregate-less). Coverage of real # aggregates that only resolve THROUGH this default comes # from their own dispatch-time `BindingPolicy.resolve` — # deliberately not required to be exhaustive here, the same # leniency this method already extended to any aggregate # left out of an explicit bind list entirely (real test # fixtures bind only the aggregates they exercise). if bind.aggregate.nil? check_verb(bind) next end aggregate = bluebook(hexagon.domain)&.aggregate(bind.aggregate_name) raise WiringError, "#{bind.aggregate} is bound but not declared in the bluebook" unless aggregate check_verb(bind) repository(hexagon.domain, aggregate) end warn_undurable_sagas!(hexagon) end self end |
#verify_default_adapter! ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/hecks/runtime/registry/verification.rb', line 52 def verify_default_adapter! name = Ports::Persistence::DEFAULT_ADAPTER check_verb( Bluebook::Bind.new( aggregate: "(default)", verb: Ports::Persistence::VERB, adapter: name ) ) adapter_class(name) self rescue WiringError => error raise WiringError, "the default persistence adapter (#{name}) is not usable, so an " \ "aggregate with no bind could not be given one: #{error.}" end |
#verify_singleton_port_answers! ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/hecks/runtime/registry/verification.rb', line 116 def verify_singleton_port_answers! @ports.each_value do |port| next if PER_AGGREGATE_PORTS.include?(port.name) next if Array(port.answers).empty? implementations = @adapters.values.select { |a| a.port == port.name } next unless implementations.size == 1 check_answers(port, implementations.first.name) end self end |