Skip to main content

qubit_io/std_io/ext/
write_seek_ext.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// =============================================================================
8
9//! Position-preserving extension operations for seekable standard writers.
10
11use std::io::Result;
12use std::io::Seek;
13use std::io::SeekFrom;
14use std::io::Write;
15
16use crate::std_io::WriteSeek;
17
18/// Extension methods for values that implement both [`Write`] and [`Seek`].
19///
20/// `WriteSeekExt` provides position-preserving write helpers for random-access
21/// output use cases such as patching headers, offsets, and indexes after a
22/// stream has already been written.
23///
24/// # Examples
25/// ```
26/// use qubit_io::std_io::ext::WriteSeekExt;
27/// use std::io::Cursor;
28///
29/// let mut output = Cursor::new(b"abcdef".to_vec());
30/// output.set_position(2);
31///
32/// output.write_all_at_preserving_position(1, b"XY")?;
33///
34/// assert_eq!(output.get_ref(), b"aXYdef");
35/// assert_eq!(output.position(), 2);
36/// # Ok::<(), std::io::Error>(())
37/// ```
38pub trait WriteSeekExt: Write + Seek {
39    /// Writes all bytes at `offset` and restores the original position.
40    ///
41    /// This method seeks to `offset`, delegates to [`Write::write_all`], and
42    /// then restores the position that was current before the call.
43    ///
44    /// # Parameters
45    /// - `offset`: Absolute byte offset from the start of the stream.
46    /// - `buffer`: Bytes to write.
47    ///
48    /// # Returns
49    /// `Ok(())` after all bytes have been written and the position restored.
50    ///
51    /// # Errors
52    /// Returns an error when reading the current position, seeking to `offset`,
53    /// writing bytes, or restoring the original position fails. If restoration
54    /// fails, the restoration error is returned.
55    fn write_all_at_preserving_position(&mut self, offset: u64, buffer: &[u8]) -> Result<()>;
56}
57
58/// Implements a position-preserving write through a type-erased stream.
59///
60/// # Parameters
61/// - `output`: Stream to seek, write, and restore.
62/// - `offset`: Absolute byte offset to write at.
63/// - `buffer`: Bytes to write.
64///
65/// # Returns
66/// `Ok(())` after all bytes have been written and the position restored.
67///
68/// # Errors
69/// Returns an error when querying, changing, or restoring the stream position,
70/// or when writing fails.
71fn write_all_at_preserving_position_impl(output: &mut dyn WriteSeek, offset: u64, buffer: &[u8]) -> Result<()> {
72    let position = output.stream_position()?;
73    let write_result = match output.seek(SeekFrom::Start(offset)) {
74        Ok(_) => output.write_all(buffer),
75        Err(error) => Err(error),
76    };
77    let restore_result = output.seek(SeekFrom::Start(position));
78    match (write_result, restore_result) {
79        (Ok(()), Ok(_)) => Ok(()),
80        (Err(error), Ok(_)) => Err(error),
81        (_, Err(error)) => Err(error),
82    }
83}
84
85impl<T> WriteSeekExt for T
86where
87    T: Write + Seek + ?Sized,
88{
89    /// Writes at an absolute offset and restores the original position.
90    ///
91    /// # Parameters
92    /// - `offset`: Absolute byte offset to write at.
93    /// - `buffer`: Bytes to write.
94    ///
95    /// # Returns
96    /// `Ok(())` after all bytes have been written and the position restored.
97    ///
98    /// # Errors
99    /// Returns an error when querying, changing, or restoring the stream
100    /// position, or when writing fails.
101    #[inline(always)]
102    fn write_all_at_preserving_position(&mut self, offset: u64, buffer: &[u8]) -> Result<()> {
103        let mut output = self;
104        write_all_at_preserving_position_impl(&mut output, offset, buffer)
105    }
106}