Skip to main content

qubit_io/std_io/ext/
read_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 readers.
10
11use std::io::{
12    Read,
13    Result,
14    Seek,
15    SeekFrom,
16};
17
18use crate::std_io::{
19    ReadSeek,
20    ext::internal::read_ext_impl,
21};
22
23/// Extension methods for values that implement both [`Read`] and [`Seek`].
24///
25/// `ReadSeekExt` provides position-preserving read helpers for common
26/// inspection use cases such as file signature checks, MIME detection, and
27/// random-offset probing.
28pub trait ReadSeekExt: Read + Seek {
29    /// Reads from the current position and restores the original position.
30    ///
31    /// This method has the same partial-EOF semantics as
32    /// [`crate::std_io::ext::ReadExt::read_exact_or_eof`], but it leaves the
33    /// stream positioned where it was before the call when restoration
34    /// succeeds.
35    ///
36    /// # Parameters
37    /// - `buffer`: Destination buffer to fill.
38    ///
39    /// # Returns
40    /// The number of bytes written into `buffer`.
41    ///
42    /// # Errors
43    /// Returns an error when reading the current position, reading bytes, or
44    /// restoring the original position fails. If both reading and restoration
45    /// fail, the restoration error is returned because the caller's stream
46    /// position contract was not preserved.
47    fn peek_exact_or_eof(&mut self, buffer: &mut [u8]) -> Result<usize>;
48
49    /// Reads from `offset` and restores the original position.
50    ///
51    /// This method seeks to `offset`, reads until `buffer` is full or EOF is
52    /// reached, and then restores the position that was current before the
53    /// call.
54    ///
55    /// # Parameters
56    /// - `offset`: Absolute byte offset from the start of the stream.
57    /// - `buffer`: Destination buffer to fill.
58    ///
59    /// # Returns
60    /// The number of bytes written into `buffer`.
61    ///
62    /// # Errors
63    /// Returns an error when reading the current position, seeking to `offset`,
64    /// reading bytes, or restoring the original position fails. If restoration
65    /// fails, the restoration error is returned.
66    fn read_exact_or_eof_at(
67        &mut self,
68        offset: u64,
69        buffer: &mut [u8],
70    ) -> Result<usize>;
71}
72
73/// Implements a position-preserving read through a type-erased stream.
74///
75/// # Parameters
76/// - `reader`: Stream to read and restore.
77/// - `buffer`: Destination buffer.
78///
79/// # Returns
80/// The number of bytes written into `buffer`.
81///
82/// # Errors
83/// Returns an error when reading, querying, or restoring the stream position
84/// fails.
85fn peek_exact_or_eof_impl(
86    reader: &mut dyn ReadSeek,
87    buffer: &mut [u8],
88) -> Result<usize> {
89    let position = reader.stream_position()?;
90    let read_result = read_ext_impl::read_exact_or_eof(reader, buffer);
91    let restore_result = reader.seek(SeekFrom::Start(position));
92    match (read_result, restore_result) {
93        (Ok(count), Ok(_)) => Ok(count),
94        (Err(error), Ok(_)) => Err(error),
95        (_, Err(error)) => Err(error),
96    }
97}
98
99/// Implements an offset read through a type-erased stream.
100///
101/// # Parameters
102/// - `reader`: Stream to seek, read, and restore.
103/// - `offset`: Absolute byte offset to read from.
104/// - `buffer`: Destination buffer.
105///
106/// # Returns
107/// The number of bytes written into `buffer`.
108///
109/// # Errors
110/// Returns an error when querying, changing, or restoring the stream position,
111/// or when reading fails.
112fn read_exact_or_eof_at_impl(
113    reader: &mut dyn ReadSeek,
114    offset: u64,
115    buffer: &mut [u8],
116) -> Result<usize> {
117    let position = reader.stream_position()?;
118    let read_result = match reader.seek(SeekFrom::Start(offset)) {
119        Ok(_) => read_ext_impl::read_exact_or_eof(reader, buffer),
120        Err(error) => Err(error),
121    };
122    let restore_result = reader.seek(SeekFrom::Start(position));
123    match (read_result, restore_result) {
124        (Ok(count), Ok(_)) => Ok(count),
125        (Err(error), Ok(_)) => Err(error),
126        (_, Err(error)) => Err(error),
127    }
128}
129
130impl<T> ReadSeekExt for T
131where
132    T: Read + Seek + ?Sized,
133{
134    /// Reads from the current position and restores it afterward.
135    ///
136    /// # Parameters
137    /// - `buffer`: Destination buffer.
138    ///
139    /// # Returns
140    /// The number of bytes written into `buffer`.
141    ///
142    /// # Errors
143    /// Returns an error when reading, querying, or restoring the stream
144    /// position fails.
145    #[inline(always)]
146    fn peek_exact_or_eof(&mut self, buffer: &mut [u8]) -> Result<usize> {
147        let mut reader = self;
148        peek_exact_or_eof_impl(&mut reader, buffer)
149    }
150
151    /// Reads from an absolute offset and restores the original position.
152    ///
153    /// # Parameters
154    /// - `offset`: Absolute byte offset to read from.
155    /// - `buffer`: Destination buffer.
156    ///
157    /// # Returns
158    /// The number of bytes written into `buffer`.
159    ///
160    /// # Errors
161    /// Returns an error when querying, changing, or restoring the stream
162    /// position, or when reading fails.
163    #[inline(always)]
164    fn read_exact_or_eof_at(
165        &mut self,
166        offset: u64,
167        buffer: &mut [u8],
168    ) -> Result<usize> {
169        let mut reader = self;
170        read_exact_or_eof_at_impl(&mut reader, offset, buffer)
171    }
172}