// Enhanced Error Handling for Continuwuity // Contributed from our chat-system improvements // Provides better user-friendly error messages and Matrix-specific error codes use thiserror::Error; /// Enhanced error types with better user messaging #[derive(Error, Debug)] pub enum EnhancedError { #[error("Authentication failed: {message}")] Authentication { message: String }, #[error("Room not found: {room_id}")] RoomNotFound { room_id: String }, #[error("User not found: {user_id}")] UserNotFound { user_id: String }, #[error("Insufficient permissions: {reason}")] InsufficientPermissions { reason: String }, #[error("Federation error with {server}: {message}")] FederationError { server: String, message: String }, #[error("Configuration error: {field} - {message}")] ConfigurationError { field: String, message: String }, #[error("Database error: {operation} failed - {message}")] DatabaseError { operation: String, message: String }, #[error("Network error: {service} unavailable - {message}")] NetworkError { service: String, message: String }, #[error("Validation error: {field} - {message}")] ValidationError { field: String, message: String }, #[error("Rate limit exceeded: {resource} - retry after {seconds}s")] RateLimitExceeded { resource: String, seconds: u64 }, } impl EnhancedError { /// Get the Matrix error code for this error pub fn matrix_error_code(&self) -> &'static str { match self { EnhancedError::Authentication { .. } => "M_UNAUTHORIZED", EnhancedError::RoomNotFound { .. } => "M_NOT_FOUND", EnhancedError::UserNotFound { .. } => "M_NOT_FOUND", EnhancedError::InsufficientPermissions { .. } => "M_FORBIDDEN", EnhancedError::FederationError { .. } => "M_FEDERATION_ERROR", EnhancedError::ConfigurationError { .. } => "M_UNKNOWN", EnhancedError::DatabaseError { .. } => "M_UNKNOWN", EnhancedError::NetworkError { .. } => "M_UNKNOWN", EnhancedError::ValidationError { .. } => "M_BAD_JSON", EnhancedError::RateLimitExceeded { .. } => "M_LIMIT_EXCEEDED", } } /// Get the HTTP status code for this error pub fn http_status_code(&self) -> u16 { match self { EnhancedError::Authentication { .. } => 401, EnhancedError::RoomNotFound { .. } | EnhancedError::UserNotFound { .. } => 404, EnhancedError::InsufficientPermissions { .. } => 403, EnhancedError::FederationError { .. } => 502, EnhancedError::ConfigurationError { .. } | EnhancedError::DatabaseError { .. } => 500, EnhancedError::NetworkError { .. } => 503, EnhancedError::ValidationError { .. } => 400, EnhancedError::RateLimitExceeded { .. } => 429, } } /// Get a user-friendly error message pub fn user_message(&self) -> String { match self { EnhancedError::Authentication { message } => { format!("Please check your login credentials. {}", message) }, EnhancedError::RoomNotFound { room_id } => { format!("The room '{}' could not be found. It may have been deleted or you may not have access.", room_id) }, EnhancedError::UserNotFound { user_id } => { format!("User '{}' was not found. Please check the username and try again.", user_id) }, EnhancedError::InsufficientPermissions { reason } => { format!("You don't have permission to perform this action. {}", reason) }, EnhancedError::FederationError { server, message } => { format!("Unable to communicate with server '{}'. Please try again later. {}", server, message) }, EnhancedError::ConfigurationError { field, message } => { format!("Server configuration error in '{}': {}", field, message) }, EnhancedError::DatabaseError { operation, message: _ } => { format!("Database operation '{}' failed. Please try again later.", operation) }, EnhancedError::NetworkError { service, message } => { format!("Network service '{}' is currently unavailable. {}", service, message) }, EnhancedError::ValidationError { field, message } => { format!("Invalid value for '{}': {}", field, message) }, EnhancedError::RateLimitExceeded { resource, seconds } => { format!("Too many requests for '{}'. Please wait {} seconds before trying again.", resource, seconds) }, } } /// Get a sanitized error message for logging (removes sensitive info) pub fn sanitized_message(&self) -> String { match self { EnhancedError::Authentication { .. } => "Authentication failed".to_string(), EnhancedError::RoomNotFound { room_id } => format!("Room not found: {}", room_id), EnhancedError::UserNotFound { user_id } => format!("User not found: {}", user_id), EnhancedError::InsufficientPermissions { .. } => "Insufficient permissions".to_string(), EnhancedError::FederationError { server, .. } => format!("Federation error with {}", server), EnhancedError::ConfigurationError { field, .. } => format!("Configuration error in {}", field), EnhancedError::DatabaseError { operation, .. } => format!("Database error in {}", operation), EnhancedError::NetworkError { service, .. } => format!("Network error in {}", service), EnhancedError::ValidationError { field, .. } => format!("Validation error in {}", field), EnhancedError::RateLimitExceeded { resource, seconds } => { format!("Rate limit exceeded for {} ({}s)", resource, seconds) }, } } } /// Helper functions for creating common errors impl EnhancedError { pub fn auth_failed(message: impl Into) -> Self { Self::Authentication { message: message.into() } } pub fn room_not_found(room_id: impl Into) -> Self { Self::RoomNotFound { room_id: room_id.into() } } pub fn user_not_found(user_id: impl Into) -> Self { Self::UserNotFound { user_id: user_id.into() } } pub fn insufficient_permissions(reason: impl Into) -> Self { Self::InsufficientPermissions { reason: reason.into() } } pub fn federation_error(server: impl Into, message: impl Into) -> Self { Self::FederationError { server: server.into(), message: message.into() } } pub fn config_error(field: impl Into, message: impl Into) -> Self { Self::ConfigurationError { field: field.into(), message: message.into() } } pub fn database_error(operation: impl Into, message: impl Into) -> Self { Self::DatabaseError { operation: operation.into(), message: message.into() } } pub fn network_error(service: impl Into, message: impl Into) -> Self { Self::NetworkError { service: service.into(), message: message.into() } } pub fn validation_error(field: impl Into, message: impl Into) -> Self { Self::ValidationError { field: field.into(), message: message.into() } } pub fn rate_limit_exceeded(resource: impl Into, seconds: u64) -> Self { Self::RateLimitExceeded { resource: resource.into(), seconds } } } /// Conversion from Continuwuity's Error to our EnhancedError impl From for EnhancedError { fn from(error: crate::Error) -> Self { match error { crate::Error::Database(msg) => { Self::database_error("database_operation", msg.to_string()) }, crate::Error::Config(field, msg) => { Self::config_error(field, msg.to_string()) }, crate::Error::Federation(server, _) => { Self::federation_error(server.to_string(), "Federation communication failed") }, crate::Error::Io(io_error) => { Self::network_error("io_operation", io_error.to_string()) }, _ => { Self::database_error("unknown_operation", error.message()) } } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_authentication_error() { let error = EnhancedError::auth_failed("Invalid token"); assert_eq!(error.matrix_error_code(), "M_UNAUTHORIZED"); assert_eq!(error.http_status_code(), 401); assert!(error.user_message().contains("check your login credentials")); assert_eq!(error.sanitized_message(), "Authentication failed"); } #[test] fn test_room_not_found_error() { let error = EnhancedError::room_not_found("!room123:example.com"); assert_eq!(error.matrix_error_code(), "M_NOT_FOUND"); assert_eq!(error.http_status_code(), 404); assert!(error.user_message().contains("could not be found")); assert!(error.sanitized_message().contains("!room123:example.com")); } #[test] fn test_user_not_found_error() { let error = EnhancedError::user_not_found("@user:example.com"); assert_eq!(error.matrix_error_code(), "M_NOT_FOUND"); assert_eq!(error.http_status_code(), 404); assert!(error.user_message().contains("was not found")); assert!(error.sanitized_message().contains("@user:example.com")); } #[test] fn test_insufficient_permissions_error() { let error = EnhancedError::insufficient_permissions("Admin role required"); assert_eq!(error.matrix_error_code(), "M_FORBIDDEN"); assert_eq!(error.http_status_code(), 403); assert!(error.user_message().contains("don't have permission")); assert_eq!(error.sanitized_message(), "Insufficient permissions"); } #[test] fn test_federation_error() { let error = EnhancedError::federation_error("matrix.org", "Connection timeout"); assert_eq!(error.matrix_error_code(), "M_FEDERATION_ERROR"); assert_eq!(error.http_status_code(), 502); assert!(error.user_message().contains("Unable to communicate")); assert!(error.sanitized_message().contains("matrix.org")); } #[test] fn test_configuration_error() { let error = EnhancedError::config_error("database_url", "Invalid format"); assert_eq!(error.matrix_error_code(), "M_UNKNOWN"); assert_eq!(error.http_status_code(), 500); assert!(error.user_message().contains("Server configuration error")); assert!(error.sanitized_message().contains("database_url")); } #[test] fn test_database_error() { let error = EnhancedError::database_error("user_lookup", "Connection lost"); assert_eq!(error.matrix_error_code(), "M_UNKNOWN"); assert_eq!(error.http_status_code(), 500); assert!(error.user_message().contains("Database operation")); assert!(error.sanitized_message().contains("user_lookup")); } #[test] fn test_network_error() { let error = EnhancedError::network_error("federation", "DNS resolution failed"); assert_eq!(error.matrix_error_code(), "M_UNKNOWN"); assert_eq!(error.http_status_code(), 503); assert!(error.user_message().contains("currently unavailable")); assert!(error.sanitized_message().contains("federation")); } #[test] fn test_validation_error() { let error = EnhancedError::validation_error("room_name", "Too long"); assert_eq!(error.matrix_error_code(), "M_BAD_JSON"); assert_eq!(error.http_status_code(), 400); assert!(error.user_message().contains("Invalid value")); assert!(error.sanitized_message().contains("room_name")); } #[test] fn test_rate_limit_error() { let error = EnhancedError::rate_limit_exceeded("room_creation", 60); assert_eq!(error.matrix_error_code(), "M_LIMIT_EXCEEDED"); assert_eq!(error.http_status_code(), 429); assert!(error.user_message().contains("Too many requests")); assert!(error.sanitized_message().contains("room_creation")); } #[test] fn test_error_display() { let error = EnhancedError::auth_failed("Token expired"); let display = format!("{}", error); assert!(display.contains("Authentication failed")); assert!(display.contains("Token expired")); } #[test] fn test_error_debug() { let error = EnhancedError::room_not_found("!test:example.com"); let debug = format!("{:?}", error); assert!(debug.contains("RoomNotFound")); assert!(debug.contains("!test:example.com")); } }