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}