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}