Skip to main content

dig_chainsource_interface/
error.rs

1//! The typed error every registry-participating [`ChainSource`](crate::ChainSource) reports.
2//!
3//! Every variant means the same thing: **the source could not reliably answer**, so a consumer
4//! MUST fail closed (treat it as "unknown", never as an absence or a permissive default). The
5//! *absence* of a coin/spend/lineage is NEVER an error — that is `Ok(None)`. This split is the
6//! crux of the fail-closed contract (SPEC §3): `Ok(None)` = "the chain genuinely has no such
7//! thing"; `Err(_)` = "I don't know, and you must not assume".
8
9use thiserror::Error;
10
11/// The reason a [`ChainSource`](crate::ChainSource) could not reliably answer a read.
12///
13/// This is the recommended `type Error` for any provider that participates in the shared registry,
14/// so aggregators can reason about failures uniformly. It is `#[non_exhaustive]`: new failure
15/// modes may be added in a minor release, so consumers MUST include a wildcard match arm.
16///
17/// Every variant is a "could not reliably answer" signal — consumers fail closed on all of them.
18/// The absence of a queried coin/spend/lineage is expressed as `Ok(None)`, never as an error here.
19#[derive(Debug, Error, Clone, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum ChainSourceError {
22    /// A transport/connection failure reaching the underlying backend (socket, HTTP, IPC). The
23    /// message is the backend's own, carried verbatim for diagnostics.
24    #[error("chain source transport error: {0}")]
25    Transport(String),
26
27    /// The backend responded, but the payload could not be parsed into the expected chain type
28    /// (a truncated coin record, an undecodable spend). The read is untrustworthy → fail closed.
29    #[error("malformed chain data: {0}")]
30    Malformed(String),
31
32    /// The backend does not support this query at all (e.g. a light source with no timestamp
33    /// index). The `&'static str` names the unsupported capability.
34    #[error("unsupported chain query: {0}")]
35    Unsupported(&'static str),
36
37    /// The read did not complete within the source's deadline. Whether the answer would have been
38    /// present is unknown → fail closed.
39    #[error("chain source request timed out")]
40    Timeout,
41
42    /// The backend refused the read for rate-limiting. The answer is unknown → fail closed.
43    #[error("chain source rate limited the request")]
44    RateLimited,
45
46    /// No provider was available to answer (an empty/exhausted registry). Distinct from `Ok(None)`:
47    /// the chain was never consulted, so the answer is unknown → fail closed.
48    #[error("no chain source provider available")]
49    NoProvider,
50
51    /// The backend returned more records than the consumer's hostile-input bound allows.
52    ///
53    /// Distinct from [`Malformed`](Self::Malformed): each record may be individually well-formed, but
54    /// the *count* exceeds the cap the consumer will accept, so the read is refused → fail closed. This
55    /// lets a consumer distinguish "the data is corrupt" from "the response is too large".
56    #[error("chain source returned {count} records, exceeding the {limit}-record cap")]
57    TooManyRecords {
58        /// The number of records the backend returned.
59        count: usize,
60        /// The maximum number of records the consumer will accept.
61        limit: usize,
62    },
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn too_many_records_display_reports_count_and_limit() {
71        let err = ChainSourceError::TooManyRecords {
72            count: 100_001,
73            limit: 100_000,
74        };
75        assert_eq!(
76            err.to_string(),
77            "chain source returned 100001 records, exceeding the 100000-record cap"
78        );
79    }
80
81    #[test]
82    fn too_many_records_is_distinct_from_malformed() {
83        let too_many = ChainSourceError::TooManyRecords { count: 5, limit: 1 };
84        let malformed = ChainSourceError::Malformed("truncated coin record".to_string());
85        assert_ne!(too_many, malformed);
86        assert!(matches!(too_many, ChainSourceError::TooManyRecords { .. }));
87        assert!(!matches!(too_many, ChainSourceError::Malformed(_)));
88    }
89}