Exception: Axn::Tools::InvalidContract

Inherits:
ContractViolation show all
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

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