Skip to main content

nostd_multipart/
io.rs

1//! Adapters that pull bytes from an `embedded-io` / `embedded-io-async` reader
2//! into a fixed buffer and drive the [`Parser`]. Enabled by the features of the
3//! same names.
4
5use core::fmt;
6
7use crate::parser::Ev;
8use crate::{Error, Event, Parser};
9
10/// Error of the reader adapters.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ReadError<E> {
13    /// The underlying reader failed.
14    Io(E),
15    /// The body is malformed.
16    Parse(Error),
17    /// The buffer is full but the parser cannot make progress: `BUF` is smaller
18    /// than [`Parser::min_buffer`].
19    BufferTooSmall,
20}
21
22impl<E> From<Error> for ReadError<E> {
23    fn from(e: Error) -> Self {
24        ReadError::Parse(e)
25    }
26}
27
28impl<E: fmt::Debug> fmt::Display for ReadError<E> {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            ReadError::Io(e) => write!(f, "read error: {e:?}"),
32            ReadError::Parse(e) => write!(f, "multipart error: {e}"),
33            ReadError::BufferTooSmall => f.write_str("buffer smaller than Parser::min_buffer()"),
34        }
35    }
36}
37
38impl<E: fmt::Debug> core::error::Error for ReadError<E> {}
39
40/// Reader-independent part of the adapters.
41struct Core<'b, const BUF: usize, const LINE: usize, const MAX_HEADERS: usize> {
42    parser: Parser<'b, LINE, MAX_HEADERS>,
43    buf: [u8; BUF],
44    start: usize,
45    end: usize,
46    eof: bool,
47    /// `(offset of the input slice in buf, event)` of the last successful `advance`.
48    pending: Option<(usize, Ev)>,
49}
50
51impl<'b, const BUF: usize, const LINE: usize, const MAX_HEADERS: usize>
52    Core<'b, BUF, LINE, MAX_HEADERS>
53{
54    fn new(parser: Parser<'b, LINE, MAX_HEADERS>) -> Self {
55        Core {
56            parser,
57            buf: [0; BUF],
58            start: 0,
59            end: 0,
60            eof: false,
61            pending: None,
62        }
63    }
64
65    /// Runs the parser on the buffered bytes; `true` once an event is ready
66    /// (fetch it with `event`), `false` if more input must be read.
67    fn advance(&mut self) -> Result<bool, Error> {
68        loop {
69            let input_at = self.start;
70            let input = self.buf.get(input_at..self.end).unwrap_or(&[]);
71            let (consumed, ev) = self.parser.step(input, self.eof)?;
72            self.start = self.start.saturating_add(consumed);
73            if let Some(ev) = ev {
74                self.pending = Some((input_at, ev));
75                return Ok(true);
76            }
77            if consumed == 0 {
78                return Ok(false);
79            }
80        }
81    }
82
83    /// The event recorded by the last successful `advance`.
84    fn event(&self) -> Event<'_, '_> {
85        let (at, ev) = self.pending.unwrap_or((0, Ev::Finished));
86        self.parser
87            .materialize(self.buf.get(at..self.end).unwrap_or(&[]), ev)
88    }
89
90    /// Compacts the buffer and returns the free space to read into.
91    fn free<E>(&mut self) -> Result<&mut [u8], ReadError<E>> {
92        if self.start > 0 {
93            self.buf.copy_within(self.start..self.end, 0);
94            self.end = self.end.saturating_sub(self.start);
95            self.start = 0;
96        }
97        // A full buffer without progress means BUF < min_buffer(). (With `eof`
98        // the parser always progresses, so no read is requested then.)
99        self.buf
100            .get_mut(self.end..)
101            .filter(|s| !s.is_empty())
102            .ok_or(ReadError::BufferTooSmall)
103    }
104
105    fn filled(&mut self, n: usize) {
106        if n == 0 {
107            self.eof = true;
108        } else {
109            self.end = self.end.saturating_add(n).min(BUF);
110        }
111    }
112}
113
114/// Defines a reader adapter over `$read` (blocking or async).
115macro_rules! reader {
116    ($(#[$doc:meta])* $name:ident, $read:path $(, $async:ident $await:ident)?) => {
117        $(#[$doc])*
118        pub struct $name<'b, R, const BUF: usize, const LINE: usize = 512, const MAX_HEADERS: usize = 16> {
119            reader: R,
120            core: Core<'b, BUF, LINE, MAX_HEADERS>,
121        }
122
123        impl<'b, R: $read, const BUF: usize, const LINE: usize, const MAX_HEADERS: usize>
124            $name<'b, R, BUF, LINE, MAX_HEADERS>
125        {
126            /// Wraps `reader`.
127            pub fn new(reader: R, parser: Parser<'b, LINE, MAX_HEADERS>) -> Self {
128                $name { reader, core: Core::new(parser) }
129            }
130
131            /// Returns the next event, reading as needed. After `Finished` it keeps
132            /// returning `Finished`; after an error it keeps returning it.
133            ///
134            /// # Errors
135            /// See [`ReadError`].
136            pub $($async)? fn next_event(&mut self) -> Result<Event<'_, '_>, ReadError<R::Error>> {
137                while !self.core.advance()? {
138                    let dst = self.core.free()?;
139                    let n = self.reader.read(dst)$(.$await)?.map_err(ReadError::Io)?;
140                    self.core.filled(n);
141                }
142                Ok(self.core.event())
143            }
144
145            /// The wrapped parser.
146            pub fn parser(&self) -> &Parser<'b, LINE, MAX_HEADERS> {
147                &self.core.parser
148            }
149
150            /// Returns the reader.
151            pub fn into_inner(self) -> R {
152                self.reader
153            }
154        }
155    };
156}
157
158#[cfg(feature = "embedded-io")]
159reader!(
160    /// Drives a [`Parser`] from a blocking [`embedded_io::Read`] with a `BUF`-byte
161    /// buffer (`BUF >= parser.min_buffer()`).
162    MultipartReader,
163    embedded_io::Read
164);
165
166#[cfg(feature = "embedded-io-async")]
167reader!(
168    /// Drives a [`Parser`] from an [`embedded_io_async::Read`] with a `BUF`-byte
169    /// buffer (`BUF >= parser.min_buffer()`).
170    MultipartReaderAsync,
171    embedded_io_async::Read,
172    async await
173);