Skip to main content

qubit_io/wrappers/
checksum_writer.rs

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