kql_panopticon/execution/processing/
output.rs

1//! Processing step output types
2//!
3//! Defines the output structure for processing steps using file-backed ResultHandle.
4
5use crate::execution::result::ResultHandle;
6use std::time::Duration;
7
8/// Output from a processing step execution
9#[derive(Debug, Clone)]
10pub struct ProcessingStepOutput {
11    /// Handle to the processing step's output file
12    handle: ResultHandle,
13
14    /// Execution duration
15    duration: Duration,
16}
17
18impl ProcessingStepOutput {
19    /// Create output from a result handle
20    pub fn new(handle: ResultHandle, duration: Duration) -> Self {
21        Self { handle, duration }
22    }
23
24    /// Create an empty output (for skipped steps)
25    pub fn empty(step_name: &str, duration: Duration) -> Self {
26        Self {
27            handle: ResultHandle::empty(step_name),
28            duration,
29        }
30    }
31
32    /// Get the result handle
33    pub fn handle(&self) -> &ResultHandle {
34        &self.handle
35    }
36
37    /// Take ownership of the result handle
38    pub fn into_handle(self) -> ResultHandle {
39        self.handle
40    }
41
42    /// Get execution duration
43    pub fn duration(&self) -> Duration {
44        self.duration
45    }
46
47    /// Get duration in milliseconds
48    pub fn duration_ms(&self) -> u64 {
49        self.duration.as_millis() as u64
50    }
51
52    /// Check if output is empty (no rows written)
53    pub fn is_empty(&self) -> bool {
54        self.handle.row_count().unwrap_or(0) == 0
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::execution::result::ResultWriter;
62    use std::path::PathBuf;
63
64    fn temp_path(name: &str) -> PathBuf {
65        std::env::temp_dir()
66            .join("kql-panopticon-test")
67            .join("processing_output")
68            .join(name)
69    }
70
71    fn cleanup(path: &std::path::Path) {
72        let _ = std::fs::remove_file(path);
73    }
74
75    #[test]
76    fn test_processing_output() {
77        let path = temp_path("test_output.jsonl");
78        cleanup(&path);
79
80        // Create output with a writer
81        let mut writer = ResultWriter::new(&path, "test_step").unwrap();
82        writer
83            .write_row(&serde_json::json!({"score": 75, "level": "HIGH"}))
84            .unwrap();
85        let handle = writer.finish().unwrap();
86
87        let output = ProcessingStepOutput::new(handle, Duration::from_millis(100));
88
89        assert!(!output.is_empty());
90        assert_eq!(output.duration_ms(), 100);
91        assert_eq!(output.handle().row_count().unwrap(), 1);
92
93        cleanup(&path);
94    }
95
96    #[test]
97    fn test_empty_output() {
98        let output = ProcessingStepOutput::empty("skipped_step", Duration::from_millis(50));
99        assert!(output.is_empty());
100        assert_eq!(output.duration_ms(), 50);
101    }
102}