Module: ActiveSanction::Error
- Extended by:
- T::Helpers, T::Sig
- Included in:
- ConfigurationError, InvalidArgument, MissingKey, SourceError, StorageError, Sync::Failed, UnsupportedError
- Defined in:
- lib/active_sanction/error.rb
Overview
The one rescue that covers this library.
begin
ActiveSanction.sync!
rescue ActiveSanction::Error => e
raise unless e.retryable?
RetryLater.enqueue(e.source_id)
end
Every error raised out of a public method answers to this, and carries
what a caller needs to decide between the only three responses there are
to a screening failure: retry this (retryable?), alert somebody
(anything else that is not the caller's fault), and this is a bug in my
call (ConfigurationError, InvalidArgument, QueryError). None of those
decisions should be made by matching on a message string, so none of them
has to be.
The hierarchy
Error the marker; rescue this
ConfigurationError this installation is set up wrong; never retry
SourceError something went wrong with one list
FetchError the bytes could not be obtained
ParseError the bytes could not be read
IntegrityError the bytes are not what they claim to be
StorageError the store could not answer
UnsupportedError this object cannot do that
InvalidArgument a public method was called wrongly
QueryError ...specifically, with an unusable query
MissingKey a field or column that does not exist
Why this is a module and not a class
Because two of its members have to be something else as well. A caller who
passes threshold: 300 has made the mistake Ruby has had a class for since
1995, and rescue ArgumentError is what the code around this library
already says; asking every host application to learn a private synonym for
it would be this library exporting its own taxonomy into code that has no
reason to care. So InvalidArgument is an ::ArgumentError and MissingKey is
a ::KeyError -- and Ruby has one superclass to give. A module is what lets
them be both, and rescue ActiveSanction::Error covers them anyway, because
rescue matches with ===, which a module answers.
The trade is that ActiveSanction::Error cannot be raised or instantiated
itself. That is not a loss: an error that says only "something in the
sanctions library went wrong" is not one a caller could act on, and every
member below names a response.
Stability
This hierarchy is public API. Within a major version an error will not move
to a different parent, and an attribute will not be removed. New subclasses
may be added under an existing parent -- that is what keeps rescue ActiveSanction::FetchError working when a new transport failure is given a
name of its own -- so a case over error classes should carry an else.
Instance Attribute Summary collapse
-
#source_id ⇒ Symbol?
readonly
Which list the failure belongs to, as the key its adapter declared, or nil for a failure that is not about one list -- a bad configuration, an unusable query, a store that will not open at all.
-
#status ⇒ Integer?
readonly
The HTTP status behind the failure, where there was one.
Instance Method Summary collapse
-
#in_source(key) ⇒ T.self_type
Stamps the list this failure belongs to onto an error raised by a layer that did not know it, and returns self so a rescue can re-raise in one line.
-
#initialize(message = nil, source_id: nil, status: nil, retryable: nil) ⇒ void
retryable:overrides whatever the subclass would have decided, for the cases only the raising code knows about. -
#retryable? ⇒ Boolean
Whether running the same call again could plausibly succeed.
-
#to_h ⇒ Hash{Symbol => T.untyped}
The failure as data, for a log line or a job record that has to survive the process.
Instance Attribute Details
#source_id ⇒ Symbol? (readonly)
Which list the failure belongs to, as the key its adapter declared, or nil for a failure that is not about one list -- a bad configuration, an unusable query, a store that will not open at all.
Always set on a SourceError by the time it leaves the source, even when the layer that raised it could not know: an HTTP client knows a URL, not which sanctions list is at the other end of it. See #in_source.
79 80 81 |
# File 'lib/active_sanction/error.rb', line 79 def source_id @source_id end |
#status ⇒ Integer? (readonly)
The HTTP status behind the failure, where there was one. Nil for everything that failed before a server answered -- a timeout, a refused connection -- and for everything that is not a fetch.
85 86 87 |
# File 'lib/active_sanction/error.rb', line 85 def status @status end |
Instance Method Details
#in_source(key) ⇒ T.self_type
Stamps the list this failure belongs to onto an error raised by a layer that did not know it, and returns self so a rescue can re-raise in one line. Never overwrites a source already recorded -- the innermost layer that knew is the one that was right.
123 124 125 126 |
# File 'lib/active_sanction/error.rb', line 123 def in_source(key) @source_id = T.let(key&.to_sym, T.nilable(Symbol)) if @source_id.nil? self end |
#initialize(message = nil, source_id: nil, status: nil, retryable: nil) ⇒ void
retryable: overrides whatever the subclass would have decided, for the
cases only the raising code knows about. Everything else is a subclass's
own answer; see #retryable?.
93 94 95 96 97 98 |
# File 'lib/active_sanction/error.rb', line 93 def initialize( = nil, source_id: nil, status: nil, retryable: nil) @source_id = T.let(source_id&.to_sym, T.nilable(Symbol)) @status = T.let(status&.to_i, T.nilable(Integer)) @retryable = T.let(retryable, T.nilable(T::Boolean)) super() end |
#retryable? ⇒ Boolean
Whether running the same call again could plausibly succeed.
A first-class predicate rather than something a consumer reconstructs from the message, because backoff is the one decision a host application has to make in the request path and it should not be making it out of English. False is the default and the safe answer: a failure nobody has classified is one to look at rather than one to hammer.
108 |
# File 'lib/active_sanction/error.rb', line 108 def retryable? = retryable_or(false) |
#to_h ⇒ Hash{Symbol => T.untyped}
The failure as data, for a log line or a job record that has to survive the process.
113 114 115 116 |
# File 'lib/active_sanction/error.rb', line 113 def to_h { error: self.class.name, message: , source_id: source_id, status: status, retryable: retryable? } .compact end |