reliar_core/serializer.rs
1//! Body ⇄ bytes conversion (SRS §12.1, 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 SHALL NOT be placed behind a `dyn Serializer` on the
9/// enqueue path (ADR 0001).
10pub trait Serializer: Send + Sync {
11 /// The serializer's own error type.
12 type Error: std::error::Error + Send + Sync + 'static;
13
14 /// The content type this serializer produces. Populates both
15 /// [`DeliveryMetadata::content_type`](crate::DeliveryMetadata::content_type) and a
16 /// provider's `content_type` column — one value, chosen by the serializer, never by the
17 /// call site.
18 fn content_type(&self) -> &ContentType;
19
20 /// Serializes a message body to bytes.
21 ///
22 /// # Errors
23 ///
24 /// Returns `Self::Error` if `body` cannot be represented in this serializer's format.
25 fn serialize<T: Message>(&self, body: &T) -> Result<bytes::Bytes, Self::Error>;
26
27 /// Deserializes bytes back into a message body.
28 ///
29 /// # Errors
30 ///
31 /// Returns `Self::Error` if `bytes` is not a valid encoding of `T` in this serializer's
32 /// format.
33 fn deserialize<T: Message>(&self, bytes: &[u8]) -> Result<T, Self::Error>;
34}
35
36#[cfg(feature = "json")]
37mod json {
38 use core::fmt;
39
40 use bytes::Bytes;
41
42 use super::Serializer;
43 use crate::{ContentType, Message};
44
45 /// The default [`Serializer`]: JSON via `serde_json`. Ships behind the default `json`
46 /// feature; disable it to supply a different wire format (ADR 0010).
47 ///
48 /// ```
49 /// use reliar_core::{JsonSerializer, Serializer};
50 ///
51 /// #[derive(serde::Serialize, serde::Deserialize)]
52 /// struct Ping;
53 /// impl reliar_core::Message for Ping {
54 /// const TYPE: &'static str = "ping";
55 /// const VERSION: u16 = 1;
56 /// }
57 ///
58 /// let serializer = JsonSerializer;
59 /// let bytes = serializer.serialize(&Ping)?;
60 /// let _: Ping = serializer.deserialize(&bytes)?;
61 /// assert_eq!(serializer.content_type().as_str(), "application/json");
62 /// # Ok::<(), reliar_core::JsonError>(())
63 /// ```
64 #[derive(Clone, Debug, Default)]
65 pub struct JsonSerializer;
66
67 impl Serializer for JsonSerializer {
68 type Error = JsonError;
69
70 fn content_type(&self) -> &ContentType {
71 &ContentType::JSON
72 }
73
74 fn serialize<T: Message>(&self, body: &T) -> Result<Bytes, Self::Error> {
75 serde_json::to_vec(body)
76 .map(Bytes::from)
77 .map_err(|source| JsonError::Serialize { source })
78 }
79
80 fn deserialize<T: Message>(&self, bytes: &[u8]) -> Result<T, Self::Error> {
81 serde_json::from_slice(bytes).map_err(|source| JsonError::Deserialize { source })
82 }
83 }
84
85 /// [`JsonSerializer`] failures. `Display` names the operation, the error class
86 /// (`serde_json::error::Category`), and the line/column — **never `serde_json::Error`'s own
87 /// message**, which for a data error embeds a fragment of the value it rejected (e.g.
88 /// `invalid type: string "sk-live-…", expected u64`). The full underlying error, message
89 /// included, is still reachable via [`std::error::Error::source`] for a caller that
90 /// deliberately wants it — that caller's own logging is then responsible for §33.
91 ///
92 /// **`Debug` is a manual impl, never derived**: `serde_json::Error`'s own `Debug` embeds its
93 /// `Display` message (the same payload fragment `Display` above must avoid), so deriving
94 /// here would leak through `{:?}` even though `Display` is safe.
95 #[non_exhaustive]
96 pub enum JsonError {
97 /// Serializing a body to JSON failed.
98 Serialize {
99 /// The underlying `serde_json` error.
100 source: serde_json::Error,
101 },
102 /// Deserializing bytes into a body failed.
103 Deserialize {
104 /// The underlying `serde_json` error.
105 source: serde_json::Error,
106 },
107 }
108
109 /// Renders a `serde_json::Error` as its classification and position only — never its
110 /// `Display`, which embeds a fragment of the offending payload for data errors.
111 fn describe(source: &serde_json::Error) -> String {
112 let category = match source.classify() {
113 serde_json::error::Category::Io => "io",
114 serde_json::error::Category::Syntax => "syntax",
115 serde_json::error::Category::Data => "data",
116 serde_json::error::Category::Eof => "eof",
117 };
118 format!(
119 "{category} error at line {}, column {}",
120 source.line(),
121 source.column()
122 )
123 }
124
125 impl fmt::Display for JsonError {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self {
128 Self::Serialize { source } => {
129 write!(f, "failed to serialize to JSON: {}", describe(source))
130 }
131 Self::Deserialize { source } => {
132 write!(f, "failed to deserialize from JSON: {}", describe(source))
133 }
134 }
135 }
136 }
137
138 impl fmt::Debug for JsonError {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 let (variant, source) = match self {
141 Self::Serialize { source } => ("Serialize", source),
142 Self::Deserialize { source } => ("Deserialize", source),
143 };
144 f.debug_struct(variant)
145 .field("classification", &describe(source))
146 .finish()
147 }
148 }
149
150 impl std::error::Error for JsonError {
151 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
152 match self {
153 Self::Serialize { source } | Self::Deserialize { source } => Some(source),
154 }
155 }
156 }
157}
158
159#[cfg(feature = "json")]
160pub use json::{JsonError, JsonSerializer};