Skip to main content

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