Skip to main content

amaters_net/
error.rs

1//! Error types for the network layer
2
3use thiserror::Error;
4use tonic::{Code, Status};
5
6use crate::proto::errors::{ErrorCategory, ErrorCode};
7
8/// Network layer result type
9pub type NetResult<T> = Result<T, NetError>;
10
11/// Network layer errors
12#[derive(Debug, Error)]
13pub enum NetError {
14    /// Network timeout error
15    #[error("Network timeout: {0}")]
16    Timeout(String),
17
18    /// Connection refused
19    #[error("Connection refused: {0}")]
20    ConnectionRefused(String),
21
22    /// Connection reset
23    #[error("Connection reset: {0}")]
24    ConnectionReset(String),
25
26    /// DNS resolution failed
27    #[error("DNS resolution failed: {0}")]
28    DnsFailure(String),
29
30    /// TLS handshake failed
31    #[error("TLS handshake failed: {0}")]
32    TlsHandshake(String),
33
34    /// Invalid request
35    #[error("Invalid request: {0}")]
36    InvalidRequest(String),
37
38    /// Unsupported protocol version
39    #[error("Unsupported protocol version: {0}")]
40    UnsupportedVersion(String),
41
42    /// Malformed message
43    #[error("Malformed message: {0}")]
44    MalformedMessage(String),
45
46    /// Missing required field
47    #[error("Missing required field: {0}")]
48    MissingField(String),
49
50    /// Authentication failed
51    #[error("Authentication failed: {0}")]
52    AuthFailed(String),
53
54    /// Authentication expired
55    #[error("Authentication expired: {0}")]
56    AuthExpired(String),
57
58    /// Insufficient permissions
59    #[error("Insufficient permissions: {0}")]
60    InsufficientPermissions(String),
61
62    /// Invalid certificate
63    #[error("Invalid certificate: {0}")]
64    InvalidCertificate(String),
65
66    /// TLS configuration error
67    #[error("TLS error: {0}")]
68    TlsError(String),
69
70    /// Storage error from amaters-core
71    #[error("Storage error: {0}")]
72    Storage(#[from] amaters_core::error::AmateRSError),
73
74    /// Server internal error
75    #[error("Server internal error: {0}")]
76    ServerInternal(String),
77
78    /// Server unavailable
79    #[error("Server unavailable: {0}")]
80    ServerUnavailable(String),
81
82    /// Rate limit exceeded
83    #[error("Rate limit exceeded: {0}")]
84    RateLimitExceeded(#[from] crate::rate_limiter::RateLimitError),
85
86    /// Server overloaded
87    #[error("Server overloaded: {0}")]
88    ServerOverloaded(String),
89
90    /// Server shutting down
91    #[error("Server shutting down: {0}")]
92    ServerShuttingDown(String),
93
94    /// gRPC transport error
95    #[error("gRPC transport error: {0}")]
96    Transport(#[from] tonic::transport::Error),
97
98    /// gRPC status error
99    #[error("gRPC status error: {0}")]
100    GrpcStatus(String),
101
102    /// Unknown error
103    #[error("Unknown error: {0}")]
104    Unknown(String),
105}
106
107impl NetError {
108    /// Get the error code for this error
109    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    /// Get the error category for this error
138    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    /// Check if this error is retryable
159    pub fn is_retryable(&self) -> bool {
160        matches!(self.error_category(), ErrorCategory::CategoryRetryable)
161    }
162}
163
164/// Convert NetError to gRPC Status
165impl 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
194/// Convert gRPC Status to NetError
195impl 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}