Skip to main content

pack_io/
io.rs

1//! `std::io::Read` / `std::io::Write` integration: the streaming Tier-2
2//! encoder and decoder pair, plus convenience free functions.
3//!
4//! This module is gated on the `std` feature (on by default). With `std` off,
5//! the crate compiles for `no_std` targets using `core` + `alloc` only and
6//! none of this module is reachable.
7//!
8//! ## When to use which entry point
9//!
10//! - For one-shot send / receive of a single value, prefer [`encode_into`] /
11//!   [`decode_from`]: they take any `Write` / `Read` and handle the
12//!   buffering.
13//! - For interleaved writes / reads across many values without per-value
14//!   allocation, instantiate an [`IoEncoder`] / [`IoDecoder`] and call
15//!   `write` / `read` repeatedly.
16//!
17//! ## Errors
18//!
19//! Both directions surface I/O failure through the codec's
20//! [`crate::SerialError`] type via [`SerialError::Io`]. The `Io` variant
21//! captures `std::io::ErrorKind` and a message string — enough to surface
22//! the original cause without taking on a non-`Clone` payload.
23
24use std::io::{Read, Write};
25
26use crate::codec::{Config, Decode, Encode};
27use crate::error::{Result, SerialError};
28use crate::traits::{Deserialize, Serialize};
29
30/// Streaming encoder that writes directly into any [`Write`]-shaped sink.
31///
32/// Each [`IoEncoder::write`] call calls into the underlying writer for the
33/// bytes the type produces. The encoder does **not** buffer; if you wrap a
34/// raw socket / file, wrap it in a [`std::io::BufWriter`] first.
35///
36/// # Examples
37///
38/// ```
39/// use pack_io::IoEncoder;
40///
41/// let mut sink: Vec<u8> = Vec::new();
42/// let mut enc = IoEncoder::new(&mut sink);
43/// enc.write(&42_u64).unwrap();
44/// enc.write(&"hello").unwrap();
45/// assert!(!sink.is_empty());
46/// ```
47#[derive(Debug)]
48pub struct IoEncoder<W: Write> {
49    writer: W,
50}
51
52impl<W: Write> IoEncoder<W> {
53    /// Wrap `writer` in an encoder.
54    #[must_use]
55    pub fn new(writer: W) -> Self {
56        Self { writer }
57    }
58
59    /// Borrow the underlying writer.
60    #[must_use]
61    pub fn writer(&self) -> &W {
62        &self.writer
63    }
64
65    /// Borrow the underlying writer mutably.
66    #[must_use]
67    pub fn writer_mut(&mut self) -> &mut W {
68        &mut self.writer
69    }
70
71    /// Consume the encoder and return the underlying writer.
72    #[must_use]
73    pub fn into_inner(self) -> W {
74        self.writer
75    }
76
77    /// Encode `value` straight into the underlying writer.
78    ///
79    /// # Errors
80    ///
81    /// - Propagates any [`crate::SerialError`] from the type's [`Serialize`].
82    /// - Maps any `std::io::Error` from the writer into [`SerialError::Io`].
83    #[inline]
84    pub fn write<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<()> {
85        value.serialize(self)
86    }
87}
88
89impl<W: Write> Encode for IoEncoder<W> {
90    #[inline]
91    fn write_byte(&mut self, byte: u8) -> Result<()> {
92        self.writer.write_all(&[byte]).map_err(map_io_error)
93    }
94
95    #[inline]
96    fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
97        self.writer.write_all(bytes).map_err(map_io_error)
98    }
99}
100
101/// Streaming decoder that reads directly from any [`Read`]-shaped source.
102///
103/// Each [`IoDecoder::read`] call may issue many small reads against the
104/// underlying source. Wrap raw sockets / files in [`std::io::BufReader`]
105/// first if read-syscall amplification is a concern.
106///
107/// # Examples
108///
109/// ```
110/// use pack_io::{IoEncoder, IoDecoder};
111/// use std::io::Cursor;
112///
113/// let mut buf: Vec<u8> = Vec::new();
114/// {
115///     let mut enc = IoEncoder::new(&mut buf);
116///     enc.write(&42_u64).unwrap();
117///     enc.write(&"hi").unwrap();
118/// }
119///
120/// let mut dec = IoDecoder::new(Cursor::new(buf));
121/// let n: u64 = dec.read().unwrap();
122/// let s: String = dec.read().unwrap();
123/// assert_eq!((n, s.as_str()), (42, "hi"));
124/// ```
125#[derive(Debug)]
126pub struct IoDecoder<R: Read> {
127    reader: R,
128    config: Config,
129}
130
131impl<R: Read> IoDecoder<R> {
132    /// Wrap `reader` with the default [`Config`].
133    #[must_use]
134    pub fn new(reader: R) -> Self {
135        Self {
136            reader,
137            config: Config::default(),
138        }
139    }
140
141    /// Wrap `reader` with the supplied configuration.
142    ///
143    /// # Errors
144    ///
145    /// Returns [`SerialError::InvalidLength`] if `config.max_alloc == 0`.
146    pub fn with_config(reader: R, config: Config) -> Result<Self> {
147        Ok(Self {
148            reader,
149            config: config.validate()?,
150        })
151    }
152
153    /// Borrow the underlying reader.
154    #[must_use]
155    pub fn reader(&self) -> &R {
156        &self.reader
157    }
158
159    /// Consume the decoder and return the underlying reader.
160    #[must_use]
161    pub fn into_inner(self) -> R {
162        self.reader
163    }
164
165    /// Decode the next value from the underlying reader.
166    ///
167    /// # Errors
168    ///
169    /// - Propagates any [`crate::SerialError`] from the type's
170    ///   [`Deserialize`].
171    /// - Maps any `std::io::Error` from the reader into [`SerialError::Io`].
172    #[inline]
173    pub fn read<T: Deserialize>(&mut self) -> Result<T> {
174        T::deserialize(self)
175    }
176}
177
178impl<R: Read> Decode for IoDecoder<R> {
179    fn read_byte(&mut self) -> Result<u8> {
180        let mut buf = [0u8; 1];
181        self.read_into(&mut buf)?;
182        Ok(buf[0])
183    }
184
185    fn read_into(&mut self, out: &mut [u8]) -> Result<()> {
186        self.reader.read_exact(out).map_err(|e| {
187            if e.kind() == std::io::ErrorKind::UnexpectedEof {
188                SerialError::UnexpectedEof {
189                    needed: out.len(),
190                    remaining: 0,
191                }
192            } else {
193                map_io_error(e)
194            }
195        })
196    }
197
198    fn max_alloc(&self) -> usize {
199        self.config.max_alloc
200    }
201}
202
203/// Encode `value` and write the result into `writer` in a single call.
204///
205/// # Errors
206///
207/// - Propagates any [`crate::SerialError`] from the type's [`Serialize`].
208/// - Maps any `std::io::Error` from the writer into [`SerialError::Io`].
209///
210/// # Examples
211///
212/// ```
213/// use pack_io::encode_into;
214///
215/// let mut buf: Vec<u8> = Vec::new();
216/// encode_into(&(7_u64, "hello"), &mut buf).unwrap();
217/// assert!(!buf.is_empty());
218/// ```
219#[inline]
220pub fn encode_into<T, W>(value: &T, writer: &mut W) -> Result<()>
221where
222    T: Serialize + ?Sized,
223    W: Write,
224{
225    let mut enc = IoEncoder::new(writer);
226    enc.write(value)
227}
228
229/// Read all remaining bytes from `reader` and decode them as a single value
230/// of type `T`.
231///
232/// Use this for whole-buffer reads (a length-prefixed message you have
233/// already extracted from the transport, a small config file, …). For
234/// length-framed protocols where the producer wrote one value and then more
235/// bytes for something else, prefer [`IoDecoder`] directly.
236///
237/// # Errors
238///
239/// - Returns [`SerialError::TrailingBytes`] if the reader yielded extra
240///   bytes after the value was decoded.
241/// - Propagates any [`crate::SerialError`] from the type's [`Deserialize`].
242/// - Maps any `std::io::Error` from the reader into [`SerialError::Io`].
243///
244/// # Examples
245///
246/// ```
247/// use pack_io::{encode, decode_from};
248/// use std::io::Cursor;
249///
250/// let bytes = encode(&42_u64).unwrap();
251/// let n: u64 = decode_from(&mut Cursor::new(bytes)).unwrap();
252/// assert_eq!(n, 42);
253/// ```
254pub fn decode_from<T, R>(reader: &mut R) -> Result<T>
255where
256    T: Deserialize,
257    R: Read,
258{
259    let mut buf = alloc::vec::Vec::new();
260    let _ = reader.read_to_end(&mut buf).map_err(map_io_error)?;
261    crate::decode(&buf)
262}
263
264/// Map a `std::io::Error` into [`SerialError::Io`].
265#[inline]
266fn map_io_error(err: std::io::Error) -> SerialError {
267    use alloc::string::ToString;
268    SerialError::Io {
269        kind: err.kind(),
270        message: err.to_string(),
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::encode;
278    use alloc::vec::Vec;
279    use std::io::Cursor;
280
281    #[test]
282    fn io_encoder_decoder_round_trip() {
283        let mut buf: Vec<u8> = Vec::new();
284        {
285            let mut enc = IoEncoder::new(&mut buf);
286            enc.write(&42_u64).unwrap();
287            enc.write(&"hello").unwrap();
288            enc.write(&true).unwrap();
289        }
290        let mut dec = IoDecoder::new(Cursor::new(buf));
291        let n: u64 = dec.read().unwrap();
292        let s: String = dec.read().unwrap();
293        let b: bool = dec.read().unwrap();
294        assert_eq!((n, s.as_str(), b), (42, "hello", true));
295    }
296
297    #[test]
298    fn encode_into_writes_same_bytes_as_encode() {
299        let value = (1u32, String::from("hi"), -2i32);
300        let from_fn = encode(&value).unwrap();
301        let mut from_io: Vec<u8> = Vec::new();
302        encode_into(&value, &mut from_io).unwrap();
303        assert_eq!(from_fn, from_io);
304    }
305
306    #[test]
307    fn decode_from_reads_same_value_as_decode() {
308        let bytes = encode(&(7u64, true)).unwrap();
309        let value: (u64, bool) = decode_from(&mut Cursor::new(bytes)).unwrap();
310        assert_eq!(value, (7, true));
311    }
312
313    #[test]
314    fn io_decoder_with_zero_cap_is_rejected() {
315        let cfg = Config::new().with_max_alloc(0);
316        let bytes: Vec<u8> = Vec::new();
317        let err = IoDecoder::with_config(Cursor::new(bytes), cfg).expect_err("zero cap");
318        assert!(matches!(err, SerialError::InvalidLength { .. }));
319    }
320
321    #[test]
322    fn io_decoder_short_read_surfaces_unexpected_eof() {
323        // Two-byte varint that says "more coming" but there's nothing after.
324        let bytes = alloc::vec![0x80];
325        let mut dec = IoDecoder::new(Cursor::new(bytes));
326        let err = dec.read::<u64>().expect_err("truncated");
327        assert!(matches!(err, SerialError::UnexpectedEof { .. }));
328    }
329}