1use std::fmt;
4
5#[derive(Debug)]
7pub enum CliError {
8 Config(String),
10 Connection(String),
12 FileOperation(String),
14 InvalidInput(String),
16 NotFound(String),
18 PermissionDenied(String),
20 Timeout(String),
22 Internal(String),
24}
25
26impl fmt::Display for CliError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 CliError::Config(msg) => write!(f, "Configuration error: {}", msg),
30 CliError::Connection(msg) => write!(f, "Connection error: {}", msg),
31 CliError::FileOperation(msg) => write!(f, "File operation error: {}", msg),
32 CliError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
33 CliError::NotFound(msg) => write!(f, "Not found: {}", msg),
34 CliError::PermissionDenied(msg) => write!(f, "Permission denied: {}", msg),
35 CliError::Timeout(msg) => write!(f, "Operation timed out: {}", msg),
36 CliError::Internal(msg) => write!(f, "Internal error: {}", msg),
37 }
38 }
39}
40
41impl std::error::Error for CliError {}
42
43pub fn format_error(error: &anyhow::Error) -> String {
45 let error_str = error.to_string();
46
47 if error_str.contains("timed out") || error_str.contains("timeout") {
48 "Operation timed out. Try increasing the timeout with: \
49 mielinctl node config cli.command_timeout_secs <seconds>"
50 .to_string()
51 } else if error_str.contains("Connection refused") || error_str.contains("connect") {
52 "Could not connect to the MielinOS daemon. \
53 Make sure it's running with: mielinctl daemon"
54 .to_string()
55 } else if error_str.contains("No such file") || error_str.contains("not found") {
56 format!("File not found: {}", error_str)
57 } else if error_str.contains("Permission denied") {
58 "Permission denied. You may need elevated privileges to perform this operation.".to_string()
59 } else if error_str.contains("config") || error_str.contains("Config") {
60 format!(
61 "Configuration error: {}. \
62 Check your config with: mielinctl node config --all",
63 error_str
64 )
65 } else {
66 error_str
67 }
68}
69
70pub trait ErrorContext<T> {
72 fn context_user_friendly(self, msg: &str) -> anyhow::Result<T>;
73}
74
75impl<T, E> ErrorContext<T> for Result<T, E>
76where
77 E: std::error::Error + Send + Sync + 'static,
78{
79 fn context_user_friendly(self, msg: &str) -> anyhow::Result<T> {
80 self.map_err(|e| anyhow::anyhow!("{}: {}", msg, e))
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn test_cli_error_display() {
90 let error = CliError::Config("invalid format".to_string());
91 assert_eq!(error.to_string(), "Configuration error: invalid format");
92
93 let error = CliError::Connection("refused".to_string());
94 assert_eq!(error.to_string(), "Connection error: refused");
95
96 let error = CliError::Timeout("30s".to_string());
97 assert_eq!(error.to_string(), "Operation timed out: 30s");
98 }
99
100 #[test]
101 fn test_format_error_timeout() {
102 let error = anyhow::anyhow!("Operation timed out");
103 let formatted = format_error(&error);
104 assert!(formatted.contains("timed out"));
105 assert!(formatted.contains("command_timeout_secs"));
106 }
107
108 #[test]
109 fn test_format_error_connection() {
110 let error = anyhow::anyhow!("Connection refused");
111 let formatted = format_error(&error);
112 assert!(formatted.contains("connect"));
113 assert!(formatted.contains("daemon"));
114 }
115
116 #[test]
117 fn test_format_error_not_found() {
118 let error = anyhow::anyhow!("No such file or directory");
119 let formatted = format_error(&error);
120 assert!(formatted.contains("File not found"));
121 }
122}