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}