Skip to main content

qubit_io/wrappers/
checksum_writer.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use std::hash::Hasher;
9use std::io::{
10    Result,
11    Seek,
12    SeekFrom,
13    Write,
14};
15
16/// Writer wrapper that updates a checksum hasher with bytes written.
17///
18/// The wrapped hasher is updated only after the inner writer accepts bytes.
19/// Failed writes do not update the hasher.
20///
21/// The checksum value is whatever the supplied [`Hasher`] reports. The Rust
22/// standard-library hashers are not specified as stable file formats and are
23/// not cryptographic digests; use this wrapper for stream instrumentation
24/// unless the chosen hasher explicitly documents stronger guarantees.
25///
26/// Seeking changes only the wrapped writer position. It does not rewind,
27/// subtract from, or otherwise adjust the hasher state.
28///
29/// # Examples
30/// ```
31/// use std::collections::hash_map::DefaultHasher;
32/// use std::hash::Hasher;
33/// use std::io::Write;
34///
35/// use qubit_io::ChecksumWriter;
36///
37/// let mut expected = DefaultHasher::new();
38/// expected.write(b"payload");
39///
40/// let mut writer = ChecksumWriter::new(Vec::new(), DefaultHasher::new());
41/// writer.write_all(b"payload")?;
42///
43/// assert_eq!(expected.finish(), writer.checksum());
44/// assert_eq!(b"payload", writer.get_ref().as_slice());
45/// # Ok::<(), std::io::Error>(())
46/// ```
47pub struct ChecksumWriter<W, H> {
48    inner: W,
49    hasher: H,
50}
51
52impl<W, H> ChecksumWriter<W, H>
53where
54    H: Hasher,
55{
56    /// Creates a checksum writer.
57    ///
58    /// # Parameters
59    /// - `inner`: Writer to wrap.
60    /// - `hasher`: Hasher updated with successfully written bytes.
61    ///
62    /// # Returns
63    /// A new checksum writer.
64    #[inline]
65    pub fn new(inner: W, hasher: H) -> Self {
66        Self { inner, hasher }
67    }
68
69    /// Returns the current checksum value.
70    ///
71    /// # Returns
72    /// The value reported by [`Hasher::finish`].
73    #[inline]
74    pub fn checksum(&self) -> u64 {
75        self.hasher.finish()
76    }
77
78    /// Returns an immutable reference to the wrapped writer.
79    ///
80    /// # Returns
81    /// The wrapped writer reference.
82    #[inline]
83    pub fn get_ref(&self) -> &W {
84        &self.inner
85    }
86
87    /// Returns a mutable reference to the wrapped writer.
88    ///
89    /// # Returns
90    /// The wrapped writer reference.
91    #[inline]
92    pub fn get_mut(&mut self) -> &mut W {
93        &mut self.inner
94    }
95
96    /// Returns an immutable reference to the wrapped hasher.
97    ///
98    /// # Returns
99    /// The wrapped hasher reference.
100    #[inline]
101    pub fn hasher_ref(&self) -> &H {
102        &self.hasher
103    }
104
105    /// Returns a mutable reference to the wrapped hasher.
106    ///
107    /// # Returns
108    /// The wrapped hasher reference.
109    #[inline]
110    pub fn hasher_mut(&mut self) -> &mut H {
111        &mut self.hasher
112    }
113
114    /// Consumes this wrapper and returns the wrapped writer and hasher.
115    ///
116    /// # Returns
117    /// A tuple containing the wrapped writer and hasher.
118    #[inline]
119    pub fn into_inner(self) -> (W, H) {
120        (self.inner, self.hasher)
121    }
122}
123
124impl<W, H> Write for ChecksumWriter<W, H>
125where
126    W: Write,
127    H: Hasher,
128{
129    fn write(&mut self, buffer: &[u8]) -> Result<usize> {
130        let count = self.inner.write(buffer)?;
131        self.hasher.write(&buffer[..count]);
132        Ok(count)
133    }
134
135    #[inline]
136    fn flush(&mut self) -> Result<()> {
137        self.inner.flush()
138    }
139}
140
141impl<W, H> Seek for ChecksumWriter<W, H>
142where
143    W: Seek,
144    H: Hasher,
145{
146    /// Seeks the wrapped writer without changing the hasher state.
147    ///
148    /// # Parameters
149    /// - `position`: Target writer position.
150    ///
151    /// # Returns
152    /// The new writer position.
153    ///
154    /// # Errors
155    /// Returns the seek error reported by the wrapped writer.
156    #[inline]
157    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
158        self.inner.seek(position)
159    }
160}