zero_trust_sdk/
error.rs

1//! Error types for the Zero Trust SDK
2
3use thiserror::Error;
4
5/// Result type alias for Zero Trust SDK operations
6pub type Result<T> = std::result::Result<T, ZeroTrustError>;
7
8/// Main error type for the Zero Trust SDK
9#[derive(Error, Debug)]
10pub enum ZeroTrustError {
11    /// HTTP request errors
12    #[error("API request failed: {0}")]
13    ApiError(#[from] reqwest::Error),
14    
15    /// JSON serialization/deserialization errors
16    #[error("Serialization error: {0}")]
17    SerializationError(#[from] serde_json::Error),
18    
19    /// IO errors
20    #[error("IO error: {0}")]
21    IoError(#[from] std::io::Error),
22    
23    /// Database errors (when migration feature is enabled)
24    #[cfg(feature = "migration")]
25    #[error("Database error: {0}")]
26    DatabaseError(#[from] sqlx::Error),
27    
28    /// Configuration errors
29    #[error("Configuration error: {0}")]
30    ConfigError(String),
31    
32    /// Authentication errors
33    #[error("Authentication failed: {0}")]
34    AuthError(String),
35    
36    /// Validation errors
37    #[error("Validation error: {0}")]
38    ValidationError(String),
39    
40    /// Migration errors
41    #[cfg(feature = "migration")]
42    #[error("Migration error: {0}")]
43    MigrationError(String),
44    
45    /// Sync errors
46    #[cfg(feature = "sync")]
47    #[error("Sync error: {0}")]
48    SyncError(String),
49    
50    /// Network timeout
51    #[error("Request timed out")]
52    Timeout,
53    
54    /// Server errors (5xx)
55    #[error("Server error: {status} - {message}")]
56    ServerError {
57        /// HTTP status code
58        status: u16,
59        /// Error message
60        message: String,
61    },
62    
63    /// Client errors (4xx)
64    #[error("Client error: {status} - {message}")]
65    ClientError {
66        /// HTTP status code
67        status: u16,
68        /// Error message
69        message: String,
70    },
71    
72    /// Resource not found
73    #[error("Resource not found: {resource}")]
74    NotFound {
75        /// Resource identifier
76        resource: String,
77    },
78    
79    /// Permission denied
80    #[error("Permission denied: {operation}")]
81    PermissionDenied {
82        /// Operation that was denied
83        operation: String,
84    },
85    
86    /// Rate limit exceeded
87    #[error("Rate limit exceeded. Try again in {retry_after} seconds")]
88    RateLimitExceeded {
89        /// Seconds to wait before retrying
90        retry_after: u64,
91    },
92    
93    /// Generic error
94    #[error("{0}")]
95    Generic(String),
96}
97
98impl ZeroTrustError {
99    /// Create a new configuration error
100    pub fn config<S: Into<String>>(message: S) -> Self {
101        Self::ConfigError(message.into())
102    }
103    
104    /// Create a new authentication error
105    pub fn auth<S: Into<String>>(message: S) -> Self {
106        Self::AuthError(message.into())
107    }
108    
109    /// Create a new validation error
110    pub fn validation<S: Into<String>>(message: S) -> Self {
111        Self::ValidationError(message.into())
112    }
113    
114    /// Create a new generic error
115    pub fn generic<S: Into<String>>(message: S) -> Self {
116        Self::Generic(message.into())
117    }
118    
119    /// Create a new not found error
120    pub fn not_found<S: Into<String>>(resource: S) -> Self {
121        Self::NotFound {
122            resource: resource.into(),
123        }
124    }
125    
126    /// Create a new permission denied error
127    pub fn permission_denied<S: Into<String>>(operation: S) -> Self {
128        Self::PermissionDenied {
129            operation: operation.into(),
130        }
131    }
132    
133    /// Create a server error from HTTP status and message
134    pub fn server_error(status: u16, message: String) -> Self {
135        Self::ServerError { status, message }
136    }
137    
138    /// Create a client error from HTTP status and message
139    pub fn client_error(status: u16, message: String) -> Self {
140        Self::ClientError { status, message }
141    }
142    
143    /// Create a rate limit error
144    pub fn rate_limit(retry_after: u64) -> Self {
145        Self::RateLimitExceeded { retry_after }
146    }
147    
148    /// Check if this is a retryable error
149    pub fn is_retryable(&self) -> bool {
150        match self {
151            Self::ApiError(e) => e.is_timeout() || e.is_connect(),
152            Self::ServerError { status, .. } => *status >= 500,
153            Self::Timeout => true,
154            Self::RateLimitExceeded { .. } => true,
155            _ => false,
156        }
157    }
158    
159    /// Check if this is an authentication error
160    pub fn is_auth_error(&self) -> bool {
161        matches!(self, Self::AuthError(_) | Self::PermissionDenied { .. })
162    }
163    
164    /// Check if this is a client error (user's fault)
165    pub fn is_client_error(&self) -> bool {
166        matches!(
167            self,
168            Self::ValidationError(_)
169                | Self::ConfigError(_)
170                | Self::ClientError { .. }
171                | Self::NotFound { .. }
172                | Self::PermissionDenied { .. }
173        )
174    }
175}
176
177impl From<anyhow::Error> for ZeroTrustError {
178    fn from(err: anyhow::Error) -> Self {
179        Self::Generic(err.to_string())
180    }
181}
182
183impl From<base64::DecodeError> for ZeroTrustError {
184    fn from(err: base64::DecodeError) -> Self {
185        Self::Generic(format!("Base64 decode error: {}", err))
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    
193    #[test]
194    fn test_error_creation() {
195        let err = ZeroTrustError::auth("Invalid credentials");
196        assert!(err.is_auth_error());
197        assert!(!err.is_retryable());
198        
199        let err = ZeroTrustError::server_error(500, "Internal error".to_string());
200        assert!(err.is_retryable());
201        assert!(!err.is_client_error());
202        
203        let err = ZeroTrustError::validation("Invalid input");
204        assert!(err.is_client_error());
205        assert!(!err.is_retryable());
206    }
207}