exarch_core/io/
counting.rs

1//! Counting writer for tracking bytes written.
2//!
3//! This module provides a `CountingWriter` that wraps any `Write`
4//! implementation and tracks the total number of bytes written.
5
6use std::io::Write;
7
8/// Wrapper writer that tracks total bytes written.
9///
10/// This writer wraps any `Write` implementation and maintains a counter
11/// of the total bytes successfully written. This is useful for:
12///
13/// - Tracking compressed archive size for reports
14/// - Monitoring write progress
15/// - Validating expected output sizes
16///
17/// # Implementation Notes
18///
19/// The counter only increments on successful writes. If a write operation
20/// fails partway through, only the successfully written bytes are counted.
21///
22/// # Examples
23///
24/// ```
25/// use exarch_core::io::CountingWriter;
26/// use std::io::Write;
27///
28/// let mut buffer = Vec::new();
29/// let mut writer = CountingWriter::new(&mut buffer);
30///
31/// writer.write_all(b"Hello, ")?;
32/// writer.write_all(b"World!")?;
33/// writer.flush()?;
34///
35/// assert_eq!(writer.total_bytes(), 13);
36/// assert_eq!(buffer, b"Hello, World!");
37/// # Ok::<(), std::io::Error>(())
38/// ```
39pub struct CountingWriter<W> {
40    /// Inner writer being wrapped
41    inner: W,
42    /// Total bytes successfully written
43    bytes_written: u64,
44}
45
46impl<W> CountingWriter<W> {
47    /// Creates a new counting writer.
48    ///
49    /// # Parameters
50    ///
51    /// - `inner`: The writer to wrap
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use exarch_core::io::CountingWriter;
57    /// use std::io::Write;
58    ///
59    /// let buffer: Vec<u8> = Vec::new();
60    /// let writer = CountingWriter::new(buffer);
61    /// ```
62    #[must_use]
63    pub fn new(inner: W) -> Self {
64        Self {
65            inner,
66            bytes_written: 0,
67        }
68    }
69
70    /// Returns the total number of bytes successfully written.
71    ///
72    /// This count includes all bytes from successful write operations,
73    /// including those from `write`, `write_all`, and `write_fmt`.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use exarch_core::io::CountingWriter;
79    /// use std::io::Write;
80    ///
81    /// let mut buffer = Vec::new();
82    /// let mut writer = CountingWriter::new(&mut buffer);
83    ///
84    /// writer.write_all(b"test")?;
85    /// assert_eq!(writer.total_bytes(), 4);
86    ///
87    /// writer.write_all(b"data")?;
88    /// assert_eq!(writer.total_bytes(), 8);
89    /// # Ok::<(), std::io::Error>(())
90    /// ```
91    #[must_use]
92    pub fn total_bytes(&self) -> u64 {
93        self.bytes_written
94    }
95
96    /// Consumes the counting writer and returns the inner writer.
97    ///
98    /// This is useful when you need to retrieve the underlying writer
99    /// after all writing is complete.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use exarch_core::io::CountingWriter;
105    /// use std::io::Write;
106    ///
107    /// let buffer = Vec::new();
108    /// let mut writer = CountingWriter::new(buffer);
109    ///
110    /// writer.write_all(b"test")?;
111    ///
112    /// let buffer = writer.into_inner();
113    /// assert_eq!(buffer, b"test");
114    /// # Ok::<(), std::io::Error>(())
115    /// ```
116    #[must_use]
117    pub fn into_inner(self) -> W {
118        self.inner
119    }
120
121    /// Returns a reference to the inner writer.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use exarch_core::io::CountingWriter;
127    ///
128    /// let buffer = Vec::new();
129    /// let writer = CountingWriter::new(buffer);
130    ///
131    /// let inner_ref: &Vec<u8> = writer.get_ref();
132    /// ```
133    #[must_use]
134    pub fn get_ref(&self) -> &W {
135        &self.inner
136    }
137
138    /// Returns a mutable reference to the inner writer.
139    ///
140    /// # Safety
141    ///
142    /// If you write to the inner writer directly (bypassing the
143    /// `CountingWriter`), the byte count will not be updated.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// use exarch_core::io::CountingWriter;
149    ///
150    /// let buffer = Vec::new();
151    /// let mut writer = CountingWriter::new(buffer);
152    ///
153    /// let inner_mut: &mut Vec<u8> = writer.get_mut();
154    /// ```
155    pub fn get_mut(&mut self) -> &mut W {
156        &mut self.inner
157    }
158}
159
160impl<W: Write> Write for CountingWriter<W> {
161    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
162        let bytes = self.inner.write(buf)?;
163        self.bytes_written += bytes as u64;
164        Ok(bytes)
165    }
166
167    fn flush(&mut self) -> std::io::Result<()> {
168        self.inner.flush()
169    }
170
171    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
172        self.inner.write_all(buf)?;
173        self.bytes_written += buf.len() as u64;
174        Ok(())
175    }
176}
177
178#[cfg(test)]
179#[allow(clippy::unwrap_used)]
180mod tests {
181    use super::*;
182    use std::io::Cursor;
183
184    #[test]
185    fn test_counting_writer_basic() {
186        let mut buffer = Vec::new();
187        let mut writer = CountingWriter::new(&mut buffer);
188
189        writer.write_all(b"Hello").unwrap();
190        assert_eq!(writer.total_bytes(), 5);
191
192        writer.write_all(b", World!").unwrap();
193        assert_eq!(writer.total_bytes(), 13);
194
195        assert_eq!(buffer, b"Hello, World!");
196    }
197
198    #[test]
199    fn test_counting_writer_write() {
200        let mut buffer = Vec::new();
201        let mut writer = CountingWriter::new(&mut buffer);
202
203        let bytes_written = writer.write(b"test").unwrap();
204        assert_eq!(bytes_written, 4);
205        assert_eq!(writer.total_bytes(), 4);
206    }
207
208    #[test]
209    fn test_counting_writer_write_fmt() {
210        let mut buffer = Vec::new();
211        let mut writer = CountingWriter::new(&mut buffer);
212
213        write!(writer, "test {}", 42).unwrap();
214        assert_eq!(writer.total_bytes(), 7);
215        assert_eq!(buffer, b"test 42");
216    }
217
218    #[test]
219    fn test_counting_writer_flush() {
220        let mut buffer = Vec::new();
221        let mut writer = CountingWriter::new(&mut buffer);
222
223        writer.write_all(b"data").unwrap();
224        writer.flush().unwrap();
225
226        assert_eq!(writer.total_bytes(), 4);
227    }
228
229    #[test]
230    fn test_counting_writer_into_inner() {
231        let buffer = Vec::new();
232        let mut writer = CountingWriter::new(buffer);
233
234        writer.write_all(b"test").unwrap();
235        assert_eq!(writer.total_bytes(), 4);
236
237        let buffer = writer.into_inner();
238        assert_eq!(buffer, b"test");
239    }
240
241    #[test]
242    fn test_counting_writer_get_ref() {
243        let buffer = Vec::new();
244        let mut writer = CountingWriter::new(buffer);
245
246        writer.write_all(b"test").unwrap();
247
248        let inner_ref = writer.get_ref();
249        assert_eq!(inner_ref, &b"test"[..]);
250    }
251
252    #[test]
253    fn test_counting_writer_get_mut() {
254        let buffer = Vec::new();
255        let mut writer = CountingWriter::new(buffer);
256
257        writer.write_all(b"test").unwrap();
258
259        let inner_mut = writer.get_mut();
260        inner_mut.push(b'!');
261
262        // Note: Direct modification doesn't update counter
263        assert_eq!(writer.total_bytes(), 4);
264        assert_eq!(writer.get_ref(), &b"test!"[..]);
265    }
266
267    #[test]
268    fn test_counting_writer_empty() {
269        let buffer: Vec<u8> = Vec::new();
270        let writer = CountingWriter::new(buffer);
271
272        assert_eq!(writer.total_bytes(), 0);
273    }
274
275    #[test]
276    fn test_counting_writer_multiple_writes() {
277        let mut buffer = Vec::new();
278        let mut writer = CountingWriter::new(&mut buffer);
279
280        for i in 0..10 {
281            write!(writer, "{i}").unwrap();
282        }
283
284        assert_eq!(writer.total_bytes(), 10);
285        assert_eq!(buffer, b"0123456789");
286    }
287
288    #[test]
289    fn test_counting_writer_with_cursor() {
290        let buffer: Vec<u8> = vec![0u8; 100];
291        let cursor = Cursor::new(buffer);
292        let mut writer = CountingWriter::new(cursor);
293
294        writer.write_all(b"test data").unwrap();
295        assert_eq!(writer.total_bytes(), 9);
296    }
297
298    #[test]
299    fn test_counting_writer_partial_write() {
300        // Use a limited buffer that can only write partial data
301        struct LimitedWriter {
302            inner: Vec<u8>,
303            max_write: usize,
304        }
305
306        impl Write for LimitedWriter {
307            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
308                let to_write = buf.len().min(self.max_write);
309                self.inner.extend_from_slice(&buf[..to_write]);
310                Ok(to_write)
311            }
312
313            fn flush(&mut self) -> std::io::Result<()> {
314                Ok(())
315            }
316        }
317
318        let limited = LimitedWriter {
319            inner: Vec::new(),
320            max_write: 3,
321        };
322        let mut writer = CountingWriter::new(limited);
323
324        // Try to write 5 bytes but only 3 will be written
325        let written = writer.write(b"hello").unwrap();
326        assert_eq!(written, 3);
327        assert_eq!(writer.total_bytes(), 3);
328
329        // Verify only 3 bytes were written
330        assert_eq!(writer.get_ref().inner, b"hel");
331    }
332}