use std::time::Duration;
use thiserror::Error;
pub type ZitiResult<T> = Result<T, ZitiError>;
#[derive(Error, Debug)]
pub enum ZitiError {
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Connection timeout after {timeout:?}")]
ConnectionTimeout {
timeout: Duration
},
#[error("Authentication failed: {reason}")]
AuthenticationFailed {
reason: String
},
#[error("API session expired")]
ApiSessionExpired,
#[error("Service not found: {service_name}")]
ServiceNotFound {
service_name: String
},
#[error("Service access denied: {service_name}")]
ServiceAccessDenied {
service_name: String
},
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Protocol error: {message}")]
ProtocolError {
message: String
},
}
impl ZitiError {
pub fn is_recoverable(&self) -> bool {
matches!(
self,
ZitiError::ConnectionFailed(_) | ZitiError::ConnectionTimeout { .. }
)
}
pub fn requires_session_renewal(&self) -> bool {
matches!(self, ZitiError::ApiSessionExpired)
}
pub fn is_configuration_error(&self) -> bool {
matches!(self, ZitiError::ConfigError(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_connection_failed_error() {
let error = ZitiError::ConnectionFailed("Network unreachable".to_string());
assert_eq!(error.to_string(), "Connection failed: Network unreachable");
assert!(error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_connection_timeout_error() {
let timeout = Duration::from_secs(30);
let error = ZitiError::ConnectionTimeout { timeout };
assert_eq!(error.to_string(), "Connection timeout after 30s");
assert!(error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_authentication_failed_error() {
let error = ZitiError::AuthenticationFailed {
reason: "Invalid credentials".to_string(),
};
assert_eq!(error.to_string(), "Authentication failed: Invalid credentials");
assert!(!error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_api_session_expired_error() {
let error = ZitiError::ApiSessionExpired;
assert_eq!(error.to_string(), "API session expired");
assert!(!error.is_recoverable());
assert!(error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_service_not_found_error() {
let error = ZitiError::ServiceNotFound {
service_name: "my-service".to_string(),
};
assert_eq!(error.to_string(), "Service not found: my-service");
assert!(!error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_service_access_denied_error() {
let error = ZitiError::ServiceAccessDenied {
service_name: "secure-service".to_string(),
};
assert_eq!(error.to_string(), "Service access denied: secure-service");
assert!(!error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_config_error() {
let error = ZitiError::ConfigError("Invalid JSON format".to_string());
assert_eq!(error.to_string(), "Configuration error: Invalid JSON format");
assert!(!error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(error.is_configuration_error());
}
#[test]
fn test_protocol_error() {
let error = ZitiError::ProtocolError {
message: "Unexpected message type".to_string(),
};
assert_eq!(error.to_string(), "Protocol error: Unexpected message type");
assert!(!error.is_recoverable());
assert!(!error.requires_session_renewal());
assert!(!error.is_configuration_error());
}
#[test]
fn test_ziti_result_type() {
let success: ZitiResult<i32> = Ok(42);
assert!(success.is_ok());
assert_eq!(42, 42);
let failure_error = ZitiError::ApiSessionExpired;
assert!(failure_error.requires_session_renewal());
let failure: ZitiResult<i32> = Err(failure_error);
assert!(failure.is_err());
}
}