actr_cli/core/
error.rs

1//! Unified Error Handling
2//!
3//! Defines unified error types and handling strategies for the CLI tool
4
5use thiserror::Error;
6
7/// CLI Unified Error Type
8#[derive(Debug, Error)]
9pub enum ActrCliError {
10    #[error("Config error: {message}")]
11    Config { message: String },
12
13    #[error("Invalid project: {message}")]
14    InvalidProject { message: String },
15
16    #[error("Network error: {message}")]
17    Network { message: String },
18
19    #[error("Dependency error: {message}")]
20    Dependency { message: String },
21
22    #[error("Service discovery error: {message}")]
23    ServiceDiscovery { message: String },
24
25    #[error("Fingerprint validation error: {message}")]
26    FingerprintValidation { message: String },
27
28    #[error("Code generation error: {message}")]
29    CodeGeneration { message: String },
30
31    #[error("Cache error: {message}")]
32    Cache { message: String },
33
34    #[error("User interface error: {message}")]
35    UserInterface { message: String },
36
37    #[error("Command execution error: {message}")]
38    Command { message: String },
39
40    #[error("Validation failed: {details}")]
41    ValidationFailed { details: String },
42
43    #[error("Install failed: {reason}")]
44    InstallFailed { reason: String },
45
46    #[error("Component not registered: {component}")]
47    ComponentNotRegistered { component: String },
48
49    #[error("IO error")]
50    Io(#[from] std::io::Error),
51
52    #[error("Serialization error")]
53    Serialization(#[from] toml::de::Error),
54
55    #[error("HTTP error")]
56    Http(#[from] reqwest::Error),
57
58    #[error("Other error: {0}")]
59    Other(#[from] anyhow::Error),
60}
61
62/// Install Error
63#[derive(Debug, Error)]
64pub enum InstallError {
65    #[error("Dependency resolution failed: {dependency}")]
66    DependencyResolutionFailed { dependency: String },
67
68    #[error("Service unavailable: {service}")]
69    ServiceUnavailable { service: String },
70
71    #[error("Network connection failed: {uri}")]
72    NetworkConnectionFailed { uri: String },
73
74    #[error("Fingerprint mismatch: {service} - expected: {expected}, actual: {actual}")]
75    FingerprintMismatch {
76        service: String,
77        expected: String,
78        actual: String,
79    },
80
81    #[error("Version conflict: {details}")]
82    VersionConflict { details: String },
83
84    #[error("Cache operation failed: {operation}")]
85    CacheOperationFailed { operation: String },
86
87    #[error("Config update failed: {reason}")]
88    ConfigUpdateFailed { reason: String },
89
90    #[error("Pre-check failed: {failures:?}")]
91    PreCheckFailed { failures: Vec<String> },
92}
93
94/// Validation Error
95#[derive(Debug, Error)]
96pub enum ValidationError {
97    #[error("Config file syntax error: {file}")]
98    ConfigSyntaxError { file: String },
99
100    #[error("Dependency not found: {dependency}")]
101    DependencyNotFound { dependency: String },
102
103    #[error("Network unreachable: {uri}")]
104    NetworkUnreachable { uri: String },
105
106    #[error("Fingerprint mismatch: {service}")]
107    FingerprintMismatch { service: String },
108
109    #[error("Circular dependency: {cycle}")]
110    CircularDependency { cycle: String },
111
112    #[error("Insufficient permissions: {resource}")]
113    InsufficientPermissions { resource: String },
114}
115
116/// User-friendly Error Display
117impl ActrCliError {
118    /// Get user-friendly error message
119    pub fn user_message(&self) -> String {
120        match self {
121            ActrCliError::Config { message } => {
122                format!(
123                    "āš ļø  Config file error: {message}\nšŸ’” Hint: Check Actr.toml syntax and content"
124                )
125            }
126            ActrCliError::Network { message } => {
127                format!(
128                    "🌐 Network connection error: {message}\nšŸ’” Hint: Check network connection and service address"
129                )
130            }
131            ActrCliError::Dependency { message } => {
132                format!(
133                    "šŸ“¦ Dependency error: {message}\nšŸ’” Hint: Run 'actr check' to check dependencies"
134                )
135            }
136            ActrCliError::ValidationFailed { details } => {
137                format!(
138                    "āŒ Validation failed: {details}\nšŸ’” Hint: Fix the issues above and try again"
139                )
140            }
141            ActrCliError::InstallFailed { reason } => {
142                format!(
143                    "šŸ“„ Install failed: {reason}\nšŸ’” Hint: Run 'actr check' to check environment"
144                )
145            }
146            _ => self.to_string(),
147        }
148    }
149
150    /// Get possible solutions
151    pub fn suggested_actions(&self) -> Vec<String> {
152        match self {
153            ActrCliError::Config { .. } => vec![
154                "Check Actr.toml file syntax".to_string(),
155                "Run 'actr config test' to validate config".to_string(),
156                "Refer to config examples in documentation".to_string(),
157            ],
158            ActrCliError::Network { .. } => vec![
159                "Check network connection".to_string(),
160                "Verify service address is correct".to_string(),
161                "Check firewall settings".to_string(),
162                "Run 'actr check --verbose' for details".to_string(),
163            ],
164            ActrCliError::Dependency { .. } => vec![
165                "Run 'actr check' to check dependency status".to_string(),
166                "Run 'actr install' to install missing dependencies".to_string(),
167                "Run 'actr discovery' to find available services".to_string(),
168            ],
169            ActrCliError::ValidationFailed { .. } => vec![
170                "Check and fix reported issues".to_string(),
171                "Run 'actr check --verbose' for detailed diagnostics".to_string(),
172                "Ensure all dependency services are available".to_string(),
173            ],
174            ActrCliError::InstallFailed { .. } => vec![
175                "Check disk space".to_string(),
176                "Check network connection".to_string(),
177                "Run 'actr check' to validate environment".to_string(),
178                "Try clearing cache and retry".to_string(),
179            ],
180            _ => vec!["View detailed error information".to_string()],
181        }
182    }
183
184    /// Get related documentation links
185    pub fn documentation_links(&self) -> Vec<(&str, &str)> {
186        match self {
187            ActrCliError::Config { .. } => vec![
188                ("Config Docs", "https://docs.actor-rtc.com/config"),
189                (
190                    "Actr.toml Reference",
191                    "https://docs.actor-rtc.com/actr-toml",
192                ),
193            ],
194            ActrCliError::Dependency { .. } => vec![
195                (
196                    "Dependency Management",
197                    "https://docs.actor-rtc.com/dependencies",
198                ),
199                (
200                    "Troubleshooting",
201                    "https://docs.actor-rtc.com/troubleshooting",
202                ),
203            ],
204            _ => vec![("User Guide", "https://docs.actor-rtc.com/guide")],
205        }
206    }
207}
208
209/// Convert validation report to error
210impl From<super::components::ValidationReport> for ActrCliError {
211    fn from(report: super::components::ValidationReport) -> Self {
212        let mut details = Vec::new();
213
214        if !report.config_validation.is_valid {
215            details.extend(
216                report
217                    .config_validation
218                    .errors
219                    .iter()
220                    .map(|e| format!("Config error: {e}")),
221            );
222        }
223
224        for dep in &report.dependency_validation {
225            if !dep.is_available {
226                details.push(format!(
227                    "Dependency unavailable: {} - {}",
228                    dep.dependency,
229                    dep.error.as_deref().unwrap_or("unknown error")
230                ));
231            }
232        }
233
234        for net in &report.network_validation {
235            if !net.is_reachable {
236                details.push(format!(
237                    "Network unreachable: {} - {}",
238                    net.uri,
239                    net.error.as_deref().unwrap_or("connection failed")
240                ));
241            }
242        }
243
244        for fp in &report.fingerprint_validation {
245            if !fp.is_valid {
246                details.push(format!(
247                    "Fingerprint validation failed: {} - {}",
248                    fp.dependency,
249                    fp.error.as_deref().unwrap_or("fingerprint mismatch")
250                ));
251            }
252        }
253
254        for conflict in &report.conflicts {
255            details.push(format!("Dependency conflict: {}", conflict.description));
256        }
257
258        ActrCliError::ValidationFailed {
259            details: details.join("; "),
260        }
261    }
262}
263
264/// Error Report Formatter
265pub struct ErrorReporter;
266
267impl ErrorReporter {
268    /// Format error report
269    pub fn format_error(error: &ActrCliError) -> String {
270        let mut output = Vec::new();
271
272        // Main error message
273        output.push(error.user_message());
274        output.push(String::new());
275
276        // Suggested solutions
277        let actions = error.suggested_actions();
278        if !actions.is_empty() {
279            output.push("šŸ”§ Suggested solutions:".to_string());
280            for (i, action) in actions.iter().enumerate() {
281                output.push(format!("   {}. {}", i + 1, action));
282            }
283            output.push(String::new());
284        }
285
286        // Documentation links
287        let docs = error.documentation_links();
288        if !docs.is_empty() {
289            output.push("šŸ“š Related documentation:".to_string());
290            for (title, url) in docs {
291                output.push(format!("   • {title}: {url}"));
292            }
293            output.push(String::new());
294        }
295
296        output.join("\n")
297    }
298
299    /// Format validation report
300    pub fn format_validation_report(report: &super::components::ValidationReport) -> String {
301        let mut output = vec![
302            "šŸ” Dependency Validation Report".to_string(),
303            "=".repeat(50),
304            String::new(),
305            "šŸ“‹ Config file validation:".to_string(),
306        ];
307
308        // Config validation
309        if report.config_validation.is_valid {
310            output.push("   āœ… Passed".to_string());
311        } else {
312            output.push("   āŒ Failed".to_string());
313            for error in &report.config_validation.errors {
314                output.push(format!("      • {error}"));
315            }
316        }
317        output.push(String::new());
318
319        // Dependency validation
320        output.push("šŸ“¦ Dependency availability:".to_string());
321        for dep in &report.dependency_validation {
322            if dep.is_available {
323                output.push(format!("   āœ… {} - available", dep.dependency));
324            } else {
325                output.push(format!(
326                    "   āŒ {} - {}",
327                    dep.dependency,
328                    dep.error.as_deref().unwrap_or("unavailable")
329                ));
330            }
331        }
332        output.push(String::new());
333
334        // Network validation
335        output.push("🌐 Network connectivity:".to_string());
336        for net in &report.network_validation {
337            if net.is_reachable {
338                let latency = net
339                    .latency_ms
340                    .map(|ms| format!(" ({ms}ms)"))
341                    .unwrap_or_default();
342                output.push(format!("   āœ… {}{}", net.uri, latency));
343            } else {
344                output.push(format!(
345                    "   āŒ {} - {}",
346                    net.uri,
347                    net.error.as_deref().unwrap_or("unreachable")
348                ));
349            }
350        }
351        output.push(String::new());
352
353        // Fingerprint validation
354        if !report.fingerprint_validation.is_empty() {
355            output.push("šŸ” Fingerprint validation:".to_string());
356            for fp in &report.fingerprint_validation {
357                if fp.is_valid {
358                    output.push(format!("   āœ… {} - passed", fp.dependency));
359                } else {
360                    output.push(format!(
361                        "   āŒ {} - {}",
362                        fp.dependency,
363                        fp.error.as_deref().unwrap_or("validation failed")
364                    ));
365                }
366            }
367            output.push(String::new());
368        }
369
370        // Conflict report
371        if !report.conflicts.is_empty() {
372            output.push("āš ļø Dependency conflicts:".to_string());
373            for conflict in &report.conflicts {
374                output.push(format!(
375                    "   • {} vs {}: {}",
376                    conflict.dependency_a, conflict.dependency_b, conflict.description
377                ));
378            }
379            output.push(String::new());
380        }
381
382        // Summary
383        if report.is_success() {
384            output.push("✨ Overall: All validations passed".to_string());
385        } else {
386            output.push("āŒ Overall: Issues need to be resolved".to_string());
387        }
388
389        output.join("\n")
390    }
391}