Skip to main content

serde_stream_formats/
encode.rs

1//! Bounded streaming encoding of Serde values.
2
3use std::fmt;
4use std::io::{
5    self,
6    Write as IoWrite,
7};
8use std::pin::Pin;
9use std::task::{
10    Context,
11    Poll,
12};
13
14use bytes::Bytes;
15use serde::Serialize;
16use tokio::sync::mpsc;
17use tokio_stream::Stream;
18use tokio_stream::wrappers::ReceiverStream;
19
20use crate::error::FormatError;
21
22/// How many encoded chunks may sit between the serializer and the
23/// consumer before the serializer backpressures.
24const SERIALIZER_CHANNEL_CAPACITY: usize = 8;
25/// The largest single chunk the serializer emits.
26const MAX_SERIALIZER_CHUNK_BYTES: usize = 64 * 1024;
27
28/// A Serde wire format this crate can encode as a bounded stream.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum EncodeFormat {
31    /// `application/json`.
32    Json,
33    /// `application/yaml` (RFC 9512).
34    Yaml,
35    /// `application/vnd.msgpack`, encoded with named fields so the
36    /// output stays self-describing.
37    MessagePack,
38    /// `application/x-postcard`.
39    Postcard,
40}
41
42impl EncodeFormat {
43    /// The format's canonical media type.
44    #[must_use]
45    pub const fn media_type(self) -> &'static str {
46        match self {
47            Self::Json => "application/json",
48            Self::Yaml => "application/yaml",
49            Self::MessagePack => "application/vnd.msgpack",
50            Self::Postcard => "application/x-postcard",
51        }
52    }
53
54    /// The format's display name for error detail.
55    #[must_use]
56    pub const fn name(self) -> &'static str {
57        match self {
58            Self::Json => "JSON",
59            Self::Yaml => "YAML",
60            Self::MessagePack => "MessagePack",
61            Self::Postcard => "Postcard",
62        }
63    }
64
65    /// Resolve a media-type value to an encodable format.
66    ///
67    /// Parameters are ignored; matching is case-insensitive; documented
68    /// pre-registration aliases are accepted for YAML and `MessagePack`.
69    #[must_use]
70    pub fn from_content_type(content_type: &str) -> Option<Self> {
71        let media_type = content_type
72            .split(';')
73            .next()
74            .unwrap_or_default()
75            .trim()
76            .to_ascii_lowercase();
77        match media_type.as_str() {
78            "application/json" => Some(Self::Json),
79            "application/yaml" | "application/x-yaml" | "text/yaml" => Some(Self::Yaml),
80            "application/vnd.msgpack" | "application/msgpack" | "application/x-msgpack" => {
81                Some(Self::MessagePack)
82            }
83            "application/x-postcard" => Some(Self::Postcard),
84            _ => None,
85        }
86    }
87
88    /// Encode a bounded value into one in-memory buffer.
89    ///
90    /// For payloads that must exist before headers are sent (an error
91    /// envelope, a small control document). Ordinary payloads stream
92    /// through [`EncodeFormat::encode_stream`].
93    ///
94    /// # Errors
95    ///
96    /// [`FormatError::Encoding`] when the value cannot be encoded in
97    /// this format.
98    pub fn encode_vec<T: Serialize>(self, value: &T) -> Result<Vec<u8>, FormatError> {
99        match self {
100            Self::Json => serde_json::to_vec(value).map_err(|error| self.unencodable(error)),
101            Self::Yaml => serde_yaml2::to_string(value)
102                .map(String::into_bytes)
103                .map_err(|error| self.unencodable(error)),
104            Self::MessagePack => {
105                rmp_serde::to_vec_named(value).map_err(|error| self.unencodable(error))
106            }
107            Self::Postcard => postcard::to_allocvec(value).map_err(|error| self.unencodable(error)),
108        }
109    }
110
111    /// Encode an owned value as an asynchronous, bounded chunk stream.
112    ///
113    /// The serializer runs on a blocking worker and hands chunks of at
114    /// most 64 KiB through a bounded channel, so memory stays
115    /// `O(channel × chunk)` regardless of the encoded size and a slow
116    /// consumer backpressures the serializer instead of buffering. An
117    /// encoding failure surfaces as a trailing `Err` item — the stream
118    /// is never silently truncated.
119    ///
120    /// Must be called from within a Tokio runtime.
121    pub fn encode_stream<T>(self, value: T) -> EncodedStream
122    where
123        T: Serialize + Send + 'static,
124    {
125        let (sender, receiver) = mpsc::channel(SERIALIZER_CHANNEL_CAPACITY);
126        tokio::task::spawn_blocking(move || {
127            let mut writer = ChannelWriter { sender };
128            let result = match self {
129                Self::Json => {
130                    serde_json::to_writer(&mut writer, &value).map_err(|error| error.to_string())
131                }
132                Self::Yaml => {
133                    let mut formatter = ChannelFormatter {
134                        writer: &mut writer,
135                    };
136                    let mut serializer = serde_yaml2::ser::YamlSerializer::new(&mut formatter);
137                    serializer.write(value).map_err(|error| error.to_string())
138                }
139                Self::MessagePack => value
140                    .serialize(&mut rmp_serde::Serializer::new(&mut writer).with_struct_map())
141                    .map_err(|error| error.to_string()),
142                Self::Postcard => postcard::to_io(&value, &mut writer)
143                    .map(|_| ())
144                    .map_err(|error| error.to_string()),
145            };
146            if let Err(error) = result {
147                let format_error = self.unencodable(error);
148                let _ = writer.sender.blocking_send(Err(format_error));
149            }
150        });
151        EncodedStream {
152            inner: ReceiverStream::new(receiver),
153        }
154    }
155
156    fn unencodable(self, error: impl std::fmt::Display) -> FormatError {
157        FormatError::Encoding {
158            format: self.name(),
159            detail: error.to_string(),
160        }
161    }
162}
163
164/// The bounded chunk stream produced by [`EncodeFormat::encode_stream`].
165#[derive(Debug)]
166pub struct EncodedStream {
167    inner: ReceiverStream<Result<Bytes, FormatError>>,
168}
169
170impl Stream for EncodedStream {
171    type Item = Result<Bytes, FormatError>;
172
173    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
174        Pin::new(&mut self.inner).poll_next(cx)
175    }
176}
177
178/// Bridges the synchronous serializer to the bounded chunk channel.
179struct ChannelWriter {
180    sender: mpsc::Sender<Result<Bytes, FormatError>>,
181}
182
183impl IoWrite for ChannelWriter {
184    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
185        if bytes.is_empty() {
186            return Ok(0);
187        }
188        for chunk in bytes.chunks(MAX_SERIALIZER_CHUNK_BYTES) {
189            self.sender
190                .blocking_send(Ok(Bytes::copy_from_slice(chunk)))
191                .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "encoded stream dropped"))?;
192        }
193        Ok(bytes.len())
194    }
195
196    fn flush(&mut self) -> io::Result<()> {
197        Ok(())
198    }
199}
200
201/// Adapts the byte writer for serializers that write `str` output.
202struct ChannelFormatter<'a> {
203    writer: &'a mut ChannelWriter,
204}
205
206impl fmt::Write for ChannelFormatter<'_> {
207    fn write_str(&mut self, value: &str) -> fmt::Result {
208        self.writer
209            .write_all(value.as_bytes())
210            .map_err(|_| fmt::Error)
211    }
212}