Skip to main content

reliar_core/
serializer.rs

1//! Body ⇄ bytes conversion (ADR 0010).
2
3use crate::{ContentType, Message};
4
5/// Converts a typed [`Message`] body to and from bytes. Lives in `reliar-core`: it touches
6/// neither storage nor transport (ADR 0010).
7///
8/// Stateless and cheap; implementations must never be placed behind a `dyn Serializer` on the
9/// enqueue path — it runs once per message and static dispatch keeps it that cheap (ADR 0001).
10///
11/// ```
12/// use bytes::Bytes;
13/// use reliar_core::{ContentType, Message, Serializer};
14///
15/// /// A minimal serializer wrapping a fixed encoder, for hosts that already have one.
16/// struct Fixed(Bytes);
17///
18/// impl Serializer for Fixed {
19///     type Error = std::convert::Infallible;
20///
21///     fn content_type(&self) -> &ContentType {
22///         &ContentType::JSON
23///     }
24///
25///     fn serialize<T: Message>(&self, _body: &T) -> Result<Bytes, Self::Error> {
26///         Ok(self.0.clone())
27///     }
28///
29///     fn deserialize<T: Message>(&self, _bytes: &[u8]) -> Result<T, Self::Error> {
30///         unimplemented!("example encoder, not a real round trip")
31///     }
32/// }
33///
34/// let serializer = Fixed(Bytes::from_static(b"{}"));
35/// assert_eq!(serializer.content_type().as_str(), "application/json");
36/// ```
37pub trait Serializer: Send + Sync {
38    /// The serializer's own error type.
39    type Error: std::error::Error + Send + Sync + 'static;
40
41    /// The content type this serializer produces. Populates both
42    /// [`DeliveryMetadata::content_type`](crate::DeliveryMetadata::content_type) and a
43    /// provider's `content_type` column — one value, chosen by the serializer, never by the
44    /// call site.
45    ///
46    /// ```
47    /// # use bytes::Bytes;
48    /// # use reliar_core::{ContentType, Message, Serializer};
49    /// # struct Fixed(Bytes);
50    /// # impl Serializer for Fixed {
51    /// #     type Error = std::convert::Infallible;
52    /// #     fn content_type(&self) -> &ContentType { &ContentType::JSON }
53    /// #     fn serialize<T: Message>(&self, _body: &T) -> Result<Bytes, Self::Error> { Ok(self.0.clone()) }
54    /// #     fn deserialize<T: Message>(&self, _bytes: &[u8]) -> Result<T, Self::Error> { unimplemented!() }
55    /// # }
56    /// let serializer = Fixed(Bytes::from_static(b"{}"));
57    /// assert_eq!(serializer.content_type().as_str(), "application/json");
58    /// ```
59    fn content_type(&self) -> &ContentType;
60
61    /// Serializes a message body to bytes.
62    ///
63    /// # Errors
64    ///
65    /// Returns `Self::Error` if `body` cannot be represented in this serializer's format.
66    ///
67    /// ```
68    /// # use bytes::Bytes;
69    /// # use reliar_core::{ContentType, Message, Serializer};
70    /// # struct Fixed(Bytes);
71    /// # impl Serializer for Fixed {
72    /// #     type Error = std::convert::Infallible;
73    /// #     fn content_type(&self) -> &ContentType { &ContentType::JSON }
74    /// #     fn serialize<T: Message>(&self, _body: &T) -> Result<Bytes, Self::Error> { Ok(self.0.clone()) }
75    /// #     fn deserialize<T: Message>(&self, _bytes: &[u8]) -> Result<T, Self::Error> { unimplemented!() }
76    /// # }
77    /// # #[derive(serde::Serialize, serde::Deserialize)]
78    /// # struct Ping;
79    /// # impl Message for Ping { const TYPE: &'static str = "ping"; const VERSION: u16 = 1; }
80    /// let serializer = Fixed(Bytes::from_static(b"{}"));
81    /// let bytes = serializer.serialize(&Ping)?;
82    /// assert_eq!(bytes.as_ref(), b"{}");
83    /// # Ok::<(), std::convert::Infallible>(())
84    /// ```
85    fn serialize<T: Message>(&self, body: &T) -> Result<bytes::Bytes, Self::Error>;
86
87    /// Deserializes bytes back into a message body.
88    ///
89    /// # Errors
90    ///
91    /// Returns `Self::Error` if `bytes` is not a valid encoding of `T` in this serializer's
92    /// format.
93    ///
94    /// ```
95    /// # #[cfg(feature = "json")]
96    /// # fn run() -> Result<(), reliar_core::JsonError> {
97    /// use reliar_core::{JsonSerializer, Message, Serializer};
98    ///
99    /// #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
100    /// struct Ping;
101    /// impl Message for Ping {
102    ///     const TYPE: &'static str = "ping";
103    ///     const VERSION: u16 = 1;
104    /// }
105    ///
106    /// let body: Ping = JsonSerializer.deserialize(b"null")?;
107    /// assert_eq!(body, Ping);
108    /// # Ok(())
109    /// # }
110    /// # #[cfg(feature = "json")]
111    /// # run().unwrap();
112    /// ```
113    fn deserialize<T: Message>(&self, bytes: &[u8]) -> Result<T, Self::Error>;
114}
115
116#[cfg(feature = "json")]
117#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
118mod json {
119    use core::fmt;
120
121    use bytes::Bytes;
122
123    use super::Serializer;
124    use crate::{ContentType, Message};
125
126    /// The default [`Serializer`]: JSON via `serde_json`. Ships behind the default `json`
127    /// feature; disable it to supply a different wire format (ADR 0010).
128    ///
129    /// ```
130    /// use reliar_core::{JsonSerializer, Serializer};
131    ///
132    /// #[derive(serde::Serialize, serde::Deserialize)]
133    /// struct Ping;
134    /// impl reliar_core::Message for Ping {
135    ///     const TYPE: &'static str = "ping";
136    ///     const VERSION: u16 = 1;
137    /// }
138    ///
139    /// let serializer = JsonSerializer;
140    /// let bytes = serializer.serialize(&Ping)?;
141    /// let _: Ping = serializer.deserialize(&bytes)?;
142    /// assert_eq!(serializer.content_type().as_str(), "application/json");
143    /// # Ok::<(), reliar_core::JsonError>(())
144    /// ```
145    #[derive(Clone, Debug, Default)]
146    pub struct JsonSerializer;
147
148    impl Serializer for JsonSerializer {
149        type Error = JsonError;
150
151        fn content_type(&self) -> &ContentType {
152            &ContentType::JSON
153        }
154
155        fn serialize<T: Message>(&self, body: &T) -> Result<Bytes, Self::Error> {
156            serde_json::to_vec(body)
157                .map(Bytes::from)
158                .map_err(|source| JsonError::Serialize { source })
159        }
160
161        fn deserialize<T: Message>(&self, bytes: &[u8]) -> Result<T, Self::Error> {
162            serde_json::from_slice(bytes).map_err(|source| JsonError::Deserialize { source })
163        }
164    }
165
166    /// [`JsonSerializer`] failures. `Display` names the operation, the error class
167    /// (`serde_json::error::Category`), and the line/column — **never `serde_json::Error`'s own
168    /// message**, which for a data error embeds a fragment of the value it rejected (e.g.
169    /// `invalid type: string "sk-live-…", expected u64`). The full underlying error, message
170    /// included, is still reachable via [`std::error::Error::source`] for a caller that
171    /// deliberately wants it — that caller's own logging then owns not leaking a payload
172    /// fragment, the same rule this type upholds by default.
173    ///
174    /// **`Debug` is a manual impl, never derived**: `serde_json::Error`'s own `Debug` embeds its
175    /// `Display` message (the same payload fragment `Display` above must avoid), so deriving
176    /// here would leak through `{:?}` even though `Display` is safe.
177    ///
178    /// ```
179    /// use reliar_core::{JsonSerializer, Serializer};
180    ///
181    /// #[derive(serde::Serialize, serde::Deserialize)]
182    /// struct Ping;
183    /// impl reliar_core::Message for Ping {
184    ///     const TYPE: &'static str = "ping";
185    ///     const VERSION: u16 = 1;
186    /// }
187    ///
188    /// let result = JsonSerializer.deserialize::<Ping>(b"not json");
189    /// let err = match result {
190    ///     Ok(_) => unreachable!("not valid JSON"),
191    ///     Err(err) => err,
192    /// };
193    /// // The message never echoes a fragment of the rejected payload.
194    /// assert!(err.to_string().starts_with("failed to deserialize from JSON:"));
195    /// ```
196    #[non_exhaustive]
197    pub enum JsonError {
198        /// Serializing a body to JSON failed.
199        Serialize {
200            /// The underlying `serde_json` error.
201            source: serde_json::Error,
202        },
203
204        /// Deserializing bytes into a body failed.
205        Deserialize {
206            /// The underlying `serde_json` error.
207            source: serde_json::Error,
208        },
209    }
210
211    /// Renders a `serde_json::Error` as its classification and position only — never its
212    /// `Display`, which embeds a fragment of the offending payload for data errors.
213    fn describe(source: &serde_json::Error) -> String {
214        let category = match source.classify() {
215            serde_json::error::Category::Io => "io",
216            serde_json::error::Category::Syntax => "syntax",
217            serde_json::error::Category::Data => "data",
218            serde_json::error::Category::Eof => "eof",
219        };
220
221        format!(
222            "{category} error at line {}, column {}",
223            source.line(),
224            source.column()
225        )
226    }
227
228    impl fmt::Display for JsonError {
229        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230            match self {
231                Self::Serialize { source } => {
232                    write!(f, "failed to serialize to JSON: {}", describe(source))
233                }
234                Self::Deserialize { source } => {
235                    write!(f, "failed to deserialize from JSON: {}", describe(source))
236                }
237            }
238        }
239    }
240
241    impl fmt::Debug for JsonError {
242        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243            let (variant, source) = match self {
244                Self::Serialize { source } => ("Serialize", source),
245                Self::Deserialize { source } => ("Deserialize", source),
246            };
247
248            f.debug_struct(variant)
249                .field("classification", &describe(source))
250                .finish()
251        }
252    }
253
254    impl std::error::Error for JsonError {
255        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
256            match self {
257                Self::Serialize { source } | Self::Deserialize { source } => Some(source),
258            }
259        }
260    }
261}
262
263#[cfg(feature = "json")]
264#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
265pub use json::{JsonError, JsonSerializer};