chain_reader/lib.rs
1use std::{
2 collections::VecDeque,
3 io::{self, Read},
4 marker,
5};
6
7/// Defines the action to take when a read error occurs.
8///
9/// This enum allows fine-grained control over how `ChainReader` handles I/O errors
10/// during read operations. Each variant represents a different strategy for
11/// error recovery or propagation.
12#[derive(Debug)]
13pub enum ErrorAction {
14 /// Propagate the error but retain the current reader.
15 ///
16 /// The next read operation will retry the same reader. This is useful for
17 /// transient errors where retrying might succeed (e.g., `io::ErrorKind::Interrupted`).
18 Raise(io::Error),
19
20 /// Propagate the error and skip to the next reader.
21 ///
22 /// The problematic reader is removed from the queue and the error is returned.
23 /// This is useful for fatal errors that cannot be recovered from.
24 RaiseAndSkip(io::Error),
25
26 /// Silently retry the current reader without propagating the error.
27 ///
28 /// The error is swallowed and the same reader will be retried on the next read.
29 /// This is useful for errors that should be handled transparently.
30 Retry,
31
32 /// Silently skip to the next reader without propagating the error.
33 ///
34 /// The problematic reader is removed from the queue without returning an error.
35 /// This is useful for non-critical errors where skipping is acceptable.
36 RetryAndSkip,
37}
38
39/// Internal enum representing different sources of readers in the chain.
40///
41/// This enum is used internally by `ChainReader` to manage both single readers
42/// and iterators that produce multiple readers. It allows the chain to handle
43/// both types of reader sources uniformly while maintaining proper FIFO ordering.
44///
45/// # Variants
46///
47/// - `Single(R)`: A single reader instance
48/// - `Multi(I)`: An iterator that produces multiple reader instances
49///
50/// This type is not part of the public API and is for internal use only.
51#[derive(Debug)]
52enum ReaderSource<R, I>
53where
54 R: Read,
55 I: Iterator<Item = R>,
56{
57 /// A single reader instance
58 Single(R),
59 /// An iterator that produces multiple reader instances
60 Multi(I),
61}
62
63/// A sequential chaining reader that combines multiple [`Read`] instances with configurable error handling.
64///
65/// # Behavior
66///
67/// - Readers are consumed in FIFO (first-in, first-out) order.
68/// - Automatically switches to the next reader when the current one reaches EOF (returns `Ok(0)`).
69/// - Allows custom error handling to decide whether to retry or skip on I/O errors.
70/// - Supports both single readers and iterators that produce readers.
71///
72/// # Comparison with [`io::Chain`]
73///
74/// - Supports a dynamic queue of readers instead of a fixed pair.
75/// - Configurable error handling strategies (see [`ErrorAction`] for details)
76/// - Automatically advances to the next reader on EOF.
77/// - Handles both single readers and iterators of readers.
78///
79/// # Examples
80///
81/// ```
82/// use chain_reader::{ChainReader, ErrorAction};
83/// use std::io::{self, Read};
84///
85/// // Define a custom reader type that can handle both stdin and files
86/// enum Readers {
87/// Stdin,
88/// File(std::fs::File),
89/// Cursor(io::Cursor<String>),
90/// }
91///
92/// impl Read for Readers {
93/// fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
94/// match self {
95/// Readers::Stdin => io::stdin().read(buf),
96/// Readers::File(it) => it.read(buf),
97/// Readers::Cursor(it) => it.read(buf),
98/// }
99/// }
100/// }
101///
102/// fn main() -> io::Result<()> {
103/// // Create a ChainReader that starts with stdin
104/// let mut chain = ChainReader::new(
105/// |e| match e.kind() {
106/// io::ErrorKind::Interrupted => ErrorAction::Retry,
107/// _ => ErrorAction::RetryAndSkip,
108/// },
109/// );
110///
111///
112/// chain.push(Readers::Stdin);
113/// // Add a file to the chain
114/// chain.push(Readers::File(std::fs::File::open("Cargo.toml")?));
115/// chain.push_iter(vec![
116/// Readers::Cursor(io::Cursor::new("hello ".to_string())),
117/// Readers::Cursor(io::Cursor::new("world!".to_string())),
118/// ]);
119///
120/// // Read from the chain - first from stdin, then from the file
121/// let mut content = Vec::new();
122/// chain.read_to_end(&mut content)?;
123///
124/// Ok(())
125/// }
126/// ```
127pub struct ChainReader<'a, R, F, I = std::iter::Empty<R>>
128where
129 R: Read,
130 I: Iterator<Item = R>,
131 F: FnMut(io::Error) -> ErrorAction,
132{
133 /// Current active reader
134 current: Option<R>,
135 /// Queue of reader sources waiting to be processed
136 sources: VecDeque<ReaderSource<R, I>>,
137 /// Error handling callback for read operations.
138 ///
139 /// When a read error occurs, this callback is invoked with the encountered [`io::Error`].
140 /// The chaining behavior is determined by the returned [`ErrorAction`] value.
141 /// See the [`ErrorAction`] documentation for available error handling strategies.
142 error_handle: F,
143 _marker: marker::PhantomData<&'a R>,
144}
145
146impl<R, F, I> ChainReader<'_, R, F, I>
147where
148 R: Read,
149 I: Iterator<Item = R>,
150 F: FnMut(io::Error) -> ErrorAction,
151{
152 /// Creates a new `ChainReader` with the given error handler and an empty reader queue.
153 ///
154 /// Readers can be added to the queue using the [`Self::push`] and [`Self::push_iter`] methods.
155 /// The `error_handle` callback determines behavior on I/O errors during reading.
156 ///
157 /// # Parameters
158 /// - `error_handle`: Error handling strategy callback
159 pub fn new(error_handle: F) -> Self {
160 Self {
161 current: None,
162 sources: VecDeque::new(),
163 error_handle,
164 _marker: marker::PhantomData,
165 }
166 }
167
168 /// Replaces the current error handler with a new one.
169 ///
170 /// This method allows changing the error handling strategy at runtime.
171 ///
172 /// # Parameters
173 /// - `error_handle`: New error handling strategy callback
174 pub fn replace_handle(&mut self, error_handle: F) {
175 self.error_handle = error_handle;
176 }
177
178 /// Appends a single reader to the end of the processing queue.
179 ///
180 /// The reader will be processed after all existing readers in the queue
181 /// have either reached EOF or been skipped due to errors.
182 ///
183 /// # Parameters
184 /// - `reader`: Reader to add to the end of the queue
185 pub fn push(&mut self, reader: R) {
186 self.sources.push_back(ReaderSource::Single(reader));
187 }
188
189 /// Appends an iterator of readers to the end of the processing queue.
190 ///
191 /// The readers produced by the iterator will be processed in order after
192 /// all existing readers in the queue have been processed.
193 ///
194 /// # Parameters
195 /// - `iter`: Iterator that produces readers to add to the end of the queue
196 pub fn push_iter<II: IntoIterator<IntoIter = I>>(&mut self, iter: II) {
197 self.sources
198 .push_back(ReaderSource::Multi(iter.into_iter()));
199 }
200
201 /// Advances to the next reader in the queue.
202 ///
203 /// Returns `true` if a new reader was successfully advanced to,
204 /// or `false` if there are no more readers in the queue.
205 ///
206 /// # Internal Implementation
207 ///
208 /// This method handles both single readers and iterator-based readers,
209 /// ensuring proper FIFO processing order.
210 fn next(&mut self) -> bool {
211 while let Some(source) = self.sources.pop_front() {
212 match source {
213 ReaderSource::Single(reader) => {
214 self.current = Some(reader);
215 return true;
216 }
217 ReaderSource::Multi(mut iter) => {
218 if let Some(reader) = iter.next() {
219 self.current = Some(reader);
220 self.sources.push_front(ReaderSource::Multi(iter));
221 return true;
222 }
223 }
224 }
225 }
226 self.current = None;
227 false
228 }
229}
230
231impl<R, F, I> Read for ChainReader<'_, R, F, I>
232where
233 R: Read,
234 I: Iterator<Item = R>,
235 F: FnMut(io::Error) -> ErrorAction,
236{
237 /// Reads data from the current reader, handling errors and EOF according to the configured strategy.
238 ///
239 /// This implementation follows the chain reading pattern:
240 /// 1. Attempt to read from the current reader
241 /// 2. On success, return the read data
242 /// 3. On EOF, automatically advance to the next reader
243 /// 4. On error, invoke the error handler to determine the appropriate action
244 ///
245 /// The method will continue this process until either:
246 /// - Data is successfully read
247 /// - All readers are exhausted (returning Ok(0))
248 /// - An unhandled error occurs (returning Err)
249 ///
250 /// # Parameters
251 /// - `buf`: Buffer to read data into
252 ///
253 /// # Returns
254 /// - `Ok(n)`: Successfully read `n` bytes
255 /// - `Err(e)`: IO error that couldn't be handled by the error strategy
256 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
257 loop {
258 if self.current.is_none() && !self.next() {
259 return Ok(0);
260 }
261
262 match self.current.as_mut().unwrap().read(buf) {
263 Ok(0) => {
264 if !self.next() {
265 return Ok(0);
266 }
267 }
268 Ok(n) => return Ok(n),
269 Err(e) => match (self.error_handle)(e) {
270 ErrorAction::Raise(e) => return Err(e),
271 ErrorAction::RaiseAndSkip(e) => {
272 self.next();
273 return Err(e);
274 }
275 ErrorAction::Retry => {}
276 ErrorAction::RetryAndSkip => {
277 self.next();
278 }
279 },
280 }
281 }
282 }
283}