use std::fmt;
pub type RelayResult<T> = Result<T, RelayError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RelayError {
AuthenticationFailed {
reason: String,
},
RateLimitExceeded {
retry_after_ms: u64,
},
SessionError {
session_id: Option<u32>,
kind: SessionErrorKind,
},
NetworkError {
operation: String,
source: String,
},
ProtocolError {
frame_type: u8,
reason: String,
},
ResourceExhausted {
resource_type: String,
current_usage: u64,
limit: u64,
},
ConfigurationError {
parameter: String,
reason: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SessionErrorKind {
NotFound,
AlreadyExists,
Expired,
Terminated,
InvalidState {
current_state: String,
expected_state: String,
},
BandwidthExceeded {
used: u64,
limit: u64,
},
}
impl fmt::Display for RelayError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RelayError::AuthenticationFailed { reason } => {
write!(f, "Authentication failed: {}", reason)
}
RelayError::RateLimitExceeded { retry_after_ms } => {
write!(f, "Rate limit exceeded, retry after {} ms", retry_after_ms)
}
RelayError::SessionError { session_id, kind } => match session_id {
Some(id) => write!(f, "Session {} error: {}", id, kind),
None => write!(f, "Session error: {}", kind),
},
RelayError::NetworkError { operation, source } => {
write!(f, "Network error during {}: {}", operation, source)
}
RelayError::ProtocolError { frame_type, reason } => {
write!(
f,
"Protocol error in frame 0x{:02x}: {}",
frame_type, reason
)
}
RelayError::ResourceExhausted {
resource_type,
current_usage,
limit,
} => {
write!(
f,
"Resource exhausted: {} usage ({}) exceeds limit ({})",
resource_type, current_usage, limit
)
}
RelayError::ConfigurationError { parameter, reason } => {
write!(f, "Configuration error for {}: {}", parameter, reason)
}
}
}
}
impl fmt::Display for SessionErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SessionErrorKind::NotFound => write!(f, "session not found"),
SessionErrorKind::AlreadyExists => write!(f, "session already exists"),
SessionErrorKind::Expired => write!(f, "session expired"),
SessionErrorKind::Terminated => write!(f, "session terminated"),
SessionErrorKind::InvalidState {
current_state,
expected_state,
} => {
write!(
f,
"invalid state '{}', expected '{}'",
current_state, expected_state
)
}
SessionErrorKind::BandwidthExceeded { used, limit } => {
write!(f, "bandwidth exceeded: {} > {}", used, limit)
}
}
}
}
impl std::error::Error for RelayError {}
impl From<std::io::Error> for RelayError {
fn from(error: std::io::Error) -> Self {
RelayError::NetworkError {
operation: "I/O operation".to_string(),
source: error.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let auth_error = RelayError::AuthenticationFailed {
reason: "Invalid signature".to_string(),
};
assert!(auth_error.to_string().contains("Authentication failed"));
let rate_limit_error = RelayError::RateLimitExceeded {
retry_after_ms: 1000,
};
assert!(rate_limit_error.to_string().contains("Rate limit exceeded"));
let session_error = RelayError::SessionError {
session_id: Some(123),
kind: SessionErrorKind::NotFound,
};
assert!(session_error.to_string().contains("Session 123 error"));
}
#[test]
fn test_session_error_kind_display() {
let invalid_state = SessionErrorKind::InvalidState {
current_state: "Connected".to_string(),
expected_state: "Idle".to_string(),
};
assert!(invalid_state.to_string().contains("invalid state"));
assert!(invalid_state.to_string().contains("Connected"));
assert!(invalid_state.to_string().contains("Idle"));
}
#[test]
fn test_error_conversion() {
let io_error =
std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "Connection refused");
let relay_error: RelayError = io_error.into();
match relay_error {
RelayError::NetworkError { operation, source } => {
assert_eq!(operation, "I/O operation");
assert!(source.contains("Connection refused"));
}
_ => panic!("Expected NetworkError"),
}
}
}