use crate::components::common::{Msg, PopupActivityMsg};
use std::fmt::Display;
use std::sync::mpsc::Sender;
#[derive(Debug, Clone, PartialEq)]
pub enum AppError {
ServiceBus(String),
Component(String),
State(String),
Config(String),
Auth(String),
Channel(String),
}
impl Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AppError::ServiceBus(msg) => write!(f, "Service Bus Error: {msg}"),
AppError::Component(msg) => write!(f, "Component Error: {msg}"),
AppError::State(msg) => write!(f, "State Error: {msg}"),
AppError::Config(msg) => write!(f, "Configuration Error: {msg}"),
AppError::Auth(msg) => write!(f, "Authentication Error: {msg}"),
AppError::Channel(msg) => write!(f, "Channel Error: {msg}"),
}
}
}
impl std::error::Error for AppError {}
impl From<quetty_server::service_bus_manager::ServiceBusError> for AppError {
fn from(err: quetty_server::service_bus_manager::ServiceBusError) -> Self {
AppError::ServiceBus(err.to_string())
}
}
pub type AppResult<T> = Result<T, AppError>;
#[derive(Debug, Clone)]
pub enum ErrorSeverity {
Warning,
Error,
Critical,
}
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub component: String,
pub operation: String,
pub user_message: String,
pub technical_details: Option<String>,
pub suggestion: Option<String>,
pub severity: ErrorSeverity,
}
impl ErrorContext {
pub fn new(component: &str, operation: &str) -> Self {
Self {
component: component.to_string(),
operation: operation.to_string(),
user_message: Self::generate_fallback_message(component),
technical_details: None,
suggestion: None,
severity: ErrorSeverity::Error,
}
}
fn generate_fallback_message(component: &str) -> String {
format!("An error occurred in {component}. Please try again.")
}
pub fn with_message(mut self, message: &str) -> Self {
self.user_message = message.to_string();
self
}
pub fn with_technical_details(mut self, details: &str) -> Self {
self.technical_details = Some(details.to_string());
self
}
pub fn with_suggestion(mut self, suggestion: &str) -> Self {
self.suggestion = Some(suggestion.to_string());
self
}
pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
self.severity = severity;
self
}
}
#[derive(Debug, Clone)]
pub struct ContextualError {
pub error: AppError,
pub context: ErrorContext,
}
impl ContextualError {
pub fn new(error: AppError, context: ErrorContext) -> Self {
Self { error, context }
}
}
impl Display for ContextualError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ({})", self.context.user_message, self.error)
}
}
#[derive(Clone)]
pub struct ErrorReporter {
tx: Sender<Msg>,
}
impl ErrorReporter {
pub fn new(tx: Sender<Msg>) -> Self {
Self { tx }
}
pub fn report_simple(&self, error: AppError, component: &str, operation: &str) {
let context =
ErrorContext::new(component, operation).with_technical_details(&error.to_string());
self.report(error, context);
}
pub fn report_warning(&self, error: AppError, component: &str, operation: &str) {
let context = ErrorContext::new(component, operation).with_severity(ErrorSeverity::Warning);
self.report(error, context);
}
pub fn report_critical_and_exit(
&self,
error: AppError,
component: &str,
operation: &str,
user_message: &str,
) {
let context = ErrorContext::new(component, operation)
.with_message(user_message)
.with_severity(ErrorSeverity::Critical)
.with_suggestion("The application will terminate. Please fix the issue and restart.");
self.report(error, context);
}
pub fn report_error(&self, error: AppError) {
self.report_simple(error, "Application", "operation");
}
pub fn report(&self, error: AppError, context: ErrorContext) {
let contextual_error = ContextualError::new(error.clone(), context.clone());
match context.severity {
ErrorSeverity::Warning => {
log::warn!(
"[{}:{}] {} {}",
context.component,
context.operation,
contextual_error,
self.format_additional_context(&context)
);
}
ErrorSeverity::Error => {
log::error!(
"[{}:{}] {} {}",
context.component,
context.operation,
contextual_error,
self.format_additional_context(&context)
);
}
ErrorSeverity::Critical => {
log::error!(
"[CRITICAL] [{}:{}] {} {}",
context.component,
context.operation,
contextual_error,
self.format_additional_context(&context)
);
}
}
match context.severity {
ErrorSeverity::Warning => {
let popup_msg = Msg::PopupActivity(PopupActivityMsg::ShowWarning(
self.format_user_message(&context),
));
if let Err(e) = self.tx.send(popup_msg) {
log::error!("Failed to send warning popup message: {e}");
}
}
ErrorSeverity::Error | ErrorSeverity::Critical => {
let formatted_error = self.create_formatted_error(&error, &context);
let popup_msg = Msg::PopupActivity(PopupActivityMsg::ShowError(formatted_error));
if let Err(e) = self.tx.send(popup_msg) {
log::error!("Failed to send error popup message: {e}");
}
}
}
}
fn format_additional_context(&self, context: &ErrorContext) -> String {
let mut parts = Vec::new();
if let Some(ref technical_details) = context.technical_details {
parts.push(format!("🔍 Technical: {technical_details}"));
}
if let Some(ref suggestion) = context.suggestion {
parts.push(format!("💡 Suggestion: {suggestion}"));
}
if parts.is_empty() {
String::new()
} else {
format!("\n{}", parts.join("\n"))
}
}
fn format_user_message(&self, context: &ErrorContext) -> String {
let mut message = context.user_message.clone();
if let Some(ref suggestion) = context.suggestion {
message.push_str(&format!("\n\n💡 Suggestion: {suggestion}"));
}
message
}
pub fn create_warning_error(&self, message: String) -> AppError {
let mut formatted_message = String::new();
formatted_message.push_str("⚠️ Warning");
formatted_message.push_str(&format!("\n\n{message}"));
AppError::Component(formatted_message)
}
fn create_formatted_error(&self, error: &AppError, context: &ErrorContext) -> AppError {
let mut formatted_message = String::new();
let emoji = match error {
AppError::Config(_) => "⚙️",
AppError::ServiceBus(_) => "🔗",
AppError::Component(_) => "🎛️",
AppError::State(_) => "📊",
AppError::Auth(_) => "🔐",
AppError::Channel(_) => "📡",
};
formatted_message.push_str(&format!("{} {}", emoji, self.get_error_title(error)));
formatted_message.push_str(&format!("\n\n{}", context.user_message));
if let Some(ref technical) = context.technical_details {
formatted_message.push_str(&format!("\n\n🔍 Details: {technical}"));
}
if let Some(ref suggestion) = context.suggestion {
formatted_message.push_str(&format!("\n\n💡 Suggestion: {suggestion}"));
}
match error {
AppError::Config(_) => AppError::Config(formatted_message),
AppError::ServiceBus(_) => AppError::ServiceBus(formatted_message),
AppError::Component(_) => AppError::Component(formatted_message),
AppError::State(_) => AppError::State(formatted_message),
AppError::Auth(_) => AppError::Auth(formatted_message),
AppError::Channel(_) => AppError::Channel(formatted_message),
}
}
fn get_error_title(&self, error: &AppError) -> String {
match error {
AppError::Config(_) => "Configuration Error".to_string(),
AppError::ServiceBus(_) => "Service Bus Error".to_string(),
AppError::Component(_) => "Component Error".to_string(),
AppError::State(_) => "Application State Error".to_string(),
AppError::Auth(_) => "Authentication Error".to_string(),
AppError::Channel(_) => "Communication Error".to_string(),
}
}
pub fn report_mount_error(
&self,
component: &str,
operation: &str,
error: impl std::fmt::Display,
) {
let app_error = AppError::Component(format!("Failed to {operation} {component}: {error}"));
self.report_simple(app_error, component, operation);
}
pub fn report_send_error(&self, context: &str, error: impl std::fmt::Display) {
let app_error = AppError::Component(format!("Failed to send {context}: {error}"));
self.report_simple(app_error, "MessageChannel", "send_message");
}
pub fn report_activation_error(&self, component: &str, error: impl std::fmt::Display) {
let app_error = AppError::Component(format!("Failed to activate {component}: {error}"));
self.report_simple(app_error, component, "activate");
}
pub fn report_key_watcher_error(&self, error: impl std::fmt::Display) {
let app_error =
AppError::Component(format!("Failed to update global key watcher: {error}"));
self.report_simple(app_error, "GlobalKeyWatcher", "update_state");
}
pub fn report_clipboard_error(&self, operation: &str, error: impl std::fmt::Display) {
let app_error = AppError::Component(format!("Failed to {operation}: {error}"));
self.report_warning(app_error, "Clipboard", operation);
}
pub fn report_theme_error(&self, operation: &str, error: impl std::fmt::Display) {
let app_error = AppError::Component(format!("Theme {operation} failed: {error}"));
self.report_warning(app_error, "ThemeManager", operation);
}
pub fn report_loading_error(
&self,
component: &str,
operation: &str,
error: impl std::fmt::Display,
) {
let context = ErrorContext::new(component, operation)
.with_message(&format!("Failed to {operation} data"))
.with_technical_details(&error.to_string())
.with_suggestion("Check your connection and try again");
let app_error = AppError::ServiceBus(error.to_string());
self.report(app_error, context);
}
pub fn report_service_bus_error(
&self,
operation: &str,
error: impl std::fmt::Display,
suggestion: Option<&str>,
) {
let context = ErrorContext::new("ServiceBus", operation)
.with_message(&format!("Service bus {operation} failed"))
.with_technical_details(&error.to_string())
.with_suggestion(suggestion.unwrap_or("Check your Azure connection and credentials"));
let app_error = AppError::ServiceBus(error.to_string());
self.report(app_error, context);
}
pub fn report_bulk_operation_error(
&self,
operation: &str,
count: usize,
error: impl std::fmt::Display,
) {
let context = ErrorContext::new("BulkOperationHandler", operation)
.with_message(&format!("Failed to {operation} {count} messages"))
.with_technical_details(&error.to_string())
.with_suggestion(
"Some messages may have been processed. Check the queue and try again if needed",
);
let app_error = AppError::ServiceBus(error.to_string());
self.report(app_error, context);
}
pub fn report_config_error(&self, config_type: &str, error: impl std::fmt::Display) {
let context = ErrorContext::new("Configuration", "load_config")
.with_message(&format!("Failed to load {config_type} configuration"))
.with_technical_details(&error.to_string())
.with_suggestion("Check your configuration file and restart the application");
let app_error = AppError::Config(error.to_string());
self.report(app_error, context);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::mpsc;
#[test]
fn test_error_context_creation() {
let context = ErrorContext::new("TestComponent", "test_operation");
assert_eq!(context.component, "TestComponent");
assert_eq!(context.operation, "test_operation");
assert_eq!(
context.user_message,
"An error occurred in TestComponent. Please try again."
);
}
#[test]
fn test_contextual_error_with_custom_message() {
let error = AppError::Config("Test error".to_string());
let context =
ErrorContext::new("TestComponent", "test_operation").with_message("Custom message");
let contextual = ContextualError::new(error, context);
assert_eq!(contextual.context.user_message, "Custom message");
}
#[test]
fn test_contextual_error_default_message() {
let error = AppError::Config("Test error".to_string());
let context = ErrorContext::new("TestComponent", "test_operation");
let contextual = ContextualError::new(error, context);
assert_eq!(
contextual.context.user_message,
"An error occurred in TestComponent. Please try again."
);
}
#[test]
fn test_critical_severity() {
let context = ErrorContext::new("TestComponent", "test_operation")
.with_severity(ErrorSeverity::Critical);
matches!(context.severity, ErrorSeverity::Critical);
}
#[test]
fn test_warning_severity_reporting() {
let (tx, rx) = mpsc::channel();
let reporter = ErrorReporter::new(tx);
let error = AppError::Component("Warning message".to_string());
reporter.report_warning(error, "TestComponent", "test_operation");
let msg = rx.recv().expect("Should receive warning message");
assert!(matches!(
msg,
Msg::PopupActivity(PopupActivityMsg::ShowWarning(_))
));
}
#[test]
fn test_user_friendly_message_generation() {
let context = ErrorContext::new("MessageLoader", "load_messages");
assert_eq!(
context.user_message,
"An error occurred in MessageLoader. Please try again."
);
let context = ErrorContext::new("UnknownComponent", "unknown_operation");
assert_eq!(
context.user_message,
"An error occurred in UnknownComponent. Please try again."
);
}
#[test]
fn test_error_context_builder_pattern() {
let context = ErrorContext::new("TestComponent", "test_operation")
.with_message("Custom message")
.with_technical_details("Technical information")
.with_suggestion("Try this solution")
.with_severity(ErrorSeverity::Warning);
assert_eq!(context.user_message, "Custom message");
assert_eq!(
context.technical_details,
Some("Technical information".to_string())
);
assert_eq!(context.suggestion, Some("Try this solution".to_string()));
assert!(matches!(context.severity, ErrorSeverity::Warning));
}
#[test]
fn test_contextual_error_display() {
let error = AppError::Config("Test error".to_string());
let context = ErrorContext::new("TestComponent", "test_operation");
let contextual = ContextualError::new(error, context);
let display_str = format!("{contextual}");
assert!(display_str.contains("TestComponent"));
assert!(display_str.contains("Test error"));
}
#[test]
fn test_new_with_message_method() {
let context = ErrorContext::new("TestComponent", "test_operation")
.with_message("Custom error message");
assert_eq!(context.component, "TestComponent");
assert_eq!(context.operation, "test_operation");
assert_eq!(context.user_message, "Custom error message");
assert!(matches!(context.severity, ErrorSeverity::Error));
}
#[test]
fn test_format_additional_context_consistency() {
let (tx, _rx) = mpsc::channel();
let reporter = ErrorReporter::new(tx);
let context = ErrorContext::new("TestComponent", "test_operation")
.with_technical_details("Technical error details")
.with_suggestion("Try this fix");
let formatted = reporter.format_additional_context(&context);
assert!(formatted.contains("🔍 Technical:"));
assert!(formatted.contains("💡 Suggestion:"));
assert!(formatted.contains("\n"));
}
}