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