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