Skip to main content

qubit_io/wrappers/
tee_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::io::{
9    Result,
10    Seek,
11    SeekFrom,
12    Write,
13};
14
15/// Writer wrapper that mirrors accepted bytes into a branch writer.
16///
17/// `TeeWriter` writes to the primary writer first, then writes exactly the
18/// accepted bytes into the branch writer with [`Write::write_all`]. If the
19/// branch writer fails, the primary writer may already have accepted bytes and
20/// the branch error is returned.
21///
22/// Seeking moves the primary writer first, then seeks the branch writer to the
23/// primary writer's resulting absolute position. If the branch seek fails, the
24/// primary writer may already have moved.
25///
26/// # Examples
27/// ```
28/// use std::io::Write;
29///
30/// use qubit_io::TeeWriter;
31///
32/// let primary = Vec::new();
33/// let branch = Vec::new();
34/// let mut writer = TeeWriter::new(primary, branch);
35///
36/// writer.write_all(b"abc")?;
37/// let (primary, branch) = writer.into_inner();
38///
39/// assert_eq!(b"abc", primary.as_slice());
40/// assert_eq!(b"abc", branch.as_slice());
41/// # Ok::<(), std::io::Error>(())
42/// ```
43pub struct TeeWriter<P, B> {
44    primary: P,
45    branch: B,
46}
47
48impl<P, B> TeeWriter<P, B> {
49    /// Creates a tee writer.
50    ///
51    /// # Parameters
52    /// - `primary`: Primary destination writer.
53    /// - `branch`: Secondary writer that mirrors accepted bytes.
54    ///
55    /// # Returns
56    /// A new tee writer.
57    #[inline]
58    pub fn new(primary: P, branch: B) -> Self {
59        Self { primary, branch }
60    }
61
62    /// Returns an immutable reference to the primary writer.
63    ///
64    /// # Returns
65    /// The primary writer reference.
66    #[inline]
67    pub fn primary_ref(&self) -> &P {
68        &self.primary
69    }
70
71    /// Returns a mutable reference to the primary writer.
72    ///
73    /// # Returns
74    /// The primary writer reference.
75    #[inline]
76    pub fn primary_mut(&mut self) -> &mut P {
77        &mut self.primary
78    }
79
80    /// Returns an immutable reference to the branch writer.
81    ///
82    /// # Returns
83    /// The branch writer reference.
84    #[inline]
85    pub fn branch_ref(&self) -> &B {
86        &self.branch
87    }
88
89    /// Returns a mutable reference to the branch writer.
90    ///
91    /// # Returns
92    /// The branch writer reference.
93    #[inline]
94    pub fn branch_mut(&mut self) -> &mut B {
95        &mut self.branch
96    }
97
98    /// Consumes this wrapper and returns both wrapped writers.
99    ///
100    /// # Returns
101    /// A tuple containing the primary writer and branch writer.
102    #[inline]
103    pub fn into_inner(self) -> (P, B) {
104        (self.primary, self.branch)
105    }
106}
107
108impl<P, B> Write for TeeWriter<P, B>
109where
110    P: Write,
111    B: Write,
112{
113    fn write(&mut self, buffer: &[u8]) -> Result<usize> {
114        let count = self.primary.write(buffer)?;
115        self.branch.write_all(&buffer[..count])?;
116        Ok(count)
117    }
118
119    fn flush(&mut self) -> Result<()> {
120        self.primary.flush()?;
121        self.branch.flush()
122    }
123}
124
125impl<P, B> Seek for TeeWriter<P, B>
126where
127    P: Seek,
128    B: Seek,
129{
130    /// Seeks both wrapped writers to the same resulting absolute position.
131    ///
132    /// # Parameters
133    /// - `position`: Target position for the primary writer.
134    ///
135    /// # Returns
136    /// The new primary writer position.
137    ///
138    /// # Errors
139    /// Returns the primary seek error, or the branch seek error after the
140    /// primary writer has already moved.
141    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
142        let primary_position = self.primary.seek(position)?;
143        self.branch.seek(SeekFrom::Start(primary_position))?;
144        Ok(primary_position)
145    }
146}