Skip to main content

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}