Skip to main content

pipe_io/
source.rs

1//! The [`Source`] trait and built-in source adapters.
2//!
3//! A source produces items one at a time via [`Source::pull`]. The
4//! driver pulls until `Ok(None)`, then invokes [`Source::close`] before
5//! flushing downstream stages.
6
7// `FnSource` carries a `PhantomData<fn() -> Result<Option<T>, E>>`
8// to fix variance and Send-ness. Clippy considers it complex; the
9// shape mirrors `FnMut() -> Result<Option<T>, E>` deliberately.
10#![allow(clippy::type_complexity)]
11
12use crate::error::StageError;
13
14#[cfg(feature = "std")]
15use alloc::string::String;
16use core::marker::PhantomData;
17
18/// Producer at the head of a pipeline.
19pub trait Source {
20    /// Type of item produced.
21    type Item;
22    /// Error type the source can return.
23    type Error: StageError;
24
25    /// Pull the next item.
26    ///
27    /// # Errors
28    ///
29    /// Returns `Err(Self::Error)` if the source fails. The driver
30    /// wraps this in [`crate::Error::Source`].
31    ///
32    /// `Ok(None)` signals end-of-stream.
33    fn pull(&mut self) -> Result<Option<Self::Item>, Self::Error>;
34
35    /// Close the source. Default impl does nothing.
36    ///
37    /// # Errors
38    ///
39    /// Returns `Err(Self::Error)` if cleanup fails.
40    fn close(&mut self) -> Result<(), Self::Error> {
41        Ok(())
42    }
43}
44
45/// [`Source`] adapter over any [`IntoIterator`].
46///
47/// # Example
48///
49/// ```
50/// use pipe_io::source::{IterSource, Source};
51///
52/// let mut s = IterSource::new(vec![1, 2, 3]);
53/// assert_eq!(s.pull().unwrap(), Some(1));
54/// assert_eq!(s.pull().unwrap(), Some(2));
55/// assert_eq!(s.pull().unwrap(), Some(3));
56/// assert_eq!(s.pull().unwrap(), None);
57/// ```
58pub struct IterSource<I: Iterator> {
59    iter: I,
60}
61
62impl<I> IterSource<I>
63where
64    I: Iterator,
65{
66    /// Wrap an iterator into a source.
67    pub fn new<II>(into_iter: II) -> Self
68    where
69        II: IntoIterator<IntoIter = I>,
70    {
71        Self {
72            iter: into_iter.into_iter(),
73        }
74    }
75}
76
77/// An error type that no `IterSource` ever returns.
78#[derive(Debug)]
79pub enum Infallible {}
80
81impl core::fmt::Display for Infallible {
82    fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        match *self {}
84    }
85}
86
87impl<I> Source for IterSource<I>
88where
89    I: Iterator,
90{
91    type Item = I::Item;
92    type Error = Infallible;
93
94    fn pull(&mut self) -> Result<Option<Self::Item>, Self::Error> {
95        Ok(self.iter.next())
96    }
97}
98
99/// [`Source`] adapter over a closure that produces fallible items.
100///
101/// The closure returns `Ok(Some(item))` to emit, `Ok(None)` to signal
102/// end-of-stream, or `Err(E)` to raise a source error.
103///
104/// # Example
105///
106/// ```
107/// use pipe_io::source::{FnSource, Source};
108///
109/// let mut n = 0u32;
110/// let mut s = FnSource::new(move || -> Result<Option<u32>, &'static str> {
111///     n += 1;
112///     if n <= 3 { Ok(Some(n)) } else { Ok(None) }
113/// });
114///
115/// assert_eq!(s.pull().unwrap(), Some(1));
116/// assert_eq!(s.pull().unwrap(), Some(2));
117/// assert_eq!(s.pull().unwrap(), Some(3));
118/// assert_eq!(s.pull().unwrap(), None);
119/// ```
120pub struct FnSource<F, T, E> {
121    func: F,
122    _marker: PhantomData<fn() -> Result<Option<T>, E>>,
123}
124
125impl<F, T, E> FnSource<F, T, E>
126where
127    F: FnMut() -> Result<Option<T>, E>,
128{
129    /// Wrap a closure into a source.
130    pub fn new(func: F) -> Self {
131        Self {
132            func,
133            _marker: PhantomData,
134        }
135    }
136}
137
138impl<F, T, E> Source for FnSource<F, T, E>
139where
140    F: FnMut() -> Result<Option<T>, E>,
141    E: StageError,
142{
143    type Item = T;
144    type Error = E;
145
146    fn pull(&mut self) -> Result<Option<Self::Item>, Self::Error> {
147        (self.func)()
148    }
149}
150
151/// [`Source`] adapter over an [`std::sync::mpsc::Receiver`].
152///
153/// `pull` returns `Ok(None)` when the channel is empty AND all senders
154/// have been dropped.
155#[cfg(feature = "std")]
156#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
157pub struct ChannelSource<T> {
158    rx: std::sync::mpsc::Receiver<T>,
159}
160
161#[cfg(feature = "std")]
162impl<T> ChannelSource<T> {
163    /// Wrap a receiver into a source.
164    #[must_use]
165    pub fn new(rx: std::sync::mpsc::Receiver<T>) -> Self {
166        Self { rx }
167    }
168}
169
170#[cfg(feature = "std")]
171impl<T> Source for ChannelSource<T>
172where
173    T: 'static,
174{
175    type Item = T;
176    type Error = Infallible;
177
178    fn pull(&mut self) -> Result<Option<Self::Item>, Self::Error> {
179        match self.rx.recv() {
180            Ok(item) => Ok(Some(item)),
181            Err(_) => Ok(None),
182        }
183    }
184}
185
186/// Line-buffered [`Source`] over any [`std::io::Read`]. Each `pull`
187/// returns one line (without the trailing newline).
188#[cfg(feature = "std")]
189#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
190pub struct ReaderSource<R: std::io::Read> {
191    reader: std::io::BufReader<R>,
192    buf: String,
193}
194
195#[cfg(feature = "std")]
196impl<R: std::io::Read> ReaderSource<R> {
197    /// Wrap a reader into a line-buffered source.
198    pub fn new(reader: R) -> Self {
199        Self {
200            reader: std::io::BufReader::new(reader),
201            buf: String::new(),
202        }
203    }
204
205    /// Wrap a reader with a specific BufReader capacity.
206    pub fn with_capacity(capacity: usize, reader: R) -> Self {
207        Self {
208            reader: std::io::BufReader::with_capacity(capacity, reader),
209            buf: String::new(),
210        }
211    }
212}
213
214#[cfg(feature = "std")]
215impl<R: std::io::Read> Source for ReaderSource<R> {
216    type Item = String;
217    type Error = std::io::Error;
218
219    fn pull(&mut self) -> Result<Option<Self::Item>, Self::Error> {
220        use std::io::BufRead;
221        self.buf.clear();
222        let read = self.reader.read_line(&mut self.buf)?;
223        if read == 0 {
224            return Ok(None);
225        }
226        if self.buf.ends_with('\n') {
227            self.buf.pop();
228            if self.buf.ends_with('\r') {
229                self.buf.pop();
230            }
231        }
232        Ok(Some(core::mem::take(&mut self.buf)))
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use alloc::vec;
240    use alloc::vec::Vec;
241
242    #[test]
243    fn iter_source_exhausts() {
244        let mut s = IterSource::new(vec![10, 20, 30]);
245        let mut out = Vec::new();
246        while let Some(v) = s.pull().unwrap() {
247            out.push(v);
248        }
249        assert_eq!(out, vec![10, 20, 30]);
250    }
251
252    #[test]
253    fn fn_source_can_fail() {
254        let mut state = 0;
255        let mut s = FnSource::new(move || -> Result<Option<u32>, &'static str> {
256            state += 1;
257            if state == 2 {
258                Err("nope")
259            } else {
260                Ok(Some(state))
261            }
262        });
263        assert_eq!(s.pull().unwrap(), Some(1));
264        assert!(s.pull().is_err());
265    }
266
267    #[cfg(feature = "std")]
268    #[test]
269    fn reader_source_splits_lines() {
270        use std::io::Cursor;
271        let data = "alpha\nbeta\r\ngamma";
272        let mut s = ReaderSource::new(Cursor::new(data));
273        assert_eq!(s.pull().unwrap().as_deref(), Some("alpha"));
274        assert_eq!(s.pull().unwrap().as_deref(), Some("beta"));
275        assert_eq!(s.pull().unwrap().as_deref(), Some("gamma"));
276        assert_eq!(s.pull().unwrap(), None);
277    }
278
279    #[cfg(feature = "std")]
280    #[test]
281    fn channel_source_terminates_when_sender_dropped() {
282        let (tx, rx) = std::sync::mpsc::channel::<i32>();
283        let mut s = ChannelSource::new(rx);
284        tx.send(1).unwrap();
285        tx.send(2).unwrap();
286        drop(tx);
287        assert_eq!(s.pull().unwrap(), Some(1));
288        assert_eq!(s.pull().unwrap(), Some(2));
289        assert_eq!(s.pull().unwrap(), None);
290    }
291}