gluonscan_core/error.rs
1//! The typed error surface. Part of the semver contract — variants are `#[non_exhaustive]`.
2
3use crate::{Capability, Protocol, Source};
4
5/// Everything a fetch can fail with. The three financially-meaningful kinds are
6/// [`Transient`](Error::Transient) (retryable), [`Integrity`](Error::Integrity) (a required source
7/// failed → fail closed), and [`AbsentPrice`](Error::AbsentPrice) (never coerce to `0`/`1`).
8#[non_exhaustive]
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11 /// A transient provider failure (HTTP 429, timeout). Safe to retry.
12 #[error("transient: {message}")]
13 Transient {
14 /// Human-readable cause.
15 message: String,
16 /// Server-advised backoff, if any.
17 retry_after: Option<std::time::Duration>,
18 },
19
20 /// A permanent failure — a bad address, an unsupported chain, malformed config. Not retryable.
21 #[error("permanent: {message}")]
22 Permanent {
23 /// Human-readable cause.
24 message: String,
25 },
26
27 /// A required source failed or returned incomplete data. The engine fails closed rather than
28 /// return partial financial data.
29 #[error("integrity: {message}")]
30 Integrity {
31 /// Human-readable cause.
32 message: String,
33 },
34
35 /// No price is available for an asset. Never substituted with `0` or `1`.
36 #[error("absent price for asset: {asset}")]
37 AbsentPrice {
38 /// The asset that could not be priced (symbol or address).
39 asset: String,
40 },
41
42 /// The requested capability is not offered by any registered backend of this protocol.
43 #[error("{protocol:?} does not support {capability:?} via {backend:?}")]
44 Unsupported {
45 /// The protocol asked for.
46 protocol: Protocol,
47 /// The capability asked for.
48 capability: Capability,
49 /// The backend that was routed.
50 backend: Source,
51 },
52
53 /// An error from an injected provider (HTTP client, RPC transport, custom adapter). Opaque on
54 /// purpose so extension points aren't forced into this enum.
55 #[error("provider: {0}")]
56 Provider(#[source] Box<dyn std::error::Error + Send + Sync>),
57}
58
59impl Error {
60 /// Whether retrying the operation could succeed.
61 #[must_use]
62 pub fn is_retryable(&self) -> bool {
63 matches!(self, Error::Transient { .. })
64 }
65}