ruststream/codec/mod.rs
1//! Pluggable serialization codecs.
2//!
3//! A [`Codec`] turns a value into wire bytes and back. It is not tied to the broker: on the
4//! consume side the pipeline is `bytes -> Codec -> typed payload -> handler`, and on the publish
5//! side the same runs in reverse. The codec is fixed where the handler is mounted, so it costs
6//! nothing on the delivery path.
7//!
8//! # Cargo features
9//!
10//! Codecs are additive cargo features: enable only what you need. A message type derives
11//! `serde::Deserialize`, and a reply type `Serialize` as well.
12//!
13//! * `json` (default): [`JsonCodec`] via `serde_json`, `application/json`.
14//! * `msgpack`: [`MsgpackCodec`] via `rmp-serde`, `application/msgpack`.
15//! * `cbor`: [`CborCodec`] via `ciborium`, `application/cbor`.
16//!
17//! [`DefaultCodec`] is an alias picked by the enabled features: `json`, else `cbor`, else
18//! `msgpack`. It is what `include(def)` decodes with and what `.out_reply(policy)` encodes with
19//! when nothing names a codec. With no codec feature at all there is nothing to name, so a
20//! mounting that needs the default becomes a compile error listing the ways out: enable a
21//! feature, name a codec, or move the message to a byte lane.
22//!
23//! # Where the codec comes from
24//!
25//! The decode codec comes from the most specific level that names one, and `include` takes no
26//! codec argument:
27//!
28//! 1. per handler, `Router::new().with_codec(codec).include(def)`;
29//! 2. per scope, `with_broker_codec(broker, codec, |b| ..)`;
30//! 3. [`DefaultCodec`].
31//!
32//! The publish side mirrors it at the mount site: `.out_reply(policy).codec(codec)` names the
33//! reply codec, `.out(marker, policy).codec(codec)` a slot's, and `.with_codec(codec)` on the
34//! builder one call's. The request decodes with the scope's codec while the reply travels on the
35//! wiring the chain built, so the two formats differ freely. The codec is a property of the
36//! mounting, never of the message type.
37//!
38//! ```
39//! # #[cfg(all(feature = "macros", feature = "memory", feature = "json", feature = "cbor"))]
40//! # mod demo {
41//! use ruststream::codec::{CborCodec, JsonCodec};
42//! use ruststream::memory::prelude::*;
43//! use serde::Deserialize;
44//!
45//! # #[derive(Deserialize)]
46//! # struct Order {
47//! # id: u64,
48//! # }
49//! # #[subscriber("orders")]
50//! # async fn handle(order: &Order) -> HandlerOutcome {
51//! # HandlerOutcome::ack()
52//! # }
53//! # #[subscriber("audit")]
54//! # async fn audit(order: &Order) -> HandlerOutcome {
55//! # HandlerOutcome::ack()
56//! # }
57//! fn app() -> RustStream {
58//! RustStream::new(AppInfo::new("codecs", "0.1.0"))
59//! // per scope: every handler mounted here decodes with CBOR
60//! .with_broker_codec(MemoryBroker::new(), CborCodec, |b| {
61//! b.include(handle);
62//! // per handler: this mounting decodes with JSON
63//! b.include_router(Router::new().with_codec(JsonCodec).include(audit));
64//! })
65//! }
66//! # }
67//! # fn main() {}
68//! ```
69//!
70//! A payload that does not decode is settled by the subscriber's `on_failure(decode = ..)`
71//! policy, a drop by default; see [`FailurePolicy`](crate::runtime::FailurePolicy).
72//!
73//! # Byte lanes
74//!
75//! A value that is its own encoding takes no codec position. A newtype over `&'a [u8]` deriving
76//! [`Deserialized`](macro@crate::Deserialized) receives the delivery's bytes as they arrived,
77//! and a type deriving [`Serialized`](macro@crate::Serialized) publishes the bytes it produces.
78//! A generated Protobuf message goes on both lanes with `#[wire(prost)]` next to the derives;
79//! the general form names the functions, `#[wire(encode = <path>, decode = <path>)]`, and serves
80//! Cap'n Proto, `FlatBuffers` and a hand-rolled frame alike. A service on the lanes alone runs
81//! with no codec feature.
82//!
83//! # A codec of your own
84//!
85//! Implement [`Codec`] and pass the value wherever a built-in one goes. A codec generic over
86//! another composes: the inner one decides the format, the wrapper transforms the bytes around
87//! it, which is the shape of a versioned envelope, a registry header or an encrypting layer.
88//! Both sides return [`CodecError`]; an inner error passes up with `?`, and a failure of the
89//! wrapper becomes [`CodecError::Decode`] or [`CodecError::Encode`] with your error as its
90//! source. [`Codec::CONTENT_TYPE`] names the media type the generated document reports.
91//!
92//! ```
93//! # #[cfg(not(feature = "json"))]
94//! # fn main() {}
95//! # #[cfg(feature = "json")]
96//! # fn main() {
97//! use std::fmt;
98//!
99//! use ruststream::BytesMut;
100//! use ruststream::codec::{Codec, CodecError, JsonCodec};
101//! use serde::Serialize;
102//! use serde::de::DeserializeOwned;
103//!
104//! /// Frames another codec's bytes with a one-byte format version.
105//! #[derive(Clone, Copy)]
106//! struct Versioned<C>(C);
107//!
108//! const VERSION: u8 = 1;
109//!
110//! #[derive(Debug)]
111//! struct BadVersion(u8);
112//!
113//! impl fmt::Display for BadVersion {
114//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115//! write!(f, "envelope version {} is not the version this build reads", self.0)
116//! }
117//! }
118//!
119//! impl std::error::Error for BadVersion {}
120//!
121//! impl<C: Codec> Codec for Versioned<C> {
122//! const CONTENT_TYPE: &'static str = C::CONTENT_TYPE;
123//!
124//! fn encode<T: Serialize>(&self, value: &T) -> Result<BytesMut, CodecError> {
125//! let mut framed = BytesMut::new();
126//! self.encode_into(value, &mut framed)?;
127//! Ok(framed)
128//! }
129//!
130//! fn encode_into<T: Serialize>(&self, value: &T, buf: &mut BytesMut) -> Result<(), CodecError> {
131//! buf.extend_from_slice(&[VERSION]);
132//! self.0.encode_into(value, buf)
133//! }
134//!
135//! fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, CodecError> {
136//! match bytes {
137//! [VERSION, payload @ ..] => self.0.decode(payload),
138//! [version, ..] => Err(CodecError::Decode(Box::new(BadVersion(*version)))),
139//! [] => Err(CodecError::Decode(Box::new(BadVersion(0)))),
140//! }
141//! }
142//! }
143//!
144//! let codec = Versioned(JsonCodec);
145//! let bytes = codec.encode(&7u32).unwrap();
146//! assert_eq!(bytes[0], VERSION);
147//! let back: u32 = codec.decode(&bytes).unwrap();
148//! assert_eq!(back, 7);
149//! # }
150//! ```
151//!
152//! [`Codec::encode_into`] is the buffer-taking sibling of `encode`, and the one the publish path
153//! calls for a broker that only reads the payload: inside a dispatch loop that buffer is the
154//! loop's own, so a codec that writes into it leaves the publish nothing to allocate. It is
155//! defaulted (encode into a buffer of its own, then copy), so a codec compiles without it and is
156//! faster with it. The wrapper above writes its version byte into the caller's buffer and hands
157//! the rest to the inner codec, which is why its `encode` is the one line that calls it.
158//!
159//! `encode` and `decode` are synchronous, and that is the boundary of what fits: only what a
160//! constant and the bytes at hand decide. An integration that needs I/O to serialize (a schema
161//! registry, a key service) goes around the codec, on the async edges: the broker's delivery
162//! path on the way in, a [`PublishLayer`](crate::runtime::PublishLayer) on the way out.
163
164/// The block an encode buffer starts with, in bytes.
165///
166/// A serializer writes a document in fragments, so a buffer that starts empty is grown - and
167/// reallocated - more than once before even the smallest message is out. This is the capacity
168/// `serde_json`'s own `to_vec` reserves, and it puts a single block between a value and the wire
169/// at every size: a small document fits the first write, and a larger one grows from here exactly
170/// as a `Vec` would.
171#[cfg(any(feature = "json", feature = "msgpack", feature = "cbor"))]
172pub(crate) const ENCODE_CAPACITY: usize = 128;
173
174#[cfg(feature = "json")]
175mod json;
176
177#[cfg(feature = "msgpack")]
178mod msgpack;
179
180#[cfg(feature = "cbor")]
181mod cbor;
182
183#[cfg(feature = "cbor")]
184pub use self::cbor::CborCodec;
185#[cfg(feature = "json")]
186pub use json::JsonCodec;
187#[cfg(feature = "msgpack")]
188pub use msgpack::MsgpackCodec;
189
190/// The codec used when an `include` / publisher call does not name one explicitly.
191///
192/// Resolved at compile time by feature priority: `json`, then `cbor`, then `msgpack`. It exists
193/// only when at least one codec feature is enabled; with none, every call site must name a codec.
194#[cfg(feature = "json")]
195pub type DefaultCodec = JsonCodec;
196/// The codec used when an `include` / publisher call does not name one explicitly. See the `json`
197/// variant for details.
198#[cfg(all(not(feature = "json"), feature = "cbor"))]
199pub type DefaultCodec = CborCodec;
200/// The codec used when an `include` / publisher call does not name one explicitly. See the `json`
201/// variant for details.
202#[cfg(all(not(feature = "json"), not(feature = "cbor"), feature = "msgpack"))]
203pub type DefaultCodec = MsgpackCodec;
204
205use std::error::Error as StdError;
206#[cfg(any(feature = "json", feature = "msgpack", feature = "cbor"))]
207use std::io;
208
209use bytes::BytesMut;
210use serde::{Serialize, de::DeserializeOwned};
211use thiserror::Error;
212
213/// The [`io::Write`] a serializer writes an encode buffer through: every write is an append.
214///
215/// `bytes`' own writer answers `write_all` with the trait's default loop over `write`, which the
216/// optimizer inlines or not depending on what else the binary holds; the difference is several
217/// hundred instructions per encoded message. These methods are appends marked for inlining, so
218/// the encode path costs the same whatever else changes around it.
219#[cfg(any(feature = "json", feature = "msgpack", feature = "cbor"))]
220pub(crate) struct BufWriter<'a>(pub(crate) &'a mut BytesMut);
221
222#[cfg(any(feature = "json", feature = "msgpack", feature = "cbor"))]
223impl io::Write for BufWriter<'_> {
224 #[inline]
225 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
226 self.0.extend_from_slice(buf);
227 Ok(buf.len())
228 }
229
230 #[inline]
231 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
232 self.0.extend_from_slice(buf);
233 Ok(())
234 }
235
236 #[inline]
237 fn flush(&mut self) -> io::Result<()> {
238 Ok(())
239 }
240}
241
242/// Errors returned by codec implementations.
243#[derive(Debug, Error)]
244#[non_exhaustive]
245pub enum CodecError {
246 /// The codec failed to encode a Rust value into bytes.
247 #[error("encode failed: {0}")]
248 Encode(#[source] Box<dyn StdError + Send + Sync>),
249
250 /// The codec failed to decode bytes into a Rust value.
251 #[error("decode failed: {0}")]
252 Decode(#[source] Box<dyn StdError + Send + Sync>),
253}
254
255/// A serializer that converts Rust values to and from bytes.
256///
257/// Implementations are stateless and cheap to clone. The trait uses generic methods rather
258/// than associated types so a single codec instance can handle any `Serialize` /
259/// `DeserializeOwned` value. This means `dyn Codec` is not object-safe; use generics or
260/// boxed concrete codecs at the call site.
261///
262/// # Examples
263///
264/// ```
265/// # #[cfg(feature = "json")]
266/// # fn main() -> Result<(), ruststream::codec::CodecError> {
267/// use ruststream::codec::{Codec, JsonCodec};
268/// # use serde::{Serialize, Deserialize};
269///
270/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
271/// struct Order { id: u32, total: f64 }
272///
273/// let codec = JsonCodec;
274/// let bytes = codec.encode(&Order { id: 1, total: 9.99 })?;
275/// let back: Order = codec.decode(&bytes)?;
276/// assert_eq!(back, Order { id: 1, total: 9.99 });
277/// # Ok(())
278/// # }
279/// # #[cfg(not(feature = "json"))]
280/// # fn main() {}
281/// ```
282pub trait Codec: Send + Sync {
283 /// The media type of the bytes this codec produces, reported as the `contentType` of every
284 /// message it encodes or decodes in the generated `AsyncAPI` document.
285 ///
286 /// The built-in codecs name `application/json`, `application/cbor` (RFC 8949) and
287 /// `application/msgpack`. `MessagePack` has no registered media type; of the two spellings
288 /// in use, `application/msgpack` is the one this crate reports.
289 ///
290 /// The default is `application/octet-stream`: what a codec whose output carries no media
291 /// type of its own should keep.
292 ///
293 /// # Examples
294 ///
295 /// ```
296 /// # #[cfg(feature = "json")]
297 /// # fn main() {
298 /// use ruststream::codec::{Codec, JsonCodec};
299 ///
300 /// assert_eq!(JsonCodec::CONTENT_TYPE, "application/json");
301 /// # }
302 /// # #[cfg(not(feature = "json"))]
303 /// # fn main() {}
304 /// ```
305 const CONTENT_TYPE: &'static str = "application/octet-stream";
306
307 /// Encodes `value` into a mutable byte buffer.
308 ///
309 /// Returning [`BytesMut`] lets the encoded buffer move into the publish pipeline (an
310 /// [`Outgoing`](crate::runtime::Outgoing) payload) without a copy, while still allowing
311 /// publish middleware to mutate it in place. It is what a publish to a transport that keeps
312 /// the payload ([`Take`](crate::Take)) calls, once per message.
313 ///
314 /// # Errors
315 ///
316 /// Returns [`CodecError::Encode`] when the underlying serializer fails.
317 fn encode<T: Serialize>(&self, value: &T) -> Result<BytesMut, CodecError>;
318
319 /// Encodes `value` into the end of a buffer the caller owns.
320 ///
321 /// What a publish to a transport that only reads the payload ([`Lend`](crate::Lend)) calls:
322 /// inside a dispatch loop that buffer is the loop's own, reused for every message, so a
323 /// codec that writes into it leaves the publish path with nothing to allocate. Implement it
324 /// wherever the serializer can write into a sink; the default encodes into a buffer of its
325 /// own and copies, which is correct but pays the allocation this exists to avoid.
326 ///
327 /// # Errors
328 ///
329 /// Returns [`CodecError::Encode`] when the underlying serializer fails. The buffer's
330 /// contents are then unspecified: the publish path discards what a failed encode wrote.
331 ///
332 /// # Examples
333 ///
334 /// ```
335 /// # #[cfg(feature = "json")]
336 /// # fn main() -> Result<(), ruststream::codec::CodecError> {
337 /// use ruststream::BytesMut;
338 /// use ruststream::codec::{Codec, JsonCodec};
339 ///
340 /// let mut buf = BytesMut::new();
341 /// JsonCodec.encode_into(&7u8, &mut buf)?;
342 /// assert_eq!(&buf[..], b"7");
343 /// # Ok(())
344 /// # }
345 /// # #[cfg(not(feature = "json"))]
346 /// # fn main() {}
347 /// ```
348 fn encode_into<T: Serialize>(&self, value: &T, buf: &mut BytesMut) -> Result<(), CodecError> {
349 buf.extend_from_slice(&self.encode(value)?);
350 Ok(())
351 }
352
353 /// Decodes `bytes` into a Rust value of type `T`.
354 ///
355 /// # Errors
356 ///
357 /// Returns [`CodecError::Decode`] when the underlying deserializer fails.
358 fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, CodecError>;
359}