shell_tunnel/execution/
result.rs1use std::time::Duration;
4
5#[derive(Debug, Clone)]
7pub struct ExecutionResult {
8 pub raw_output: Vec<u8>,
10 pub text_output: String,
12 pub exit_code: Option<i32>,
14 pub duration: Duration,
16 pub timed_out: bool,
18 pub total_bytes: u64,
25 pub truncated: bool,
27}
28
29impl ExecutionResult {
30 pub fn new(raw_output: Vec<u8>, text_output: String, duration: Duration) -> Self {
37 let total_bytes = raw_output.len() as u64;
38 Self {
39 raw_output,
40 text_output,
41 exit_code: None,
42 duration,
43 timed_out: false,
44 total_bytes,
45 truncated: false,
46 }
47 }
48
49 pub fn timeout(raw_output: Vec<u8>, text_output: String, duration: Duration) -> Self {
51 let total_bytes = raw_output.len() as u64;
52 Self {
53 raw_output,
54 text_output,
55 exit_code: None,
56 duration,
57 timed_out: true,
58 total_bytes,
59 truncated: false,
60 }
61 }
62
63 pub fn with_output_extent(mut self, total_bytes: u64, truncated: bool) -> Self {
69 self.total_bytes = total_bytes;
70 self.truncated = truncated;
71 self
72 }
73
74 pub fn with_exit_code(mut self, code: i32) -> Self {
76 self.exit_code = Some(code);
77 self
78 }
79
80 pub fn success(&self) -> bool {
82 self.exit_code == Some(0)
83 }
84
85 pub fn failed(&self) -> bool {
87 self.timed_out || matches!(self.exit_code, Some(c) if c != 0)
88 }
89
90 pub fn output_trimmed(&self) -> &str {
92 self.text_output.trim()
93 }
94
95 pub fn output_lines(&self) -> impl Iterator<Item = &str> {
97 self.text_output.lines()
98 }
99}
100
101impl Default for ExecutionResult {
102 fn default() -> Self {
103 Self {
104 raw_output: Vec::new(),
105 text_output: String::new(),
106 exit_code: None,
107 duration: Duration::ZERO,
108 timed_out: false,
109 total_bytes: 0,
110 truncated: false,
111 }
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct OutputChunk {
118 pub raw: Vec<u8>,
120 pub text: String,
122 pub source: OutputSource,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum OutputSource {
129 Stdout,
131 Stderr,
133 Combined,
135}
136
137impl OutputChunk {
138 pub fn new(raw: Vec<u8>, source: OutputSource) -> Self {
140 let text = String::from_utf8_lossy(&raw).into_owned();
141 Self { raw, text, source }
142 }
143
144 pub fn stdout(raw: Vec<u8>) -> Self {
146 Self::new(raw, OutputSource::Stdout)
147 }
148
149 pub fn combined(raw: Vec<u8>) -> Self {
151 Self::new(raw, OutputSource::Combined)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_execution_result_new() {
161 let result = ExecutionResult::new(
162 b"hello\n".to_vec(),
163 "hello\n".to_string(),
164 Duration::from_millis(100),
165 );
166
167 assert_eq!(result.raw_output, b"hello\n");
168 assert_eq!(result.text_output, "hello\n");
169 assert_eq!(result.duration, Duration::from_millis(100));
170 assert!(!result.timed_out);
171 assert!(result.exit_code.is_none());
172 }
173
174 #[test]
175 fn test_execution_result_success() {
176 let result = ExecutionResult::default().with_exit_code(0);
177 assert!(result.success());
178 assert!(!result.failed());
179 }
180
181 #[test]
182 fn test_execution_result_failed() {
183 let result = ExecutionResult::default().with_exit_code(1);
184 assert!(!result.success());
185 assert!(result.failed());
186 }
187
188 #[test]
189 fn test_execution_result_timeout() {
190 let result = ExecutionResult::timeout(vec![], String::new(), Duration::from_secs(30));
191 assert!(result.timed_out);
192 assert!(result.failed());
193 }
194
195 #[test]
196 fn test_output_trimmed() {
197 let result = ExecutionResult::new(vec![], " hello world \n".to_string(), Duration::ZERO);
198 assert_eq!(result.output_trimmed(), "hello world");
199 }
200
201 #[test]
202 fn test_output_lines() {
203 let result =
204 ExecutionResult::new(vec![], "line1\nline2\nline3".to_string(), Duration::ZERO);
205 let lines: Vec<_> = result.output_lines().collect();
206 assert_eq!(lines, vec!["line1", "line2", "line3"]);
207 }
208
209 #[test]
210 fn test_output_chunk_stdout() {
211 let chunk = OutputChunk::stdout(b"test output".to_vec());
212 assert_eq!(chunk.source, OutputSource::Stdout);
213 assert_eq!(chunk.text, "test output");
214 }
215
216 #[test]
217 fn test_output_chunk_combined() {
218 let chunk = OutputChunk::combined(b"mixed output".to_vec());
219 assert_eq!(chunk.source, OutputSource::Combined);
220 }
221}