torrust_tracker_deployer_lib/shared/command/
error.rs1use std::path::PathBuf;
7
8use thiserror::Error;
9
10#[derive(Error, Debug)]
12pub enum CommandError {
13 #[error("Failed to start command '{command}': {source}")]
15 StartupFailed {
16 command: String,
17 #[source]
18 source: std::io::Error,
19 },
20
21 #[error("Working directory does not exist: '{working_dir}'")]
23 WorkingDirectoryNotFound { working_dir: PathBuf },
24
25 #[error(
27 "Command '{command}' failed with exit code {exit_code}\nStdout: {stdout}\nStderr: {stderr}"
28 )]
29 ExecutionFailed {
30 command: String,
31 exit_code: String,
32 stdout: String,
33 stderr: String,
34 },
35}
36
37impl crate::shared::Traceable for CommandError {
38 fn trace_format(&self) -> String {
39 match self {
40 Self::StartupFailed { command, source } => {
41 format!("CommandError: Failed to start '{command}' - {source}")
42 }
43 Self::WorkingDirectoryNotFound { working_dir } => {
44 format!(
45 "CommandError: Working directory does not exist - '{}'",
46 working_dir.display()
47 )
48 }
49 Self::ExecutionFailed {
50 command,
51 exit_code,
52 stdout,
53 stderr,
54 } => {
55 format!("CommandError: Command '{command}' failed with exit code {exit_code}\nStdout: {stdout}\nStderr: {stderr}")
56 }
57 }
58 }
59
60 fn trace_source(&self) -> Option<&dyn crate::shared::Traceable> {
61 None
63 }
64
65 fn error_kind(&self) -> crate::shared::ErrorKind {
66 crate::shared::ErrorKind::CommandExecution
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use std::error::Error;
74 use std::io;
75
76 #[test]
77 fn it_should_format_startup_failed_error_correctly() {
78 let io_error = io::Error::new(io::ErrorKind::NotFound, "command not found");
79 let error = CommandError::StartupFailed {
80 command: "nonexistent_command".to_string(),
81 source: io_error,
82 };
83
84 let error_message = error.to_string();
85 assert!(error_message.contains("Failed to start command 'nonexistent_command'"));
86 assert!(error_message.contains("command not found"));
87 }
88
89 #[test]
90 fn it_should_format_execution_failed_error_correctly() {
91 let error = CommandError::ExecutionFailed {
92 command: "false".to_string(),
93 exit_code: "1".to_string(),
94 stdout: String::new(),
95 stderr: "command failed".to_string(),
96 };
97
98 let error_message = error.to_string();
99 assert!(error_message.contains("Command 'false' failed with exit code 1"));
100 assert!(error_message.contains("Stderr: command failed"));
101 }
102
103 #[test]
104 fn it_should_preserve_source_error_chain() {
105 let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "permission denied");
106 let error = CommandError::StartupFailed {
107 command: "restricted_command".to_string(),
108 source: io_error,
109 };
110
111 assert!(error.source().is_some());
113 assert_eq!(error.source().unwrap().to_string(), "permission denied");
114 }
115
116 #[test]
117 fn it_should_format_working_directory_not_found_error_correctly() {
118 let error = CommandError::WorkingDirectoryNotFound {
119 working_dir: PathBuf::from("/nonexistent/path/to/dir"),
120 };
121
122 let error_message = error.to_string();
123 assert!(error_message.contains("Working directory does not exist"));
124 assert!(error_message.contains("/nonexistent/path/to/dir"));
125 }
126}