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}