Skip to main content

qubit_io/wrappers/
tee_reader.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::io::{
9    Read,
10    Result,
11    Seek,
12    SeekFrom,
13    Write,
14};
15
16use super::SyncSeekTeeReader;
17
18/// Reader wrapper that mirrors read bytes into a branch writer.
19///
20/// `TeeReader` forwards reads to the source reader and writes every
21/// successfully read byte into the branch writer while the stream is consumed.
22/// If the branch writer fails, the source bytes have already been read from the
23/// inner reader and the branch error is returned.
24///
25/// Seeking a `TeeReader` seeks only the source reader. It does not seek or
26/// otherwise modify the branch writer; bytes mirrored after the seek are simply
27/// appended or written according to the branch writer's own state.
28///
29/// `TeeReader` intentionally does not implement [`std::io::BufRead`]. Mirroring
30/// from `fill_buf` would copy bytes before the caller commits to consuming
31/// them, while mirroring from `consume` could not report branch write failures
32/// because `BufRead::consume` has no error return.
33///
34/// If buffered access is needed, wrap this reader outside the tee layer, for
35/// example `BufReader<TeeReader<R, W>>`. In that composition bytes are mirrored
36/// when the outer `BufReader` refills its internal buffer, which may be earlier
37/// than the application later consuming those bytes from `fill_buf`.
38///
39/// # Failure and retry semantics
40///
41/// A branch error does not restore bytes already consumed from the source.
42/// Callers should therefore treat a branch write error as terminal unless they
43/// can reposition or otherwise recover the source. An outer buffering layer
44/// may discard a failed refill even though the source has already advanced:
45///
46/// ```text
47/// source before refill: "abcdef"
48/// refill reads "abc":   source advances to "def", branch returns an error
49/// retry:                reading resumes at "def"; "abc" may be unavailable
50/// ```
51///
52/// After a branch error, the source and branch may be out of sync. This wrapper
53/// does not retain the consumed bytes or provide rollback.
54///
55/// # Examples
56/// ```
57/// use std::io::{
58///     Cursor,
59///     Read,
60/// };
61///
62/// use qubit_io::TeeReader;
63///
64/// let source = Cursor::new(b"abc".to_vec());
65/// let branch = Vec::new();
66/// let mut reader = TeeReader::new(source, branch);
67///
68/// let mut data = Vec::new();
69/// reader.read_to_end(&mut data)?;
70/// let (_source, branch) = reader.into_inner();
71///
72/// assert_eq!(b"abc", data.as_slice());
73/// assert_eq!(b"abc", branch.as_slice());
74/// # Ok::<(), std::io::Error>(())
75/// ```
76pub struct TeeReader<R, W> {
77    inner: R,
78    branch: W,
79}
80
81impl<R, W> TeeReader<R, W> {
82    /// Creates a tee reader.
83    ///
84    /// # Parameters
85    /// - `reader`: Source reader.
86    /// - `branch`: Writer that receives the bytes successfully read.
87    ///
88    /// # Returns
89    /// A new tee reader.
90    #[inline]
91    pub fn new(reader: R, branch: W) -> Self {
92        Self {
93            inner: reader,
94            branch,
95        }
96    }
97
98    /// Creates a tee reader that synchronizes branch seeks with source seeks.
99    ///
100    /// The returned wrapper mirrors read bytes like [`TeeReader`], but its
101    /// [`Seek`] implementation also seeks the branch writer to the source
102    /// reader's resulting absolute position. If the branch seek fails, the
103    /// source reader may already have moved.
104    ///
105    /// # Parameters
106    /// - `reader`: Source reader.
107    /// - `branch`: Writer that receives bytes successfully read and is sought
108    ///   to the same absolute position as the source reader.
109    ///
110    /// # Returns
111    /// A sync-seek tee reader.
112    #[inline]
113    pub fn with_sync_branch_seek(
114        reader: R,
115        branch: W,
116    ) -> SyncSeekTeeReader<R, W> {
117        SyncSeekTeeReader::new(reader, branch)
118    }
119
120    /// Returns an immutable reference to the source reader.
121    ///
122    /// # Returns
123    /// The source reader reference.
124    #[inline]
125    pub fn inner(&self) -> &R {
126        &self.inner
127    }
128
129    /// Returns a mutable reference to the source reader.
130    ///
131    /// # Returns
132    /// The source reader reference.
133    #[inline]
134    pub fn inner_mut(&mut self) -> &mut R {
135        &mut self.inner
136    }
137
138    /// Returns an immutable reference to the branch writer.
139    ///
140    /// # Returns
141    /// The branch writer reference.
142    #[inline]
143    pub fn branch(&self) -> &W {
144        &self.branch
145    }
146
147    /// Returns a mutable reference to the branch writer.
148    ///
149    /// # Returns
150    /// The branch writer reference.
151    #[inline]
152    pub fn branch_mut(&mut self) -> &mut W {
153        &mut self.branch
154    }
155
156    /// Consumes this wrapper and returns the source reader and branch writer.
157    ///
158    /// # Returns
159    /// A tuple containing the source reader and branch writer.
160    #[inline]
161    pub fn into_inner(self) -> (R, W) {
162        (self.inner, self.branch)
163    }
164}
165
166impl<R, W> Read for TeeReader<R, W>
167where
168    R: Read,
169    W: Write,
170{
171    fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
172        let count = self.inner.read(buffer)?;
173        self.branch.write_all(&buffer[..count])?;
174        Ok(count)
175    }
176}
177
178impl<R, W> Seek for TeeReader<R, W>
179where
180    R: Seek,
181{
182    /// Seeks the source reader without touching the branch writer.
183    ///
184    /// # Parameters
185    /// - `position`: Target position for the source reader.
186    ///
187    /// # Returns
188    /// The new source reader position.
189    ///
190    /// # Errors
191    /// Returns the seek error reported by the source reader.
192    #[inline]
193    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
194        self.inner.seek(position)
195    }
196}