Skip to main content

moonpool_core/
codec.rs

1//! Pluggable message serialization for moonpool.
2//!
3//! The [`MessageCodec`] trait allows users to bring their own serialization format
4//! (JSON, bincode, protobuf, messagepack, etc.) while moonpool provides a default
5//! [`JsonCodec`] for debugging and getting started quickly.
6//!
7//! # Example
8//!
9//! ```rust
10//! use moonpool_core::{MessageCodec, JsonCodec, CodecError};
11//! use serde::{Serialize, Deserialize};
12//!
13//! #[derive(Serialize, Deserialize, Debug, PartialEq)]
14//! struct MyMessage {
15//!     id: u32,
16//!     content: String,
17//! }
18//!
19//! let codec = JsonCodec;
20//! let msg = MyMessage { id: 42, content: "hello".to_string() };
21//!
22//! // Encode
23//! let bytes = codec.encode(&msg).unwrap();
24//!
25//! // Decode
26//! let decoded: MyMessage = codec.decode(&bytes).unwrap();
27//! assert_eq!(msg, decoded);
28//! ```
29//!
30//! # Implementing Custom Codecs
31//!
32//! ```rust
33//! use moonpool_core::{MessageCodec, CodecError};
34//! use serde::{Serialize, de::DeserializeOwned};
35//!
36//! #[derive(Clone, Default)]
37//! struct BincodeCodec;
38//!
39//! // impl MessageCodec for BincodeCodec {
40//! //     fn encode<T: Serialize>(&self, msg: &T) -> Result<Vec<u8>, CodecError> {
41//! //         bincode::serialize(msg).map_err(|e| CodecError::Encode(e.into()))
42//! //     }
43//! //
44//! //     fn decode<T: DeserializeOwned>(&self, buf: &[u8]) -> Result<T, CodecError> {
45//! //         bincode::deserialize(buf).map_err(|e| CodecError::Decode(e.into()))
46//! //     }
47//! // }
48//! ```
49
50use std::fmt;
51
52use serde::Serialize;
53use serde::de::DeserializeOwned;
54
55/// Error type for codec operations.
56#[derive(Debug)]
57pub enum CodecError {
58    /// Failed to encode a message to bytes.
59    Encode(Box<dyn std::error::Error + Send + Sync>),
60    /// Failed to decode bytes to a message.
61    Decode(Box<dyn std::error::Error + Send + Sync>),
62}
63
64impl fmt::Display for CodecError {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            CodecError::Encode(e) => write!(f, "encode error: {e}"),
68            CodecError::Decode(e) => write!(f, "decode error: {e}"),
69        }
70    }
71}
72
73impl std::error::Error for CodecError {
74    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
75        match self {
76            CodecError::Encode(e) | CodecError::Decode(e) => Some(e.as_ref()),
77        }
78    }
79}
80
81/// Pluggable message serialization format.
82///
83/// Implement this trait to use custom serialization formats (bincode, protobuf, etc.).
84/// The trait requires `Clone + 'static` so codec instances can be stored in queues
85/// and transports.
86///
87/// # Serde Dependency
88///
89/// This trait uses serde's `Serialize` and `DeserializeOwned` bounds, which means
90/// your message types must derive or implement serde traits. If you need completely
91/// custom serialization without serde, you can implement your own queue type.
92pub trait MessageCodec: Clone + Send + Sync + 'static {
93    /// Encode a serializable message to bytes.
94    ///
95    /// # Errors
96    ///
97    /// Returns `CodecError::Encode` if serialization fails.
98    fn encode<T: Serialize>(&self, msg: &T) -> Result<Vec<u8>, CodecError>;
99
100    /// Decode bytes to a deserializable message.
101    ///
102    /// # Errors
103    ///
104    /// Returns `CodecError::Decode` if deserialization fails.
105    fn decode<T: DeserializeOwned>(&self, buf: &[u8]) -> Result<T, CodecError>;
106}
107
108/// JSON codec using `serde_json`.
109///
110/// This is the default codec provided by moonpool. It's great for debugging
111/// (human-readable output) but not the most efficient for production use.
112///
113/// # Example
114///
115/// ```rust
116/// use moonpool_core::{MessageCodec, JsonCodec};
117/// use serde::{Serialize, Deserialize};
118///
119/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
120/// struct Ping { seq: u32 }
121///
122/// let codec = JsonCodec;
123/// let ping = Ping { seq: 1 };
124///
125/// let bytes = codec.encode(&ping).unwrap();
126/// assert_eq!(&bytes, br#"{"seq":1}"#);
127///
128/// let decoded: Ping = codec.decode(&bytes).unwrap();
129/// assert_eq!(decoded, ping);
130/// ```
131#[derive(Clone, Default, Debug, Copy)]
132pub struct JsonCodec;
133
134impl MessageCodec for JsonCodec {
135    fn encode<T: Serialize>(&self, msg: &T) -> Result<Vec<u8>, CodecError> {
136        serde_json::to_vec(msg).map_err(|e| CodecError::Encode(Box::new(e)))
137    }
138
139    fn decode<T: DeserializeOwned>(&self, buf: &[u8]) -> Result<T, CodecError> {
140        serde_json::from_slice(buf).map_err(|e| CodecError::Decode(Box::new(e)))
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use serde::{Deserialize, Serialize};
148
149    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
150    struct TestMessage {
151        id: u32,
152        content: String,
153    }
154
155    #[derive(Serialize, Deserialize, Debug, PartialEq)]
156    struct NestedMessage {
157        outer: String,
158        inner: TestMessage,
159    }
160
161    #[test]
162    fn test_json_codec_roundtrip() {
163        let codec = JsonCodec;
164        let msg = TestMessage {
165            id: 42,
166            content: "hello world".to_string(),
167        };
168
169        let bytes = codec.encode(&msg).expect("encode should succeed");
170        let decoded: TestMessage = codec.decode(&bytes).expect("decode should succeed");
171
172        assert_eq!(msg, decoded);
173    }
174
175    #[test]
176    fn test_json_codec_nested() {
177        let codec = JsonCodec;
178        let msg = NestedMessage {
179            outer: "outer".to_string(),
180            inner: TestMessage {
181                id: 1,
182                content: "inner".to_string(),
183            },
184        };
185
186        let bytes = codec.encode(&msg).expect("encode should succeed");
187        let decoded: NestedMessage = codec.decode(&bytes).expect("decode should succeed");
188
189        assert_eq!(msg, decoded);
190    }
191
192    #[test]
193    fn test_json_codec_primitives() {
194        let codec = JsonCodec;
195
196        // String
197        let s = "test string".to_string();
198        let bytes = codec.encode(&s).expect("encode should succeed");
199        let decoded: String = codec.decode(&bytes).expect("decode should succeed");
200        assert_eq!(s, decoded);
201
202        // Integer
203        let n = 12345u64;
204        let bytes = codec.encode(&n).expect("encode should succeed");
205        let decoded: u64 = codec.decode(&bytes).expect("decode should succeed");
206        assert_eq!(n, decoded);
207
208        // Vec
209        let v = vec![1, 2, 3, 4, 5];
210        let bytes = codec.encode(&v).expect("encode should succeed");
211        let decoded: Vec<i32> = codec.decode(&bytes).expect("decode should succeed");
212        assert_eq!(v, decoded);
213    }
214
215    #[test]
216    fn test_json_codec_decode_error() {
217        let codec = JsonCodec;
218        let invalid_json = b"not valid json {";
219
220        let result: Result<TestMessage, CodecError> = codec.decode(invalid_json);
221        assert!(result.is_err());
222
223        let err = result.expect_err("decode should have failed");
224        assert!(matches!(err, CodecError::Decode(_)));
225        assert!(err.to_string().contains("decode error"));
226    }
227
228    #[test]
229    fn test_json_codec_type_mismatch() {
230        let codec = JsonCodec;
231        let msg = TestMessage {
232            id: 42,
233            content: "hello".to_string(),
234        };
235
236        let bytes = codec.encode(&msg).expect("encode should succeed");
237
238        // Try to decode as wrong type
239        let result: Result<String, CodecError> = codec.decode(&bytes);
240        assert!(result.is_err());
241    }
242
243    #[test]
244    fn test_codec_error_display() {
245        let encode_err = CodecError::Encode(Box::new(std::io::Error::other("test encode error")));
246        assert!(encode_err.to_string().contains("encode error"));
247
248        let decode_err = CodecError::Decode(Box::new(std::io::Error::other("test decode error")));
249        assert!(decode_err.to_string().contains("decode error"));
250    }
251
252    #[test]
253    fn test_json_codec_is_clone() {
254        let codec1 = JsonCodec;
255        let codec2 = codec1;
256
257        let msg = TestMessage {
258            id: 1,
259            content: "test".to_string(),
260        };
261
262        // Both codecs should work identically
263        let bytes1 = codec1.encode(&msg).expect("encode should succeed");
264        let bytes2 = codec2.encode(&msg).expect("encode should succeed");
265        assert_eq!(bytes1, bytes2);
266    }
267
268    #[test]
269    fn test_json_codec_default() {
270        let codec = JsonCodec;
271        let msg = TestMessage {
272            id: 99,
273            content: "default".to_string(),
274        };
275
276        let bytes = codec.encode(&msg).expect("encode should succeed");
277        let decoded: TestMessage = codec.decode(&bytes).expect("decode should succeed");
278        assert_eq!(msg, decoded);
279    }
280
281    #[test]
282    fn test_json_codec_empty_struct() {
283        #[derive(Serialize, Deserialize, Debug, PartialEq)]
284        struct Empty {}
285
286        let codec = JsonCodec;
287        let msg = Empty {};
288
289        let bytes = codec.encode(&msg).expect("encode should succeed");
290        assert_eq!(&bytes, b"{}");
291
292        let decoded: Empty = codec.decode(&bytes).expect("decode should succeed");
293        assert_eq!(msg, decoded);
294    }
295
296    #[test]
297    fn test_json_codec_option() {
298        let codec = JsonCodec;
299
300        let some_val: Option<i32> = Some(42);
301        let bytes = codec.encode(&some_val).expect("encode should succeed");
302        let decoded: Option<i32> = codec.decode(&bytes).expect("decode should succeed");
303        assert_eq!(some_val, decoded);
304
305        let none_val: Option<i32> = None;
306        let bytes = codec.encode(&none_val).expect("encode should succeed");
307        let decoded: Option<i32> = codec.decode(&bytes).expect("decode should succeed");
308        assert_eq!(none_val, decoded);
309    }
310}