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}