Skip to main content

serde_stream_formats/
decode.rs

1//! Incremental decoding of Serde documents from a byte reader.
2
3use std::io::Read;
4
5use serde::de::DeserializeOwned;
6
7use crate::error::FormatError;
8
9/// Scratch capacity for the Postcard incremental decoder.
10const POSTCARD_SCRATCH_BYTES: usize = 16 * 1024;
11
12/// A Serde wire format this crate can decode incrementally from a
13/// reader, without ever owning the complete input.
14///
15/// Whole-document formats (YAML, TOML) are deliberately absent: their
16/// available decoders require the full input in memory, which defeats
17/// the bounded-memory contract. Reject them upstream with a typed
18/// unsupported-media-type failure instead.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum DecodeFormat {
21    /// `application/json`.
22    Json,
23    /// `application/vnd.msgpack`, self-describing (named fields).
24    MessagePack,
25    /// `application/x-postcard` — not self-describing; callers must
26    /// know the schema out of band.
27    Postcard,
28}
29
30impl DecodeFormat {
31    /// The format's canonical media type.
32    #[must_use]
33    pub const fn media_type(self) -> &'static str {
34        match self {
35            Self::Json => "application/json",
36            Self::MessagePack => "application/vnd.msgpack",
37            Self::Postcard => "application/x-postcard",
38        }
39    }
40
41    /// The format's display name for error detail.
42    #[must_use]
43    pub const fn name(self) -> &'static str {
44        match self {
45            Self::Json => "JSON",
46            Self::MessagePack => "MessagePack",
47            Self::Postcard => "Postcard",
48        }
49    }
50
51    /// Resolve a `Content-Type` value to a decodable format.
52    ///
53    /// Parameters (`; charset=…`) are ignored; matching is
54    /// case-insensitive. Documented pre-registration aliases are
55    /// accepted for `MessagePack`. `None` means the media type is not
56    /// an incrementally decodable format.
57    #[must_use]
58    pub fn from_content_type(content_type: &str) -> Option<Self> {
59        let media_type = content_type
60            .split(';')
61            .next()
62            .unwrap_or_default()
63            .trim()
64            .to_ascii_lowercase();
65        match media_type.as_str() {
66            "application/json" => Some(Self::Json),
67            "application/vnd.msgpack" | "application/msgpack" | "application/x-msgpack" => {
68                Some(Self::MessagePack)
69            }
70            "application/x-postcard" => Some(Self::Postcard),
71            _ => None,
72        }
73    }
74
75    /// Decode a complete in-memory document.
76    ///
77    /// For inputs that are already bounded (a small control message, a
78    /// test fixture). Streaming inputs go through
79    /// [`DecodeFormat::decode_reader`].
80    ///
81    /// # Errors
82    ///
83    /// [`FormatError::MalformedDocument`] with decoder position detail
84    /// (where the decoder provides it).
85    pub fn decode_slice<T: DeserializeOwned>(self, body: &[u8]) -> Result<T, FormatError> {
86        match self {
87            Self::Json => {
88                serde_json::from_slice(body).map_err(|error| FormatError::MalformedDocument {
89                    format: self.name(),
90                    detail: format!("line {} column {}: {error}", error.line(), error.column()),
91                })
92            }
93            Self::MessagePack => rmp_serde::from_slice(body).map_err(|error| self.malformed(error)),
94            Self::Postcard => postcard::from_bytes(body).map_err(|error| self.malformed(error)),
95        }
96    }
97
98    /// Decode a document incrementally from `reader`.
99    ///
100    /// The decoder pulls from the reader as it parses, so the caller
101    /// never owns the complete input. Run it on a blocking-capable
102    /// thread when the reader bridges an asynchronous source.
103    ///
104    /// A reader that enforces a size limit should surface the limit
105    /// hit as `io::Error::other(`[`crate::PayloadLimitExceeded`]`)`;
106    /// it is reported as [`FormatError::PayloadTooLarge`].
107    ///
108    /// # Errors
109    ///
110    /// [`FormatError::Read`] / [`FormatError::PayloadTooLarge`] when
111    /// the reader fails, [`FormatError::MalformedDocument`] when the
112    /// input is not a valid document in this format.
113    pub fn decode_reader<T, R>(self, reader: R) -> Result<T, FormatError>
114    where
115        T: DeserializeOwned,
116        R: Read,
117    {
118        match self {
119            Self::Json => serde_json::from_reader(reader).map_err(|error| {
120                if error.is_io() {
121                    return FormatError::from_read_error(&error.into());
122                }
123                FormatError::MalformedDocument {
124                    format: self.name(),
125                    detail: error.to_string(),
126                }
127            }),
128            Self::MessagePack => rmp_serde::from_read(reader).map_err(|error| match error {
129                rmp_serde::decode::Error::InvalidMarkerRead(error)
130                | rmp_serde::decode::Error::InvalidDataRead(error) => {
131                    FormatError::from_read_error(&error)
132                }
133                error => self.malformed(error),
134            }),
135            Self::Postcard => {
136                let mut scratch = [0_u8; POSTCARD_SCRATCH_BYTES];
137                postcard::from_io((reader, &mut scratch))
138                    .map(|(value, _)| value)
139                    .map_err(|error| self.malformed(error))
140            }
141        }
142    }
143
144    /// A typed malformed-document failure carrying the decoder's detail.
145    fn malformed(self, error: impl std::fmt::Display) -> FormatError {
146        FormatError::MalformedDocument {
147            format: self.name(),
148            detail: error.to_string(),
149        }
150    }
151}