Skip to main content

alopex_cli/output/
collector.rs

1//! Output collector for capturing rows without formatting.
2
3use std::sync::{Arc, Mutex};
4
5use crate::error::Result;
6use crate::models::Row;
7
8use super::formatter::Formatter;
9
10const MAX_COLLECTED_BYTES: usize = 10 * 1024 * 1024;
11
12#[derive(Default)]
13struct CollectorState {
14    rows: Vec<Row>,
15    bytes: usize,
16    truncated: bool,
17}
18
19#[derive(Clone, Default)]
20pub struct RowCollector {
21    state: Arc<Mutex<CollectorState>>,
22}
23
24impl RowCollector {
25    pub fn new() -> Self {
26        Self {
27            state: Arc::new(Mutex::new(CollectorState::default())),
28        }
29    }
30
31    pub fn formatter(&self) -> CollectingFormatter {
32        CollectingFormatter {
33            state: Arc::clone(&self.state),
34        }
35    }
36
37    pub fn rows(&self) -> Vec<Row> {
38        self.state.lock().expect("row collector lock").rows.clone()
39    }
40
41    pub fn truncation_warning(&self) -> Option<String> {
42        let state = self.state.lock().expect("row collector lock");
43        if state.truncated {
44            Some(format!(
45                "Warning: output truncated after {}MB to keep the TUI responsive.",
46                MAX_COLLECTED_BYTES / (1024 * 1024)
47            ))
48        } else {
49            None
50        }
51    }
52}
53
54pub struct CollectingFormatter {
55    state: Arc<Mutex<CollectorState>>,
56}
57
58impl Formatter for CollectingFormatter {
59    fn write_header(
60        &mut self,
61        _writer: &mut dyn std::io::Write,
62        _columns: &[crate::models::Column],
63    ) -> Result<()> {
64        Ok(())
65    }
66
67    fn write_row(&mut self, _writer: &mut dyn std::io::Write, row: &Row) -> Result<()> {
68        let row_bytes = serde_json::to_vec(row)?.len();
69        let mut state = self.state.lock().expect("row collector lock");
70        if state.truncated {
71            return Ok(());
72        }
73        if state.bytes + row_bytes > MAX_COLLECTED_BYTES {
74            state.truncated = true;
75            return Ok(());
76        }
77        state.bytes += row_bytes;
78        state.rows.push(row.clone());
79        Ok(())
80    }
81
82    fn write_footer(&mut self, _writer: &mut dyn std::io::Write) -> Result<()> {
83        Ok(())
84    }
85
86    fn supports_streaming(&self) -> bool {
87        true
88    }
89}