Skip to main content

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