Skip to main content

urma_node/
error.rs

1use std::io::ErrorKind;
2
3#[derive(Debug)]
4pub enum Error {
5    Io(std::io::Error),
6    Database(rusqlite::Error),
7    Json(serde_json::Error),
8    Source(Box<dyn std::error::Error + Send + Sync>),
9    CommitSource(String),
10    Invalid(String),
11    Busy(String),
12    /// A remote peer answered Busy: its capacity, not ours.
13    PeerBusy,
14    /// A peer request failed in transport (dial, timeout, closed connection,
15    /// I/O, unsupported protocol): no statement about the peer's honesty.
16    Transport(String),
17    /// Our own connection admission refused a dial, before reaching the peer.
18    DialDenied(DialLimit),
19    /// A dial authenticated this node itself, e.g. a seed resolving to us.
20    LocalPeer,
21    Stale,
22}
23
24/// Which local admission limit refused a connection.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum DialLimit {
27    /// Too many connections still handshaking in that direction.
28    Pending,
29    /// Every established slot in that direction (or in total) is taken.
30    Established,
31    /// The peer already holds an established connection.
32    Duplicate,
33}
34
35impl std::fmt::Display for DialLimit {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.write_str(match self {
38            Self::Pending => "pending connection limit",
39            Self::Established => "established connection limit",
40            Self::Duplicate => "peer already connected",
41        })
42    }
43}
44
45impl std::fmt::Display for Error {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            // A socket read timeout surfaces as EAGAIN (`WouldBlock`) on Unix.
49            Self::Io(e) if matches!(e.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => {
50                write!(
51                    f,
52                    "timed out: no answer in time, the other side may be busy ({e})"
53                )
54            }
55            Self::Io(e) => write!(f, "archive I/O: {e}"),
56            Self::Database(e) => write!(f, "archive database: {e}"),
57            Self::Json(e) => write!(f, "archive encoding: {e}"),
58            Self::Source(e) => write!(f, "chain source: {e}"),
59            Self::CommitSource(e) => write!(f, "commit source: {e}"),
60            Self::Invalid(e) => f.write_str(e),
61            Self::Busy(e) => write!(f, "archive busy: {e}"),
62            Self::PeerBusy => f.write_str("remote peer busy"),
63            Self::Transport(e) => write!(f, "peer transport: {e}"),
64            Self::DialDenied(limit) => write!(f, "dial denied by local connection limit ({limit})"),
65            Self::LocalPeer => f.write_str("dial reached this node itself"),
66            Self::Stale => f.write_str("result belongs to a superseded chain view"),
67        }
68    }
69}
70
71impl std::error::Error for Error {
72    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73        match self {
74            Self::Io(e) => Some(e),
75            Self::Database(e) => Some(e),
76            Self::Json(e) => Some(e),
77            Self::Source(e) => Some(e.as_ref()),
78            _ => None,
79        }
80    }
81}
82
83impl From<std::io::Error> for Error {
84    fn from(e: std::io::Error) -> Self {
85        Self::Io(e)
86    }
87}
88impl From<rusqlite::Error> for Error {
89    fn from(e: rusqlite::Error) -> Self {
90        Self::Database(e)
91    }
92}
93impl From<serde_json::Error> for Error {
94    fn from(e: serde_json::Error) -> Self {
95        Self::Json(e)
96    }
97}
98
99impl From<urma_node_client::Error> for Error {
100    fn from(error: urma_node_client::Error) -> Self {
101        match error {
102            urma_node_client::Error::Io(error) => Self::Io(error),
103            urma_node_client::Error::Json(error) => Self::Json(error),
104            urma_node_client::Error::Invalid(message) => Self::Invalid(message),
105            urma_node_client::Error::Busy(message) => Self::Busy(message),
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::Error;
113    use std::io::{self, ErrorKind};
114
115    #[test]
116    fn a_timeout_reads_as_timed_out_not_archive_io() {
117        for kind in [ErrorKind::WouldBlock, ErrorKind::TimedOut] {
118            let error = Error::from(urma_node_client::Error::Io(io::Error::from(kind)));
119            let shown = error.to_string();
120            assert!(matches!(error, Error::Io(_)), "the typed kind is kept");
121            assert!(shown.starts_with("timed out"), "{shown}");
122            assert!(!shown.contains("archive I/O"), "{shown}");
123        }
124        let refused = Error::from(io::Error::from(ErrorKind::ConnectionRefused));
125        assert!(refused.to_string().starts_with("archive I/O"));
126    }
127}