1use thiserror::Error;
7
8#[derive(Debug, Error)]
26pub enum WiseGateError {
27 #[error("Invalid IP address: {0}")]
29 InvalidIp(String),
30
31 #[error("Configuration error: {0}")]
33 ConfigError(String),
34
35 #[error("Proxy error: {0}")]
37 ProxyError(String),
38
39 #[error("Rate limit exceeded for IP: {0}")]
41 RateLimitExceeded(String),
42
43 #[error("IP blocked: {0}")]
45 IpBlocked(String),
46
47 #[error("URL pattern blocked: {0}")]
49 PatternBlocked(String),
50
51 #[error("HTTP method blocked: {0}")]
53 MethodBlocked(String),
54
55 #[error("Upstream connection failed: {0}")]
57 UpstreamConnectionFailed(String),
58
59 #[error("Upstream timeout: {0}")]
61 UpstreamTimeout(String),
62
63 #[error("Request body too large: {size} bytes (max: {max} bytes)")]
65 BodyTooLarge {
66 size: usize,
68 max: usize,
70 },
71
72 #[error("Body read error: {0}")]
74 BodyReadError(String),
75
76 #[error("Client request body timed out")]
78 ClientBodyTimeout,
79
80 #[error("HTTP client error: {0}")]
82 HttpClientError(#[from] reqwest::Error),
83}
84
85impl WiseGateError {
86 pub fn status_code(&self) -> hyper::StatusCode {
92 use hyper::StatusCode;
93
94 match self {
95 Self::InvalidIp(_) => StatusCode::BAD_REQUEST,
96 Self::ConfigError(_) => StatusCode::INTERNAL_SERVER_ERROR,
97 Self::ProxyError(_) => StatusCode::BAD_GATEWAY,
98 Self::RateLimitExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
99 Self::IpBlocked(_) => StatusCode::FORBIDDEN,
100 Self::PatternBlocked(_) => StatusCode::NOT_FOUND,
101 Self::MethodBlocked(_) => StatusCode::METHOD_NOT_ALLOWED,
102 Self::UpstreamConnectionFailed(_) => StatusCode::BAD_GATEWAY,
103 Self::UpstreamTimeout(_) => StatusCode::GATEWAY_TIMEOUT,
104 Self::BodyTooLarge { .. } => StatusCode::PAYLOAD_TOO_LARGE,
105 Self::BodyReadError(_) => StatusCode::BAD_REQUEST,
106 Self::ClientBodyTimeout => StatusCode::REQUEST_TIMEOUT,
107 Self::HttpClientError(_) => StatusCode::BAD_GATEWAY,
108 }
109 }
110
111 pub fn user_message(&self) -> &str {
116 match self {
117 Self::InvalidIp(_) => "Invalid request",
118 Self::ConfigError(_) => "Internal server error",
119 Self::ProxyError(_) => "Bad gateway",
120 Self::RateLimitExceeded(_) => "Rate limit exceeded",
121 Self::IpBlocked(_) => "Access denied",
122 Self::PatternBlocked(_) => "Not found",
123 Self::MethodBlocked(_) => "Method not allowed",
124 Self::UpstreamConnectionFailed(_) => "Service unavailable",
125 Self::UpstreamTimeout(_) => "Gateway timeout",
126 Self::BodyTooLarge { .. } => "Request body too large",
127 Self::BodyReadError(_) => "Bad request",
128 Self::ClientBodyTimeout => "Request timeout",
129 Self::HttpClientError(_) => "Bad gateway",
130 }
131 }
132
133 pub fn is_server_error(&self) -> bool {
138 matches!(
139 self,
140 Self::ConfigError(_)
141 | Self::ProxyError(_)
142 | Self::UpstreamConnectionFailed(_)
143 | Self::UpstreamTimeout(_)
144 | Self::HttpClientError(_)
145 )
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use hyper::StatusCode;
153
154 #[test]
155 fn test_error_display() {
156 let err = WiseGateError::InvalidIp("192.168.1.999".into());
157 assert_eq!(err.to_string(), "Invalid IP address: 192.168.1.999");
158
159 let err = WiseGateError::RateLimitExceeded("10.0.0.1".into());
160 assert_eq!(err.to_string(), "Rate limit exceeded for IP: 10.0.0.1");
161
162 let err = WiseGateError::BodyTooLarge {
163 size: 200,
164 max: 100,
165 };
166 assert_eq!(
167 err.to_string(),
168 "Request body too large: 200 bytes (max: 100 bytes)"
169 );
170 }
171
172 #[test]
173 fn test_status_codes_all_variants() {
174 assert_eq!(
175 WiseGateError::InvalidIp("".into()).status_code(),
176 StatusCode::BAD_REQUEST
177 );
178 assert_eq!(
179 WiseGateError::ConfigError("".into()).status_code(),
180 StatusCode::INTERNAL_SERVER_ERROR
181 );
182 assert_eq!(
183 WiseGateError::ProxyError("".into()).status_code(),
184 StatusCode::BAD_GATEWAY
185 );
186 assert_eq!(
187 WiseGateError::RateLimitExceeded("".into()).status_code(),
188 StatusCode::TOO_MANY_REQUESTS
189 );
190 assert_eq!(
191 WiseGateError::IpBlocked("".into()).status_code(),
192 StatusCode::FORBIDDEN
193 );
194 assert_eq!(
195 WiseGateError::PatternBlocked("".into()).status_code(),
196 StatusCode::NOT_FOUND
197 );
198 assert_eq!(
199 WiseGateError::MethodBlocked("".into()).status_code(),
200 StatusCode::METHOD_NOT_ALLOWED
201 );
202 assert_eq!(
203 WiseGateError::UpstreamConnectionFailed("".into()).status_code(),
204 StatusCode::BAD_GATEWAY
205 );
206 assert_eq!(
207 WiseGateError::UpstreamTimeout("".into()).status_code(),
208 StatusCode::GATEWAY_TIMEOUT
209 );
210 assert_eq!(
211 WiseGateError::BodyTooLarge { size: 0, max: 0 }.status_code(),
212 StatusCode::PAYLOAD_TOO_LARGE
213 );
214 assert_eq!(
215 WiseGateError::BodyReadError("".into()).status_code(),
216 StatusCode::BAD_REQUEST
217 );
218 }
219
220 #[test]
221 fn test_user_messages_do_not_leak_internals() {
222 assert_eq!(
223 WiseGateError::ConfigError("database connection string".into()).user_message(),
224 "Internal server error"
225 );
226 assert_eq!(
227 WiseGateError::ProxyError("connection refused".into()).user_message(),
228 "Bad gateway"
229 );
230 assert_eq!(
231 WiseGateError::IpBlocked("10.0.0.1".into()).user_message(),
232 "Access denied"
233 );
234 assert_eq!(
235 WiseGateError::UpstreamConnectionFailed("".into()).user_message(),
236 "Service unavailable"
237 );
238 assert_eq!(
239 WiseGateError::BodyReadError("".into()).user_message(),
240 "Bad request"
241 );
242 }
243
244 #[test]
245 fn test_is_server_error_all_variants() {
246 assert!(WiseGateError::ConfigError("".into()).is_server_error());
248 assert!(WiseGateError::ProxyError("".into()).is_server_error());
249 assert!(WiseGateError::UpstreamConnectionFailed("".into()).is_server_error());
250 assert!(WiseGateError::UpstreamTimeout("".into()).is_server_error());
251
252 assert!(!WiseGateError::InvalidIp("".into()).is_server_error());
254 assert!(!WiseGateError::RateLimitExceeded("".into()).is_server_error());
255 assert!(!WiseGateError::IpBlocked("".into()).is_server_error());
256 assert!(!WiseGateError::PatternBlocked("".into()).is_server_error());
257 assert!(!WiseGateError::MethodBlocked("".into()).is_server_error());
258 assert!(!WiseGateError::BodyTooLarge { size: 0, max: 0 }.is_server_error());
259 assert!(!WiseGateError::BodyReadError("".into()).is_server_error());
260 }
261}