1use thiserror::Error;
6
7#[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#[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#[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
116impl ActrCliError {
118 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 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 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
209impl 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
264pub struct ErrorReporter;
266
267impl ErrorReporter {
268 pub fn format_error(error: &ActrCliError) -> String {
270 let mut output = Vec::new();
271
272 output.push(error.user_message());
274 output.push(String::new());
275
276 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 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 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 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 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 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 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 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 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}