Skip to main content

xrpl/asynch/clients/
exceptions.rs

1#[cfg(all(not(feature = "std"), feature = "websocket"))]
2use alloc::boxed::Box;
3use thiserror_no_std::Error;
4
5#[cfg(feature = "helpers")]
6use crate::asynch::wallet::exceptions::XRPLFaucetException;
7use crate::{models::XRPLModelException, XRPLSerdeJsonError};
8
9#[cfg(feature = "json-rpc")]
10use super::XRPLJsonRpcException;
11#[cfg(feature = "websocket")]
12use super::XRPLWebSocketException;
13
14pub type XRPLClientResult<T, E = XRPLClientException> = core::result::Result<T, E>;
15
16/// Granular network/transport error categories for client failures.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum XRPLNetworkErrorKind {
20    ConnectionClosed,
21    AlreadyClosed,
22    ConnectionRefused,
23    ConnectionReset,
24    TimedOut,
25    Dns,
26    Tls,
27    Protocol,
28    InvalidResponse,
29    OtherIo,
30    Other,
31}
32
33#[derive(Debug, Error)]
34#[non_exhaustive]
35pub enum XRPLClientException {
36    #[error("serde_json error: {0}")]
37    XRPLSerdeJsonError(#[from] XRPLSerdeJsonError),
38    #[error("XRPL Model error: {0}")]
39    XRPLModelError(#[from] XRPLModelException),
40    #[cfg(feature = "helpers")]
41    #[error("XRPL Faucet error: {0}")]
42    XRPLFaucetError(#[from] XRPLFaucetException),
43    #[cfg(feature = "websocket")]
44    #[error("XRPL WebSocket error: {0}")]
45    XRPLWebSocketError(Box<XRPLWebSocketException>),
46    #[cfg(feature = "json-rpc")]
47    #[error("XRPL JSON-RPC error: {0}")]
48    XRPLJsonRpcError(#[from] XRPLJsonRpcException),
49    #[error("URL parse error: {0}")]
50    UrlParseError(#[from] url::ParseError),
51    #[cfg(feature = "std")]
52    #[error("I/O error: {0}")]
53    IoError(#[from] alloc::io::Error),
54}
55
56impl From<serde_json::Error> for XRPLClientException {
57    fn from(error: serde_json::Error) -> Self {
58        XRPLClientException::XRPLSerdeJsonError(XRPLSerdeJsonError::from(error))
59    }
60}
61
62#[cfg(all(not(feature = "std"), feature = "json-rpc"))]
63impl From<reqwless::Error> for XRPLClientException {
64    fn from(error: reqwless::Error) -> Self {
65        XRPLClientException::XRPLJsonRpcError(XRPLJsonRpcException::ReqwlessError(error))
66    }
67}
68
69#[cfg(feature = "websocket")]
70impl From<XRPLWebSocketException> for XRPLClientException {
71    fn from(error: XRPLWebSocketException) -> Self {
72        XRPLClientException::XRPLWebSocketError(Box::new(error))
73    }
74}
75
76#[cfg(all(feature = "std", feature = "websocket"))]
77impl From<tokio_tungstenite::tungstenite::Error> for XRPLClientException {
78    fn from(error: tokio_tungstenite::tungstenite::Error) -> Self {
79        XRPLClientException::XRPLWebSocketError(Box::new(XRPLWebSocketException::from(error)))
80    }
81}
82
83#[cfg(all(feature = "std", feature = "json-rpc"))]
84impl From<reqwest::Error> for XRPLClientException {
85    fn from(error: reqwest::Error) -> Self {
86        XRPLClientException::XRPLJsonRpcError(XRPLJsonRpcException::ReqwestError(error))
87    }
88}
89
90impl XRPLClientException {
91    /// Return a typed network error category, if this is a transport failure.
92    pub fn network_error_kind(&self) -> Option<XRPLNetworkErrorKind> {
93        match self {
94            #[cfg(feature = "websocket")]
95            XRPLClientException::XRPLWebSocketError(error) => error.network_error_kind(),
96            #[cfg(feature = "json-rpc")]
97            XRPLClientException::XRPLJsonRpcError(error) => error.network_error_kind(),
98            #[cfg(feature = "std")]
99            XRPLClientException::IoError(error) => Some(match error.kind() {
100                alloc::io::ErrorKind::ConnectionRefused => XRPLNetworkErrorKind::ConnectionRefused,
101                alloc::io::ErrorKind::ConnectionReset => XRPLNetworkErrorKind::ConnectionReset,
102                alloc::io::ErrorKind::TimedOut => XRPLNetworkErrorKind::TimedOut,
103                _ => XRPLNetworkErrorKind::OtherIo,
104            }),
105            _ => None,
106        }
107    }
108}
109
110#[cfg(feature = "std")]
111impl alloc::error::Error for XRPLClientException {}
112
113#[cfg(all(test, feature = "std", feature = "websocket", feature = "json-rpc"))]
114mod tests {
115    use alloc::boxed::Box;
116
117    use super::*;
118    use crate::asynch::clients::{XRPLJsonRpcException, XRPLWebSocketException};
119
120    #[test]
121    fn maps_client_network_error_kinds() {
122        let cases = [
123            (
124                XRPLClientException::XRPLWebSocketError(Box::new(
125                    XRPLWebSocketException::ConnectionClosed,
126                )),
127                Some(XRPLNetworkErrorKind::ConnectionClosed),
128            ),
129            (
130                XRPLClientException::XRPLJsonRpcError(XRPLJsonRpcException::RequestError(
131                    "not transport".into(),
132                )),
133                None,
134            ),
135            (
136                XRPLClientException::IoError(alloc::io::Error::from(
137                    alloc::io::ErrorKind::ConnectionRefused,
138                )),
139                Some(XRPLNetworkErrorKind::ConnectionRefused),
140            ),
141            (
142                XRPLClientException::IoError(alloc::io::Error::from(
143                    alloc::io::ErrorKind::ConnectionReset,
144                )),
145                Some(XRPLNetworkErrorKind::ConnectionReset),
146            ),
147            (
148                XRPLClientException::IoError(alloc::io::Error::from(
149                    alloc::io::ErrorKind::TimedOut,
150                )),
151                Some(XRPLNetworkErrorKind::TimedOut),
152            ),
153            (
154                XRPLClientException::IoError(alloc::io::Error::from(alloc::io::ErrorKind::Other)),
155                Some(XRPLNetworkErrorKind::OtherIo),
156            ),
157        ];
158
159        for (error, expected) in cases {
160            assert_eq!(error.network_error_kind(), expected);
161        }
162    }
163}