alopex_cli/output/
collector.rs1use 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}