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)]
141pub enum OutputSource {
142 Stdout,
144 Stderr,
146 Combined,
148}
149
150impl OutputChunk {
151 pub fn new(raw: Vec<u8>, source: OutputSource) -> Self {
153 let text = String::from_utf8_lossy(&raw).into_owned();
154 Self { raw, text, source }
155 }
156
157 pub fn stdout(raw: Vec<u8>) -> Self {
163 Self::new(raw, OutputSource::Stdout)
164 }
165
166 pub fn combined(raw: Vec<u8>) -> Self {
168 Self::new(raw, OutputSource::Combined)
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_execution_result_new() {
178 let result = ExecutionResult::new(
179 b"hello\n".to_vec(),
180 "hello\n".to_string(),
181 Duration::from_millis(100),
182 );
183
184 assert_eq!(result.raw_output, b"hello\n");
185 assert_eq!(result.text_output, "hello\n");
186 assert_eq!(result.duration, Duration::from_millis(100));
187 assert!(!result.timed_out);
188 assert!(result.exit_code.is_none());
189 }
190
191 #[test]
192 fn test_execution_result_success() {
193 let result = ExecutionResult::default().with_exit_code(0);
194 assert!(result.success());
195 assert!(!result.failed());
196 }
197
198 #[test]
199 fn test_execution_result_failed() {
200 let result = ExecutionResult::default().with_exit_code(1);
201 assert!(!result.success());
202 assert!(result.failed());
203 }
204
205 #[test]
206 fn test_execution_result_timeout() {
207 let result = ExecutionResult::timeout(vec![], String::new(), Duration::from_secs(30));
208 assert!(result.timed_out);
209 assert!(result.failed());
210 }
211
212 #[test]
213 fn test_output_trimmed() {
214 let result = ExecutionResult::new(vec![], " hello world \n".to_string(), Duration::ZERO);
215 assert_eq!(result.output_trimmed(), "hello world");
216 }
217
218 #[test]
219 fn test_output_lines() {
220 let result =
221 ExecutionResult::new(vec![], "line1\nline2\nline3".to_string(), Duration::ZERO);
222 let lines: Vec<_> = result.output_lines().collect();
223 assert_eq!(lines, vec!["line1", "line2", "line3"]);
224 }
225
226 #[test]
227 fn test_output_chunk_stdout() {
228 let chunk = OutputChunk::stdout(b"test output".to_vec());
229 assert_eq!(chunk.source, OutputSource::Stdout);
230 assert_eq!(chunk.text, "test output");
231 }
232
233 #[test]
234 fn test_output_chunk_combined() {
235 let chunk = OutputChunk::combined(b"mixed output".to_vec());
236 assert_eq!(chunk.source, OutputSource::Combined);
237 }
238}