1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, ZeroTrustError>;
7
8#[derive(Error, Debug)]
10pub enum ZeroTrustError {
11 #[error("API request failed: {0}")]
13 ApiError(#[from] reqwest::Error),
14
15 #[error("Serialization error: {0}")]
17 SerializationError(#[from] serde_json::Error),
18
19 #[error("IO error: {0}")]
21 IoError(#[from] std::io::Error),
22
23 #[cfg(feature = "migration")]
25 #[error("Database error: {0}")]
26 DatabaseError(#[from] sqlx::Error),
27
28 #[error("Configuration error: {0}")]
30 ConfigError(String),
31
32 #[error("Authentication failed: {0}")]
34 AuthError(String),
35
36 #[error("Validation error: {0}")]
38 ValidationError(String),
39
40 #[cfg(feature = "migration")]
42 #[error("Migration error: {0}")]
43 MigrationError(String),
44
45 #[cfg(feature = "sync")]
47 #[error("Sync error: {0}")]
48 SyncError(String),
49
50 #[error("Request timed out")]
52 Timeout,
53
54 #[error("Server error: {status} - {message}")]
56 ServerError {
57 status: u16,
59 message: String,
61 },
62
63 #[error("Client error: {status} - {message}")]
65 ClientError {
66 status: u16,
68 message: String,
70 },
71
72 #[error("Resource not found: {resource}")]
74 NotFound {
75 resource: String,
77 },
78
79 #[error("Permission denied: {operation}")]
81 PermissionDenied {
82 operation: String,
84 },
85
86 #[error("Rate limit exceeded. Try again in {retry_after} seconds")]
88 RateLimitExceeded {
89 retry_after: u64,
91 },
92
93 #[error("{0}")]
95 Generic(String),
96}
97
98impl ZeroTrustError {
99 pub fn config<S: Into<String>>(message: S) -> Self {
101 Self::ConfigError(message.into())
102 }
103
104 pub fn auth<S: Into<String>>(message: S) -> Self {
106 Self::AuthError(message.into())
107 }
108
109 pub fn validation<S: Into<String>>(message: S) -> Self {
111 Self::ValidationError(message.into())
112 }
113
114 pub fn generic<S: Into<String>>(message: S) -> Self {
116 Self::Generic(message.into())
117 }
118
119 pub fn not_found<S: Into<String>>(resource: S) -> Self {
121 Self::NotFound {
122 resource: resource.into(),
123 }
124 }
125
126 pub fn permission_denied<S: Into<String>>(operation: S) -> Self {
128 Self::PermissionDenied {
129 operation: operation.into(),
130 }
131 }
132
133 pub fn server_error(status: u16, message: String) -> Self {
135 Self::ServerError { status, message }
136 }
137
138 pub fn client_error(status: u16, message: String) -> Self {
140 Self::ClientError { status, message }
141 }
142
143 pub fn rate_limit(retry_after: u64) -> Self {
145 Self::RateLimitExceeded { retry_after }
146 }
147
148 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 pub fn is_auth_error(&self) -> bool {
161 matches!(self, Self::AuthError(_) | Self::PermissionDenied { .. })
162 }
163
164 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}