1use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, WiseGateError>;
10
11#[derive(Debug, Error)]
29pub enum WiseGateError {
30 #[error("Invalid IP address: {0}")]
32 InvalidIp(String),
33
34 #[error("Configuration error: {0}")]
36 ConfigError(String),
37
38 #[error("Proxy error: {0}")]
40 ProxyError(String),
41
42 #[error("Rate limit exceeded for IP: {0}")]
44 RateLimitExceeded(String),
45
46 #[error("IP blocked: {0}")]
48 IpBlocked(String),
49
50 #[error("URL pattern blocked: {0}")]
52 PatternBlocked(String),
53
54 #[error("HTTP method blocked: {0}")]
56 MethodBlocked(String),
57
58 #[error("Authentication required")]
60 AuthenticationRequired,
61
62 #[error("Invalid credentials")]
64 InvalidCredentials,
65
66 #[error("Upstream connection failed: {0}")]
68 UpstreamConnectionFailed(String),
69
70 #[error("Upstream timeout: {0}")]
72 UpstreamTimeout(String),
73
74 #[error("Request body too large: {size} bytes (max: {max} bytes)")]
76 BodyTooLarge {
77 size: usize,
79 max: usize,
81 },
82
83 #[error("Body read error: {0}")]
85 BodyReadError(String),
86
87 #[error("HTTP client error: {0}")]
89 HttpClientError(#[from] reqwest::Error),
90
91 #[error("Invalid header: {0}")]
93 InvalidHeader(String),
94}
95
96impl WiseGateError {
97 pub fn status_code(&self) -> hyper::StatusCode {
103 use hyper::StatusCode;
104
105 match self {
106 Self::InvalidIp(_) => StatusCode::BAD_REQUEST,
107 Self::ConfigError(_) => StatusCode::INTERNAL_SERVER_ERROR,
108 Self::ProxyError(_) => StatusCode::BAD_GATEWAY,
109 Self::RateLimitExceeded(_) => StatusCode::TOO_MANY_REQUESTS,
110 Self::IpBlocked(_) => StatusCode::FORBIDDEN,
111 Self::PatternBlocked(_) => StatusCode::NOT_FOUND,
112 Self::MethodBlocked(_) => StatusCode::METHOD_NOT_ALLOWED,
113 Self::AuthenticationRequired => StatusCode::UNAUTHORIZED,
114 Self::InvalidCredentials => StatusCode::UNAUTHORIZED,
115 Self::UpstreamConnectionFailed(_) => StatusCode::BAD_GATEWAY,
116 Self::UpstreamTimeout(_) => StatusCode::GATEWAY_TIMEOUT,
117 Self::BodyTooLarge { .. } => StatusCode::PAYLOAD_TOO_LARGE,
118 Self::BodyReadError(_) => StatusCode::BAD_REQUEST,
119 Self::HttpClientError(_) => StatusCode::BAD_GATEWAY,
120 Self::InvalidHeader(_) => StatusCode::BAD_REQUEST,
121 }
122 }
123
124 pub fn user_message(&self) -> &str {
129 match self {
130 Self::InvalidIp(_) => "Invalid request",
131 Self::ConfigError(_) => "Internal server error",
132 Self::ProxyError(_) => "Bad gateway",
133 Self::RateLimitExceeded(_) => "Rate limit exceeded",
134 Self::IpBlocked(_) => "Access denied",
135 Self::PatternBlocked(_) => "Not found",
136 Self::MethodBlocked(_) => "Method not allowed",
137 Self::AuthenticationRequired => "Unauthorized",
138 Self::InvalidCredentials => "Unauthorized",
139 Self::UpstreamConnectionFailed(_) => "Service unavailable",
140 Self::UpstreamTimeout(_) => "Gateway timeout",
141 Self::BodyTooLarge { .. } => "Request body too large",
142 Self::BodyReadError(_) => "Bad request",
143 Self::HttpClientError(_) => "Bad gateway",
144 Self::InvalidHeader(_) => "Bad request",
145 }
146 }
147
148 pub fn is_server_error(&self) -> bool {
153 matches!(
154 self,
155 Self::ConfigError(_)
156 | Self::ProxyError(_)
157 | Self::UpstreamConnectionFailed(_)
158 | Self::UpstreamTimeout(_)
159 | Self::HttpClientError(_)
160 )
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use hyper::StatusCode;
168
169 #[test]
170 fn test_error_display() {
171 let err = WiseGateError::InvalidIp("192.168.1.999".into());
172 assert_eq!(err.to_string(), "Invalid IP address: 192.168.1.999");
173
174 let err = WiseGateError::RateLimitExceeded("10.0.0.1".into());
175 assert_eq!(err.to_string(), "Rate limit exceeded for IP: 10.0.0.1");
176
177 let err = WiseGateError::BodyTooLarge {
178 size: 200,
179 max: 100,
180 };
181 assert_eq!(
182 err.to_string(),
183 "Request body too large: 200 bytes (max: 100 bytes)"
184 );
185 }
186
187 #[test]
188 fn test_status_codes() {
189 assert_eq!(
190 WiseGateError::InvalidIp("".into()).status_code(),
191 StatusCode::BAD_REQUEST
192 );
193 assert_eq!(
194 WiseGateError::RateLimitExceeded("".into()).status_code(),
195 StatusCode::TOO_MANY_REQUESTS
196 );
197 assert_eq!(
198 WiseGateError::IpBlocked("".into()).status_code(),
199 StatusCode::FORBIDDEN
200 );
201 assert_eq!(
202 WiseGateError::PatternBlocked("".into()).status_code(),
203 StatusCode::NOT_FOUND
204 );
205 assert_eq!(
206 WiseGateError::MethodBlocked("".into()).status_code(),
207 StatusCode::METHOD_NOT_ALLOWED
208 );
209 assert_eq!(
210 WiseGateError::UpstreamTimeout("".into()).status_code(),
211 StatusCode::GATEWAY_TIMEOUT
212 );
213 assert_eq!(
214 WiseGateError::BodyTooLarge { size: 0, max: 0 }.status_code(),
215 StatusCode::PAYLOAD_TOO_LARGE
216 );
217 }
218
219 #[test]
220 fn test_user_messages() {
221 assert_eq!(
222 WiseGateError::ConfigError("secret".into()).user_message(),
223 "Internal server error"
224 );
225 assert_eq!(
226 WiseGateError::IpBlocked("10.0.0.1".into()).user_message(),
227 "Access denied"
228 );
229 }
230
231 #[test]
232 fn test_is_server_error() {
233 assert!(WiseGateError::ConfigError("".into()).is_server_error());
234 assert!(WiseGateError::UpstreamConnectionFailed("".into()).is_server_error());
235 assert!(WiseGateError::UpstreamTimeout("".into()).is_server_error());
236
237 assert!(!WiseGateError::RateLimitExceeded("".into()).is_server_error());
238 assert!(!WiseGateError::IpBlocked("".into()).is_server_error());
239 assert!(!WiseGateError::MethodBlocked("".into()).is_server_error());
240 }
241}