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