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 PeerBusy,
14 Transport(String),
17 DialDenied(DialLimit),
19 LocalPeer,
21 Stale,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum DialLimit {
27 Pending,
29 Established,
31 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 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}