use thiserror::Error;
pub type Result<T> = std::result::Result<T, ZeroTrustError>;
#[derive(Error, Debug)]
pub enum ZeroTrustError {
#[error("API request failed: {0}")]
ApiError(#[from] reqwest::Error),
#[error("Serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[cfg(feature = "migration")]
#[error("Database error: {0}")]
DatabaseError(#[from] sqlx::Error),
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Authentication failed: {0}")]
AuthError(String),
#[error("Validation error: {0}")]
ValidationError(String),
#[cfg(feature = "migration")]
#[error("Migration error: {0}")]
MigrationError(String),
#[cfg(feature = "sync")]
#[error("Sync error: {0}")]
SyncError(String),
#[error("Request timed out")]
Timeout,
#[error("Server error: {status} - {message}")]
ServerError {
status: u16,
message: String,
},
#[error("Client error: {status} - {message}")]
ClientError {
status: u16,
message: String,
},
#[error("Resource not found: {resource}")]
NotFound {
resource: String,
},
#[error("Permission denied: {operation}")]
PermissionDenied {
operation: String,
},
#[error("Rate limit exceeded. Try again in {retry_after} seconds")]
RateLimitExceeded {
retry_after: u64,
},
#[error("{0}")]
Generic(String),
}
impl ZeroTrustError {
pub fn config<S: Into<String>>(message: S) -> Self {
Self::ConfigError(message.into())
}
pub fn auth<S: Into<String>>(message: S) -> Self {
Self::AuthError(message.into())
}
pub fn validation<S: Into<String>>(message: S) -> Self {
Self::ValidationError(message.into())
}
pub fn generic<S: Into<String>>(message: S) -> Self {
Self::Generic(message.into())
}
pub fn not_found<S: Into<String>>(resource: S) -> Self {
Self::NotFound {
resource: resource.into(),
}
}
pub fn permission_denied<S: Into<String>>(operation: S) -> Self {
Self::PermissionDenied {
operation: operation.into(),
}
}
pub fn server_error(status: u16, message: String) -> Self {
Self::ServerError { status, message }
}
pub fn client_error(status: u16, message: String) -> Self {
Self::ClientError { status, message }
}
pub fn rate_limit(retry_after: u64) -> Self {
Self::RateLimitExceeded { retry_after }
}
pub fn is_retryable(&self) -> bool {
match self {
Self::ApiError(e) => e.is_timeout() || e.is_connect(),
Self::ServerError { status, .. } => *status >= 500,
Self::Timeout => true,
Self::RateLimitExceeded { .. } => true,
_ => false,
}
}
pub fn is_auth_error(&self) -> bool {
matches!(self, Self::AuthError(_) | Self::PermissionDenied { .. })
}
pub fn is_client_error(&self) -> bool {
matches!(
self,
Self::ValidationError(_)
| Self::ConfigError(_)
| Self::ClientError { .. }
| Self::NotFound { .. }
| Self::PermissionDenied { .. }
)
}
}
impl From<anyhow::Error> for ZeroTrustError {
fn from(err: anyhow::Error) -> Self {
Self::Generic(err.to_string())
}
}
impl From<base64::DecodeError> for ZeroTrustError {
fn from(err: base64::DecodeError) -> Self {
Self::Generic(format!("Base64 decode error: {}", err))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = ZeroTrustError::auth("Invalid credentials");
assert!(err.is_auth_error());
assert!(!err.is_retryable());
let err = ZeroTrustError::server_error(500, "Internal error".to_string());
assert!(err.is_retryable());
assert!(!err.is_client_error());
let err = ZeroTrustError::validation("Invalid input");
assert!(err.is_client_error());
assert!(!err.is_retryable());
}
}