Skip to main content

xenith_core/
error.rs

1use crate::{ChainId, MessageId, StateKey};
2use thiserror::Error;
3
4/// Unified error type for all xenith operations.
5///
6/// # Example
7///
8/// ```
9/// use xenith_core::{XenithError, ChainId};
10/// let e = XenithError::UnsupportedChain(ChainId::from(9999));
11/// assert_eq!(e.to_string(), "chain 9999 is not supported by this transport");
12/// ```
13#[derive(Clone, Debug, Error)]
14pub enum XenithError {
15    /// The `chain` field identifies which chain the transport was interacting
16    /// with when the error occurred. Use `ChainId(0)` only when the chain
17    /// cannot be determined (e.g., during initial connection before chain
18    /// ID is known, or when parsing a response that carries no chain context).
19    #[error("transport error on chain {chain}: {message}")]
20    Transport { chain: ChainId, message: String },
21
22    #[error("state diverged for key {key} across chains: {chains:?}")]
23    Divergence { key: StateKey, chains: Vec<ChainId> },
24
25    #[error("message {id} timed out after {elapsed_secs}s")]
26    Timeout { id: MessageId, elapsed_secs: u64 },
27
28    #[error("insufficient fee: required {required}, provided {provided}")]
29    InsufficientFee { required: String, provided: String },
30
31    #[error("chain {0} is not supported by this transport")]
32    UnsupportedChain(ChainId),
33
34    #[error("state store error: {0}")]
35    StoreError(String),
36
37    #[error("serialization error: {0}")]
38    Serialization(String),
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn transport_message() {
47        let e = XenithError::Transport {
48            chain: ChainId(1),
49            message: "connection refused".into(),
50        };
51        assert_eq!(
52            e.to_string(),
53            "transport error on chain 1: connection refused"
54        );
55    }
56
57    #[test]
58    fn divergence_message() {
59        let e = XenithError::Divergence {
60            key: StateKey::new("proto", "pool", "0xabc"),
61            chains: vec![ChainId(1), ChainId(42161)],
62        };
63        assert_eq!(
64            e.to_string(),
65            "state diverged for key proto.pool.0xabc across chains: [ChainId(1), ChainId(42161)]"
66        );
67    }
68
69    #[test]
70    fn timeout_message() {
71        let e = XenithError::Timeout {
72            id: MessageId(7),
73            elapsed_secs: 30,
74        };
75        assert_eq!(e.to_string(), "message 7 timed out after 30s");
76    }
77
78    #[test]
79    fn insufficient_fee_message() {
80        let e = XenithError::InsufficientFee {
81            required: "500000".into(),
82            provided: "100000".into(),
83        };
84        assert_eq!(
85            e.to_string(),
86            "insufficient fee: required 500000, provided 100000"
87        );
88    }
89
90    #[test]
91    fn unsupported_chain_message() {
92        let e = XenithError::UnsupportedChain(ChainId(9999));
93        assert_eq!(
94            e.to_string(),
95            "chain 9999 is not supported by this transport"
96        );
97    }
98
99    #[test]
100    fn store_error_message() {
101        let e = XenithError::StoreError("disk full".into());
102        assert_eq!(e.to_string(), "state store error: disk full");
103    }
104
105    #[test]
106    fn serialization_message() {
107        let e = XenithError::Serialization("unexpected EOF".into());
108        assert_eq!(e.to_string(), "serialization error: unexpected EOF");
109    }
110
111    #[test]
112    fn implements_std_error() {
113        fn takes_error(_: &dyn std::error::Error) {}
114        takes_error(&XenithError::StoreError("x".into()));
115    }
116}