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}