faucet_server/
error.rs

1use std::convert::Infallible;
2
3use crate::client::ExclusiveBody;
4
5pub enum BadRequestReason {
6    MissingHeader(&'static str),
7    InvalidHeader(&'static str),
8    NoPathOrQuery,
9    NoHostName,
10    UnsupportedUrlScheme,
11}
12
13pub type FaucetResult<T> = std::result::Result<T, FaucetError>;
14
15pub enum FaucetError {
16    PoolBuild(deadpool::managed::BuildError),
17    PoolTimeout(deadpool::managed::TimeoutType),
18    PoolPostCreateHook,
19    PoolClosed,
20    PoolNoRuntimeSpecified,
21    NoSocketsAvailable,
22    ConnectionClosed,
23    Io(std::io::Error),
24    Unknown(String),
25    HostParseError(std::net::AddrParseError),
26    Hyper(hyper::Error),
27    BadRequest(BadRequestReason),
28    InvalidHeaderValues(hyper::header::InvalidHeaderValue),
29    Http(hyper::http::Error),
30    MissingArgument(&'static str),
31    DuplicateRoute(String),
32    Utf8Coding,
33    BufferCapacity(tokio_tungstenite::tungstenite::error::CapacityError),
34    ProtocolViolation(tokio_tungstenite::tungstenite::error::ProtocolError),
35    WSWriteBufferFull(tokio_tungstenite::tungstenite::Message),
36    PostgreSQL(tokio_postgres::Error),
37    AttackAttempt,
38}
39
40impl From<tokio_postgres::Error> for FaucetError {
41    fn from(value: tokio_postgres::Error) -> Self {
42        Self::PostgreSQL(value)
43    }
44}
45
46impl From<tokio_tungstenite::tungstenite::Error> for FaucetError {
47    fn from(value: tokio_tungstenite::tungstenite::Error) -> Self {
48        use tokio_tungstenite::tungstenite::error::UrlError;
49        use tokio_tungstenite::tungstenite::Error;
50        match value {
51            Error::Io(err) => FaucetError::Io(err),
52            Error::Url(err) => match err {
53                UrlError::NoPathOrQuery => FaucetError::BadRequest(BadRequestReason::NoPathOrQuery),
54                UrlError::NoHostName | UrlError::EmptyHostName => {
55                    FaucetError::BadRequest(BadRequestReason::NoHostName)
56                }
57                UrlError::TlsFeatureNotEnabled => panic!("TLS Not enabled"),
58                UrlError::UnableToConnect(err) => FaucetError::Unknown(err),
59                UrlError::UnsupportedUrlScheme => {
60                    FaucetError::BadRequest(BadRequestReason::UnsupportedUrlScheme)
61                }
62            },
63            Error::Tls(err) => FaucetError::Unknown(err.to_string()),
64            Error::Utf8 => FaucetError::Utf8Coding,
65            Error::Http(_) => FaucetError::Unknown("Unknown HTTP error".to_string()),
66            Error::Capacity(err) => FaucetError::BufferCapacity(err),
67            Error::HttpFormat(err) => FaucetError::Http(err),
68            Error::Protocol(err) => FaucetError::ProtocolViolation(err),
69            Error::AlreadyClosed | Error::ConnectionClosed => FaucetError::ConnectionClosed,
70            Error::AttackAttempt => FaucetError::AttackAttempt,
71            Error::WriteBufferFull(msg) => FaucetError::WSWriteBufferFull(msg),
72        }
73    }
74}
75
76impl From<hyper::header::InvalidHeaderValue> for FaucetError {
77    fn from(e: hyper::header::InvalidHeaderValue) -> Self {
78        Self::InvalidHeaderValues(e)
79    }
80}
81
82impl From<hyper::http::Error> for FaucetError {
83    fn from(e: hyper::http::Error) -> Self {
84        Self::Http(e)
85    }
86}
87
88impl From<deadpool::managed::PoolError<FaucetError>> for FaucetError {
89    fn from(value: deadpool::managed::PoolError<FaucetError>) -> Self {
90        match value {
91            deadpool::managed::PoolError::Backend(e) => e,
92            deadpool::managed::PoolError::Timeout(e) => Self::PoolTimeout(e),
93            deadpool::managed::PoolError::Closed => Self::PoolClosed,
94            deadpool::managed::PoolError::PostCreateHook(_) => Self::PoolPostCreateHook,
95            deadpool::managed::PoolError::NoRuntimeSpecified => Self::PoolNoRuntimeSpecified,
96        }
97    }
98}
99
100impl From<Infallible> for FaucetError {
101    fn from(_: Infallible) -> Self {
102        unreachable!("Infallible error")
103    }
104}
105
106impl From<deadpool::managed::BuildError> for FaucetError {
107    fn from(e: deadpool::managed::BuildError) -> Self {
108        Self::PoolBuild(e)
109    }
110}
111
112impl From<std::io::Error> for FaucetError {
113    fn from(e: std::io::Error) -> Self {
114        Self::Io(e)
115    }
116}
117
118impl From<std::net::AddrParseError> for FaucetError {
119    fn from(e: std::net::AddrParseError) -> Self {
120        Self::HostParseError(e)
121    }
122}
123
124impl From<hyper::Error> for FaucetError {
125    fn from(e: hyper::Error) -> Self {
126        Self::Hyper(e)
127    }
128}
129
130impl std::fmt::Display for FaucetError {
131    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
132        match self {
133            Self::PoolBuild(e) => write!(f, "Pool build error: {}", e),
134            Self::PoolTimeout(e) => write!(f, "Pool timeout error: {:?}", e),
135            Self::PoolPostCreateHook => write!(f, "Pool post create hook error"),
136            Self::PoolClosed => write!(f, "Pool closed error"),
137            Self::PoolNoRuntimeSpecified => write!(f, "Pool no runtime specified error"),
138            Self::Io(e) => write!(f, "IO error: {}", e),
139            Self::Unknown(e) => write!(f, "Unknown error: {}", e),
140            Self::HostParseError(e) => write!(f, "Error parsing host address: {}", e),
141            Self::Hyper(e) => write!(f, "Hyper error: {}", e),
142            Self::Http(e) => write!(f, "Http error: {}", e),
143            Self::InvalidHeaderValues(e) => write!(f, "Invalid header values: {}", e),
144            Self::MissingArgument(s) => write!(f, "Missing argument: {}", s),
145            Self::DuplicateRoute(route) => write!(f, "Route '{route}' is duplicated"),
146            Self::AttackAttempt => write!(f, "Attack attempt detected"),
147            Self::ConnectionClosed => write!(f, "Connection closed"),
148            Self::ProtocolViolation(e) => write!(f, "Protocol violation: {e}"),
149            Self::Utf8Coding => write!(f, "Utf8 Coding error"),
150            Self::BufferCapacity(cap_err) => write!(f, "Buffer Capacity: {cap_err}"),
151            Self::WSWriteBufferFull(buf) => write!(f, "Web Socket Write buffer full, {buf}"),
152            Self::PostgreSQL(value) => write!(f, "PostgreSQL error: {value}"),
153            Self::BadRequest(r) => match r {
154                BadRequestReason::UnsupportedUrlScheme => {
155                    write!(f, "UnsupportedUrlScheme use ws:// os wss://")
156                }
157                BadRequestReason::NoHostName => write!(f, "No Host Name"),
158                BadRequestReason::MissingHeader(header) => {
159                    write!(f, "Missing header: {}", header)
160                }
161                BadRequestReason::InvalidHeader(header) => {
162                    write!(f, "Invalid header: {}", header)
163                }
164                BadRequestReason::NoPathOrQuery => write!(f, "No path and/or query"),
165            },
166            Self::NoSocketsAvailable => write!(f, "No sockets available"),
167        }
168    }
169}
170
171impl std::fmt::Debug for FaucetError {
172    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
173        write!(f, "{}", self)
174    }
175}
176
177impl std::error::Error for FaucetError {}
178
179impl FaucetError {
180    pub fn no_sec_web_socket_key() -> Self {
181        Self::BadRequest(BadRequestReason::MissingHeader("Sec-WebSocket-Key"))
182    }
183    pub fn unknown(s: impl ToString) -> Self {
184        Self::Unknown(s.to_string())
185    }
186}
187
188impl From<FaucetError> for hyper::Response<ExclusiveBody> {
189    fn from(val: FaucetError) -> Self {
190        let mut resp = hyper::Response::new(ExclusiveBody::plain_text(val.to_string()));
191        *resp.status_mut() = hyper::StatusCode::INTERNAL_SERVER_ERROR;
192        resp
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_faucet_error() {
202        let err = FaucetError::unknown("test");
203        assert_eq!(err.to_string(), "Unknown error: test");
204    }
205
206    #[test]
207    fn test_faucet_error_debug() {
208        let err = FaucetError::unknown("test");
209        assert_eq!(format!("{:?}", err), r#"Unknown error: test"#);
210    }
211
212    #[test]
213    fn test_faucet_error_from_hyper_error() {
214        let err = hyper::Request::builder()
215            .uri("INVALID URI")
216            .body(())
217            .unwrap_err();
218
219        let _err: FaucetError = From::from(err);
220    }
221
222    #[test]
223    fn test_faucet_error_from_io_error() {
224        let err = std::io::Error::new(std::io::ErrorKind::Other, "test");
225
226        let _err: FaucetError = From::from(err);
227    }
228
229    #[test]
230    fn test_faucet_error_from_pool_error() {
231        let err = deadpool::managed::PoolError::Backend(FaucetError::unknown("test"));
232
233        let _err: FaucetError = From::from(err);
234    }
235
236    #[test]
237    fn test_faucet_error_from_pool_build_error() {
238        let err = deadpool::managed::BuildError::NoRuntimeSpecified;
239
240        let _err: FaucetError = From::from(err);
241    }
242
243    #[test]
244    fn test_faucet_error_from_pool_timeout_error() {
245        let err = deadpool::managed::PoolError::<FaucetError>::Timeout(
246            deadpool::managed::TimeoutType::Create,
247        );
248
249        let _err: FaucetError = From::from(err);
250    }
251
252    #[test]
253    fn test_faucet_error_from_pool_closed_error() {
254        let err = deadpool::managed::PoolError::<FaucetError>::Closed;
255
256        let _err: FaucetError = From::from(err);
257    }
258
259    #[test]
260    fn test_faucet_error_from_pool_post_create_hook_error() {
261        let err = deadpool::managed::PoolError::<FaucetError>::PostCreateHook(
262            deadpool::managed::HookError::message("test"),
263        );
264
265        let _err: FaucetError = From::from(err);
266    }
267
268    #[test]
269    fn test_faucet_error_from_pool_no_runtime_specified_error() {
270        let err = deadpool::managed::PoolError::<FaucetError>::NoRuntimeSpecified;
271
272        let _err: FaucetError = From::from(err);
273    }
274
275    #[test]
276    fn test_faucet_error_from_hyper_invalid_header_value_error() {
277        let err = hyper::header::HeaderValue::from_bytes([0x00].as_ref()).unwrap_err();
278
279        let _err: FaucetError = From::from(err);
280    }
281
282    #[test]
283    fn test_faucet_error_from_addr_parse_error() {
284        let err = "INVALID".parse::<std::net::SocketAddr>().unwrap_err();
285
286        let _err: FaucetError = From::from(err);
287    }
288
289    #[test]
290    fn test_faucet_error_displat_missing_header() {
291        let _err = FaucetError::BadRequest(BadRequestReason::MissingHeader("test"));
292    }
293
294    #[test]
295    fn test_faucet_error_displat_invalid_header() {
296        let _err = FaucetError::BadRequest(BadRequestReason::InvalidHeader("test"));
297    }
298
299    #[test]
300    fn test_from_fauct_error_to_hyper_response() {
301        let err = FaucetError::unknown("test");
302        let resp: hyper::Response<ExclusiveBody> = err.into();
303        assert_eq!(resp.status(), hyper::StatusCode::INTERNAL_SERVER_ERROR);
304    }
305}