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