1use thiserror::Error;
4use tonic::{Code, Status};
5
6use crate::proto::errors::{ErrorCategory, ErrorCode};
7
8pub type NetResult<T> = Result<T, NetError>;
10
11#[derive(Debug, Error)]
13pub enum NetError {
14 #[error("Network timeout: {0}")]
16 Timeout(String),
17
18 #[error("Connection refused: {0}")]
20 ConnectionRefused(String),
21
22 #[error("Connection reset: {0}")]
24 ConnectionReset(String),
25
26 #[error("DNS resolution failed: {0}")]
28 DnsFailure(String),
29
30 #[error("TLS handshake failed: {0}")]
32 TlsHandshake(String),
33
34 #[error("Invalid request: {0}")]
36 InvalidRequest(String),
37
38 #[error("Unsupported protocol version: {0}")]
40 UnsupportedVersion(String),
41
42 #[error("Malformed message: {0}")]
44 MalformedMessage(String),
45
46 #[error("Missing required field: {0}")]
48 MissingField(String),
49
50 #[error("Authentication failed: {0}")]
52 AuthFailed(String),
53
54 #[error("Authentication expired: {0}")]
56 AuthExpired(String),
57
58 #[error("Insufficient permissions: {0}")]
60 InsufficientPermissions(String),
61
62 #[error("Invalid certificate: {0}")]
64 InvalidCertificate(String),
65
66 #[error("TLS error: {0}")]
68 TlsError(String),
69
70 #[error("Storage error: {0}")]
72 Storage(#[from] amaters_core::error::AmateRSError),
73
74 #[error("Server internal error: {0}")]
76 ServerInternal(String),
77
78 #[error("Server unavailable: {0}")]
80 ServerUnavailable(String),
81
82 #[error("Rate limit exceeded: {0}")]
84 RateLimitExceeded(#[from] crate::rate_limiter::RateLimitError),
85
86 #[error("Server overloaded: {0}")]
88 ServerOverloaded(String),
89
90 #[error("Server shutting down: {0}")]
92 ServerShuttingDown(String),
93
94 #[error("gRPC transport error: {0}")]
96 Transport(#[from] tonic::transport::Error),
97
98 #[error("gRPC status error: {0}")]
100 GrpcStatus(String),
101
102 #[error("Unknown error: {0}")]
104 Unknown(String),
105}
106
107impl NetError {
108 pub fn error_code(&self) -> ErrorCode {
110 match self {
111 NetError::Timeout(_) => ErrorCode::ErrorNetworkTimeout,
112 NetError::ConnectionRefused(_) => ErrorCode::ErrorNetworkConnectionRefused,
113 NetError::ConnectionReset(_) => ErrorCode::ErrorNetworkConnectionReset,
114 NetError::DnsFailure(_) => ErrorCode::ErrorNetworkDnsFailed,
115 NetError::TlsHandshake(_) => ErrorCode::ErrorNetworkTlsHandshake,
116 NetError::InvalidRequest(_) => ErrorCode::ErrorProtocolInvalidRequest,
117 NetError::UnsupportedVersion(_) => ErrorCode::ErrorProtocolUnsupportedVersion,
118 NetError::MalformedMessage(_) => ErrorCode::ErrorProtocolMalformedMessage,
119 NetError::MissingField(_) => ErrorCode::ErrorProtocolMissingField,
120 NetError::AuthFailed(_) => ErrorCode::ErrorAuthFailed,
121 NetError::AuthExpired(_) => ErrorCode::ErrorAuthExpired,
122 NetError::InsufficientPermissions(_) => ErrorCode::ErrorAuthInsufficientPermissions,
123 NetError::InvalidCertificate(_) | NetError::TlsError(_) => {
124 ErrorCode::ErrorAuthInvalidCertificate
125 }
126 NetError::RateLimitExceeded(_) => ErrorCode::ErrorServerOverloaded,
127 NetError::Storage(_) => ErrorCode::ErrorStorageIo,
128 NetError::ServerInternal(_) => ErrorCode::ErrorServerInternal,
129 NetError::ServerUnavailable(_) => ErrorCode::ErrorServerUnavailable,
130 NetError::ServerOverloaded(_) => ErrorCode::ErrorServerOverloaded,
131 NetError::ServerShuttingDown(_) => ErrorCode::ErrorServerShuttingDown,
132 NetError::Transport(_) | NetError::GrpcStatus(_) => ErrorCode::ErrorNetworkTimeout,
133 NetError::Unknown(_) => ErrorCode::ErrorUnknown,
134 }
135 }
136
137 pub fn error_category(&self) -> ErrorCategory {
139 match self {
140 NetError::Timeout(_)
141 | NetError::ConnectionRefused(_)
142 | NetError::ConnectionReset(_)
143 | NetError::ServerUnavailable(_)
144 | NetError::ServerOverloaded(_) => ErrorCategory::CategoryRetryable,
145 NetError::RateLimitExceeded(_) => ErrorCategory::CategoryRetryable,
146 NetError::AuthFailed(_) | NetError::AuthExpired(_) => ErrorCategory::CategoryAuth,
147 NetError::InvalidRequest(_)
148 | NetError::MalformedMessage(_)
149 | NetError::MissingField(_)
150 | NetError::InsufficientPermissions(_) => ErrorCategory::CategoryClientError,
151 NetError::ServerInternal(_) | NetError::ServerShuttingDown(_) => {
152 ErrorCategory::CategoryServerError
153 }
154 _ => ErrorCategory::CategoryNonRetryable,
155 }
156 }
157
158 pub fn is_retryable(&self) -> bool {
160 matches!(self.error_category(), ErrorCategory::CategoryRetryable)
161 }
162}
163
164impl From<NetError> for Status {
166 fn from(err: NetError) -> Self {
167 let code = match &err {
168 NetError::Timeout(_) => Code::DeadlineExceeded,
169 NetError::ConnectionRefused(_) | NetError::ConnectionReset(_) => Code::Unavailable,
170 NetError::DnsFailure(_) | NetError::TlsHandshake(_) => Code::Unavailable,
171 NetError::InvalidRequest(_)
172 | NetError::MalformedMessage(_)
173 | NetError::MissingField(_) => Code::InvalidArgument,
174 NetError::UnsupportedVersion(_) => Code::Unimplemented,
175 NetError::AuthFailed(_) | NetError::InvalidCertificate(_) | NetError::TlsError(_) => {
176 Code::Unauthenticated
177 }
178 NetError::AuthExpired(_) => Code::Unauthenticated,
179 NetError::InsufficientPermissions(_) => Code::PermissionDenied,
180 NetError::RateLimitExceeded(_) => Code::ResourceExhausted,
181 NetError::Storage(_) => Code::Internal,
182 NetError::ServerInternal(_) => Code::Internal,
183 NetError::ServerUnavailable(_) | NetError::ServerOverloaded(_) => Code::Unavailable,
184 NetError::ServerShuttingDown(_) => Code::Unavailable,
185 NetError::Transport(_) => Code::Unavailable,
186 NetError::GrpcStatus(_) => Code::Unknown,
187 NetError::Unknown(_) => Code::Unknown,
188 };
189
190 Status::new(code, err.to_string())
191 }
192}
193
194impl From<Status> for NetError {
196 fn from(status: Status) -> Self {
197 match status.code() {
198 Code::DeadlineExceeded => NetError::Timeout(status.message().to_string()),
199 Code::Unavailable => NetError::ServerUnavailable(status.message().to_string()),
200 Code::InvalidArgument => NetError::InvalidRequest(status.message().to_string()),
201 Code::Unimplemented => NetError::UnsupportedVersion(status.message().to_string()),
202 Code::Unauthenticated => NetError::AuthFailed(status.message().to_string()),
203 Code::PermissionDenied => {
204 NetError::InsufficientPermissions(status.message().to_string())
205 }
206 Code::Internal => NetError::ServerInternal(status.message().to_string()),
207 _ => NetError::Unknown(status.message().to_string()),
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_error_code_mapping() {
218 let err = NetError::Timeout("test".to_string());
219 assert_eq!(err.error_code(), ErrorCode::ErrorNetworkTimeout);
220
221 let err = NetError::AuthFailed("test".to_string());
222 assert_eq!(err.error_code(), ErrorCode::ErrorAuthFailed);
223 }
224
225 #[test]
226 fn test_error_category() {
227 let err = NetError::Timeout("test".to_string());
228 assert_eq!(err.error_category(), ErrorCategory::CategoryRetryable);
229 assert!(err.is_retryable());
230
231 let err = NetError::InvalidRequest("test".to_string());
232 assert_eq!(err.error_category(), ErrorCategory::CategoryClientError);
233 assert!(!err.is_retryable());
234 }
235
236 #[test]
237 fn test_status_conversion() {
238 let err = NetError::Timeout("timeout".to_string());
239 let status: Status = err.into();
240 assert_eq!(status.code(), Code::DeadlineExceeded);
241 }
242
243 #[test]
244 fn test_status_from_error() {
245 let err = NetError::Timeout("timeout".to_string());
246 let status: Status = err.into();
247 assert_eq!(status.code(), Code::DeadlineExceeded);
248
249 let err2 = NetError::GrpcStatus("grpc error".to_string());
250 let status2: Status = err2.into();
251 assert_eq!(status2.code(), Code::Unknown);
252 }
253}