Exception: Axn::Tools::InvalidContract
- Inherits:
-
ContractViolation
- Object
- StandardError
- ContractViolation
- Axn::Tools::InvalidContract
- Defined in:
- lib/axn/exceptions.rb
Overview
Raised by Axn::Tools.validate_contracts! for a tool whose contract failure cannot be reported AS ITSELF.
Reporting it as itself means renaming it to say which tool it came from, and renaming an exception runs the
exception's own code — #exception, which raise dispatches on whatever object it is handed, and the
duplication hooks Exception#exception(message) reaches. axn will not run that code while reporting the
failure it caused (an override that raises replaces the failure, and one outside StandardError escapes the
boot rescue entirely), so when the class owns any of it, axn reports its own error instead.
Nothing is lost but the class: the original is this error's cause, and its message is repeated here.
Deliberately builds its text in initialize rather than in #message, as ReraiseFailed below
does for the same reason. Everything it needs is rendered text by the time it is constructed, so there is
nothing to defer — and this exception exists precisely because reporting must not depend on an exception's
own methods, so it renders identically through #message, through a bound Exception#to_s, and to
anything that reads the stored message directly.
Every value it interpolates came from somewhere else's object — the tool's constant path, the original's
message, the original's class name — so each is RENDERED into this message rather than joined to it. Bytes
with no UTF-8 rendering (or in another encoding entirely) would otherwise raise
Encoding::CompatibilityError from super itself, replacing the tool-contract failure with an encoding
failure at boot, which is the outcome this error exists to prevent one indirection over. Rendering is
idempotent, so the caller having already rendered them (as Axn::Tools.validate_contracts! does, needing
the same text for its other branch) costs an allocation and changes nothing: the guarantee holds for any
caller rather than resting on that one's diligence.
Rendered through Internal::RenderedText, which composes Internal::Text — the byte primitive this file
already requires — with the type test a public class owes its callers. Never through the reflection
layer's own renderer, which is built ON this file: a message path here that reached UP into that layer
would NameError under the standalone loads spec/axn/standalone_require_spec.rb pins. The type test is
what keeps new(tool: :foo, …) — a Symbol being the obvious way to name a tool — rendering as foo
rather than raising a TypeError out of the message path, since Text.renderable binds String methods and
takes Strings alone.
Instance Method Summary collapse
-
#initialize(tool:, reason:, original_class:) ⇒ InvalidContract
constructor
A new instance of InvalidContract.
Constructor Details
#initialize(tool:, reason:, original_class:) ⇒ InvalidContract
Returns a new instance of InvalidContract.
260 261 262 263 264 265 266 267 |
# File 'lib/axn/exceptions.rb', line 260 def initialize(tool:, reason:, original_class:) tool, reason, original_class = [tool, reason, original_class].map { |text| Axn::Internal::RenderedText.of(text) } super("#{tool} has an invalid tool contract — #{reason} (raised as #{self.class}, and not as the original " \ "#{original_class}, because that class supplies its own `#exception` or duplication hook, or the " \ "object is frozen: axn does not run an exception's own code while reporting the failure it caused. " \ "The original is this error's `cause`.)") end |