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("HTTP client error: {0}")]
78 HttpClientError(#[from] reqwest::Error),
79}
80
81impl WiseGateError {
82 pub fn status_code(&self) -> hyper::StatusCode {
88 use hyper::StatusCode;
89
90 match self {
91 Self::InvalidIp(_) => StatusCode::BAD_REQUEST,
92 Self::ConfigError(_) => StatusCode::INTERNAL_SERVER_ERROR,
93 Self::ProxyError(_) => StatusCode::BAD_GATEWAY,
94 Self::RateLimitExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
95 Self::IpBlocked(_) => StatusCode::FORBIDDEN,
96 Self::PatternBlocked(_) => StatusCode::NOT_FOUND,
97 Self::MethodBlocked(_) => StatusCode::METHOD_NOT_ALLOWED,
98 Self::UpstreamConnectionFailed(_) => StatusCode::BAD_GATEWAY,
99 Self::UpstreamTimeout(_) => StatusCode::GATEWAY_TIMEOUT,
100 Self::BodyTooLarge { .. } => StatusCode::PAYLOAD_TOO_LARGE,
101 Self::BodyReadError(_) => StatusCode::BAD_REQUEST,
102 Self::HttpClientError(_) => StatusCode::BAD_GATEWAY,
103 }
104 }
105
106 pub fn user_message(&self) -> &str {
111 match self {
112 Self::InvalidIp(_) => "Invalid request",
113 Self::ConfigError(_) => "Internal server error",
114 Self::ProxyError(_) => "Bad gateway",
115 Self::RateLimitExceeded(_) => "Rate limit exceeded",
116 Self::IpBlocked(_) => "Access denied",
117 Self::PatternBlocked(_) => "Not found",
118 Self::MethodBlocked(_) => "Method not allowed",
119 Self::UpstreamConnectionFailed(_) => "Service unavailable",
120 Self::UpstreamTimeout(_) => "Gateway timeout",
121 Self::BodyTooLarge { .. } => "Request body too large",
122 Self::BodyReadError(_) => "Bad request",
123 Self::HttpClientError(_) => "Bad gateway",
124 }
125 }
126
127 pub fn is_server_error(&self) -> bool {
132 matches!(
133 self,
134 Self::ConfigError(_)
135 | Self::ProxyError(_)
136 | Self::UpstreamConnectionFailed(_)
137 | Self::UpstreamTimeout(_)
138 | Self::HttpClientError(_)
139 )
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use hyper::StatusCode;
147
148 #[test]
149 fn test_error_display() {
150 let err = WiseGateError::InvalidIp("192.168.1.999".into());
151 assert_eq!(err.to_string(), "Invalid IP address: 192.168.1.999");
152
153 let err = WiseGateError::RateLimitExceeded("10.0.0.1".into());
154 assert_eq!(err.to_string(), "Rate limit exceeded for IP: 10.0.0.1");
155
156 let err = WiseGateError::BodyTooLarge {
157 size: 200,
158 max: 100,
159 };
160 assert_eq!(
161 err.to_string(),
162 "Request body too large: 200 bytes (max: 100 bytes)"
163 );
164 }
165
166 #[test]
167 fn test_status_codes_all_variants() {
168 assert_eq!(
169 WiseGateError::InvalidIp("".into()).status_code(),
170 StatusCode::BAD_REQUEST
171 );
172 assert_eq!(
173 WiseGateError::ConfigError("".into()).status_code(),
174 StatusCode::INTERNAL_SERVER_ERROR
175 );
176 assert_eq!(
177 WiseGateError::ProxyError("".into()).status_code(),
178 StatusCode::BAD_GATEWAY
179 );
180 assert_eq!(
181 WiseGateError::RateLimitExceeded("".into()).status_code(),
182 StatusCode::TOO_MANY_REQUESTS
183 );
184 assert_eq!(
185 WiseGateError::IpBlocked("".into()).status_code(),
186 StatusCode::FORBIDDEN
187 );
188 assert_eq!(
189 WiseGateError::PatternBlocked("".into()).status_code(),
190 StatusCode::NOT_FOUND
191 );
192 assert_eq!(
193 WiseGateError::MethodBlocked("".into()).status_code(),
194 StatusCode::METHOD_NOT_ALLOWED
195 );
196 assert_eq!(
197 WiseGateError::UpstreamConnectionFailed("".into()).status_code(),
198 StatusCode::BAD_GATEWAY
199 );
200 assert_eq!(
201 WiseGateError::UpstreamTimeout("".into()).status_code(),
202 StatusCode::GATEWAY_TIMEOUT
203 );
204 assert_eq!(
205 WiseGateError::BodyTooLarge { size: 0, max: 0 }.status_code(),
206 StatusCode::PAYLOAD_TOO_LARGE
207 );
208 assert_eq!(
209 WiseGateError::BodyReadError("".into()).status_code(),
210 StatusCode::BAD_REQUEST
211 );
212 }
213
214 #[test]
215 fn test_user_messages_do_not_leak_internals() {
216 assert_eq!(
217 WiseGateError::ConfigError("database connection string".into()).user_message(),
218 "Internal server error"
219 );
220 assert_eq!(
221 WiseGateError::ProxyError("connection refused".into()).user_message(),
222 "Bad gateway"
223 );
224 assert_eq!(
225 WiseGateError::IpBlocked("10.0.0.1".into()).user_message(),
226 "Access denied"
227 );
228 assert_eq!(
229 WiseGateError::UpstreamConnectionFailed("".into()).user_message(),
230 "Service unavailable"
231 );
232 assert_eq!(
233 WiseGateError::BodyReadError("".into()).user_message(),
234 "Bad request"
235 );
236 }
237
238 #[test]
239 fn test_is_server_error_all_variants() {
240 assert!(WiseGateError::ConfigError("".into()).is_server_error());
242 assert!(WiseGateError::ProxyError("".into()).is_server_error());
243 assert!(WiseGateError::UpstreamConnectionFailed("".into()).is_server_error());
244 assert!(WiseGateError::UpstreamTimeout("".into()).is_server_error());
245
246 assert!(!WiseGateError::InvalidIp("".into()).is_server_error());
248 assert!(!WiseGateError::RateLimitExceeded("".into()).is_server_error());
249 assert!(!WiseGateError::IpBlocked("".into()).is_server_error());
250 assert!(!WiseGateError::PatternBlocked("".into()).is_server_error());
251 assert!(!WiseGateError::MethodBlocked("".into()).is_server_error());
252 assert!(!WiseGateError::BodyTooLarge { size: 0, max: 0 }.is_server_error());
253 assert!(!WiseGateError::BodyReadError("".into()).is_server_error());
254 }
255}