Module: Axn::Internal::Identity
- Defined in:
- lib/axn/internal/identity.rb
Overview
Identity comparisons that cannot be intercepted by the objects being compared.
Axn accepts caller-supplied collaborators whose contract is one named method and nothing else — a
tracer promises #in_span. Asking such an object whether it is nil, or whether it is the same
object as another, by dispatching nil?/equal? to it means a proxy that overrides or forwards
those can change or break an answer axn relies on. The binding comes from BasicObject rather than
Object so it holds for a BasicObject-based proxy too, which is the shape most likely to forward
everything through method_missing.
Class Method Summary collapse
-
.blank_string?(value) ⇒ Boolean
True when a String is empty or only whitespace, read through String's OWN implementations.
-
.class_of(value) ⇒ Object
value's class, read through Object's own#class. -
.describe(value) ⇒ Object
A caller-supplied value rendered for an error message, which must not be able to replace that error.
-
.kind?(value, klass) ⇒ Boolean
True when
valueis an instance ofklass— the undispatched form ofvalue.is_a?(klass), for avaluewhose own answer axn has no reason to trust. -
.name_error_for?(error, name) ⇒ Boolean
Whether
error— a NameError, or the NoMethodError that subclasses it — reportsnameas the name nothing answered to. -
.nil_value?(value) ⇒ Boolean
True when
valueIS nil — the undispatched form ofvalue.nil?. -
.same?(one, other) ⇒ Boolean
True when
oneandotherare the same object. -
.undescribable(value) ⇒ Object
Names the value's CLASS without asking the value anything.
-
.utf8_string(value) ⇒ Object
A caller-supplied String rendered so it can be interpolated into a UTF-8 message.
Class Method Details
.blank_string?(value) ⇒ Boolean
True when a String is empty or only whitespace, read through String's OWN implementations. A
subclass may override strip/empty?, and this runs while an error is already being raised —
dispatching there lets the caller's method replace that error with anything it likes, including
a class the surrounding rescue was never meant to catch. Caller must have checked kind? first.
117 |
# File 'lib/axn/internal/identity.rb', line 117 def self.blank_string?(value) = EMPTY.bind_call(STRIP.bind_call(value)) |
.class_of(value) ⇒ Object
value's class, read through Object's own #class. A config object can declare a setting named
class, whose generated reader shadows it — so dispatching would hand back the setting's value
where a Class was expected.
54 |
# File 'lib/axn/internal/identity.rb', line 54 def self.class_of(value) = CLASS_OF.bind_call(value) |
.describe(value) ⇒ Object
A caller-supplied value rendered for an error message, which must not be able to replace that
error. inspect IS dispatched, deliberately: the object's own is what makes a message useful
("foo" rather than #<String:0x…>), and rendering everything through Object#inspect would
degrade every well-behaved value to defend against broken ones. So the call is made and its
failure absorbed instead.
Absorbs every class, including those axn never swallows. An inspect that raises is an ordinary
bug — an uninitialized ivar, a broken association — and on an error path the exception being
reported has to win over anything raised while describing it. A non-String inspect is treated
as a failure too, since interpolating it would dispatch again.
77 78 79 80 81 82 |
# File 'lib/axn/internal/identity.rb', line 77 def self.describe(value) rendered = value.inspect kind?(rendered, String) ? utf8_string(rendered) : undescribable(value) rescue Exception # rubocop:disable Lint/RescueException undescribable(value) end |
.kind?(value, klass) ⇒ Boolean
True when value is an instance of klass — the undispatched form of value.is_a?(klass),
for a value whose own answer axn has no reason to trust.
49 |
# File 'lib/axn/internal/identity.rb', line 49 def self.kind?(value, klass) = KIND.bind_call(klass, value) |
.name_error_for?(error, name) ⇒ Boolean
Whether error — a NameError, or the NoMethodError that subclasses it — reports name as the name
nothing answered to. The question is "is this method genuinely ABSENT, or did a present implementation
raise from inside?", and the two answers are not interchangeable: swallowing the second hides a
collaborator's own bug, while re-raising the first turns an optional method into a hard requirement.
Neither half dispatches anything the error can define. The stored name is read through NameError's
OWN name, so a subclass defining (or raising from) name cannot answer in its place; and axn's own
Symbol is the RECEIVER of equal?, so a returned object's == never runs either. NoMethodError
inherits name from NameError, so one binding covers both.
65 |
# File 'lib/axn/internal/identity.rb', line 65 def self.name_error_for?(error, name) = name.equal?(NAME_ERROR_NAME.bind_call(error)) |
.nil_value?(value) ⇒ Boolean
True when value IS nil — the undispatched form of value.nil?.
45 |
# File 'lib/axn/internal/identity.rb', line 45 def self.nil_value?(value) = EQUAL.bind_call(value, nil) |
.same?(one, other) ⇒ Boolean
True when one and other are the same object.
42 |
# File 'lib/axn/internal/identity.rb', line 42 def self.same?(one, other) = EQUAL.bind_call(one, other) |
.undescribable(value) ⇒ Object
Names the value's CLASS without asking the value anything. Nested rescue because class_of
binds an Object method, which a BasicObject-based proxy cannot receive at all.
86 87 88 89 90 |
# File 'lib/axn/internal/identity.rb', line 86 def self.undescribable(value) "#<#{MODULE_NAME.bind_call(class_of(value))} (inspect unavailable)>" rescue Exception # rubocop:disable Lint/RescueException "#<unrenderable value>" end |
.utf8_string(value) ⇒ Object
A caller-supplied String rendered so it can be interpolated into a UTF-8 message. ALWAYS returns valid UTF-8, which is the contract callers depend on.
The fallback is a SCRUB rather than the escape Internal::Text.renderable takes, and the difference
is the point: this renders text that has to appear at any cost — a reason string from a caller's
validate: lambda, joined into the validation message a user reads — where a lossy caf<?> beats
both an escaped spelling and no message. A layer naming an OFFENDER wants the escape instead, since
it must not quietly alter what it names.
A successful rendering is COPIED and retagged rather than returned as it came, because
Text.utf8_rendering answers its ASCII-only fast path with the argument itself — which for ASCII bytes
tagged BINARY, US-ASCII or Latin-1 is not UTF-8, and for a String subclass is an object the caller still
owns. Both would break the promise above: the encoding one silently, since ASCII-only bytes concatenate
with anything and so the wrong tag only surfaces once the result is joined to something else.
106 107 108 109 110 111 |
# File 'lib/axn/internal/identity.rb', line 106 def self.utf8_string(value) rendered = Text.utf8_rendering(value) return FORCE_ENCODING.bind_call(::String.new(rendered), Encoding::UTF_8) if rendered SCRUB.bind_call(FORCE_ENCODING.bind_call(DUP.bind_call(value), Encoding::UTF_8)) end |