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.
58 fn peek_exact_or_eof(&mut self, buffer: &mut [u8]) -> Result<usize>;
59
60 /// Reads from `offset` and restores the original position.
61 ///
62 /// This method seeks to `offset`, reads until `buffer` is full or EOF is
63 /// reached, and then restores the position that was current before the
64 /// call.
65 ///
66 /// # Parameters
67 /// - `offset`: Absolute byte offset from the start of the stream.
68 /// - `buffer`: Destination buffer to fill.
69 ///
70 /// # Returns
71 /// The number of bytes written into `buffer`.
72 ///
73 /// # Errors
74 /// Returns an error when reading the current position, seeking to `offset`,
75 /// reading bytes, or restoring the original position fails. If restoration
76 /// fails, the restoration error is returned.
77 fn read_exact_or_eof_at(&mut self, offset: u64, buffer: &mut [u8]) -> Result<usize>;
78}
79
80/// Implements a position-preserving read through a type-erased stream.
81///
82/// # Parameters
83/// - `reader`: Stream to read and restore.
84/// - `buffer`: Destination buffer.
85///
86/// # Returns
87/// The number of bytes written into `buffer`.
88///
89/// # Errors
90/// Returns an error when reading, querying, or restoring the stream position
91/// fails.
92fn peek_exact_or_eof_impl(reader: &mut dyn ReadSeek, buffer: &mut [u8]) -> Result<usize> {
93 let position = reader.stream_position()?;
94 let read_result = read_ext_impl::read_exact_or_eof(reader, buffer);
95 let restore_result = reader.seek(SeekFrom::Start(position));
96 match (read_result, restore_result) {
97 (Ok(count), Ok(_)) => Ok(count),
98 (Err(error), Ok(_)) => Err(error),
99 (_, Err(error)) => Err(error),
100 }
101}
102
103/// Implements an offset read through a type-erased stream.
104///
105/// # Parameters
106/// - `reader`: Stream to seek, read, and restore.
107/// - `offset`: Absolute byte offset to read from.
108/// - `buffer`: Destination buffer.
109///
110/// # Returns
111/// The number of bytes written into `buffer`.
112///
113/// # Errors
114/// Returns an error when querying, changing, or restoring the stream position,
115/// or when reading fails.
116fn read_exact_or_eof_at_impl(reader: &mut dyn ReadSeek, offset: u64, buffer: &mut [u8]) -> 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(&mut self, offset: u64, buffer: &mut [u8]) -> Result<usize> {
165 let mut reader = self;
166 read_exact_or_eof_at_impl(&mut reader, offset, buffer)
167 }
168}