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};