intercom_rs/
codec.rs

1//! Message encoding and decoding with pluggable codec support.
2//!
3//! This module provides two codec implementations:
4//! - `MsgPackCodec` - MessagePack serialization (default, via rmp-serde)
5//! - `JsonCodec` - JSON serialization (via serde_json)
6//!
7//! Enable the desired codec via cargo features:
8//! - `msgpack` (default) - MessagePack codec
9//! - `json` - JSON codec
10
11use serde::{de::DeserializeOwned, Serialize};
12
13use crate::error::Result;
14
15/// Marker trait for codec types.
16///
17/// This trait is implemented by codec marker types like [`MsgPackCodec`] and [`JsonCodec`].
18/// It provides the actual encode/decode functionality.
19pub trait CodecType: Clone + Send + Sync + 'static {
20    /// Encode a value to bytes.
21    fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>>;
22
23    /// Decode a value from bytes.
24    fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T>;
25}
26
27/// MessagePack codec marker type.
28///
29/// Uses rmp-serde for efficient binary serialization.
30///
31/// # Example
32///
33/// ```no_run
34/// use intercom::{Client, MsgPackCodec};
35///
36/// # async fn example() -> intercom::Result<()> {
37/// let client = Client::<MsgPackCodec>::connect("nats://localhost:4222").await?;
38/// # Ok(())
39/// # }
40/// ```
41#[cfg(feature = "msgpack")]
42#[derive(Debug, Clone, Copy, Default)]
43pub struct MsgPackCodec;
44
45#[cfg(feature = "msgpack")]
46impl CodecType for MsgPackCodec {
47    fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
48        Ok(rmp_serde::to_vec(value)?)
49    }
50
51    fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T> {
52        Ok(rmp_serde::from_slice(data)?)
53    }
54}
55
56/// JSON codec marker type.
57///
58/// Uses serde_json for human-readable serialization.
59///
60/// # Example
61///
62/// ```no_run
63/// use intercom::{Client, JsonCodec};
64///
65/// # async fn example() -> intercom::Result<()> {
66/// let client = Client::<JsonCodec>::connect("nats://localhost:4222").await?;
67/// # Ok(())
68/// # }
69/// ```
70#[cfg(feature = "json")]
71#[derive(Debug, Clone, Copy, Default)]
72pub struct JsonCodec;
73
74#[cfg(feature = "json")]
75impl CodecType for JsonCodec {
76    fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
77        Ok(serde_json::to_vec(value)?)
78    }
79
80    fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T> {
81        Ok(serde_json::from_slice(data)?)
82    }
83}
84
85/// Trait for encoding messages.
86pub trait Encode {
87    /// Encode a message to bytes.
88    fn encode(&self) -> Result<Vec<u8>>;
89}
90
91/// Trait for decoding messages.
92pub trait Decode: Sized {
93    /// Decode a message from bytes.
94    fn decode(data: &[u8]) -> Result<Self>;
95}
96
97/// Trait for encoding and decoding messages.
98pub trait Codec: Encode + Decode {}
99
100// Default codec type alias for convenience
101#[cfg(feature = "msgpack")]
102/// The default codec type (MessagePack when msgpack feature is enabled).
103pub type DefaultCodec = MsgPackCodec;
104
105#[cfg(all(feature = "json", not(feature = "msgpack")))]
106/// The default codec type (JSON when only json feature is enabled).
107pub type DefaultCodec = JsonCodec;
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use serde::{Deserialize, Serialize};
113
114    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115    struct TestMessage {
116        id: u64,
117        content: String,
118    }
119
120    #[cfg(feature = "msgpack")]
121    #[test]
122    fn test_msgpack_encode_decode_roundtrip() {
123        let msg = TestMessage {
124            id: 42,
125            content: "hello world".to_string(),
126        };
127
128        let encoded = MsgPackCodec::encode(&msg).unwrap();
129        let decoded: TestMessage = MsgPackCodec::decode(&encoded).unwrap();
130
131        assert_eq!(msg, decoded);
132    }
133
134    #[cfg(feature = "msgpack")]
135    #[test]
136    fn test_msgpack_is_compact() {
137        let msg = TestMessage {
138            id: 1,
139            content: "test".to_string(),
140        };
141
142        let encoded = MsgPackCodec::encode(&msg).unwrap();
143        // MessagePack output should be non-empty binary data
144        assert!(!encoded.is_empty());
145        // MessagePack is typically more compact than JSON
146        let json = serde_json::to_vec(&msg).unwrap();
147        assert!(encoded.len() <= json.len());
148    }
149
150    #[cfg(feature = "json")]
151    #[test]
152    fn test_json_encode_decode_roundtrip() {
153        let msg = TestMessage {
154            id: 42,
155            content: "hello world".to_string(),
156        };
157
158        let encoded = JsonCodec::encode(&msg).unwrap();
159        let decoded: TestMessage = JsonCodec::decode(&encoded).unwrap();
160
161        assert_eq!(msg, decoded);
162    }
163
164    #[cfg(feature = "json")]
165    #[test]
166    fn test_json_is_readable() {
167        let msg = TestMessage {
168            id: 1,
169            content: "test".to_string(),
170        };
171
172        let encoded = JsonCodec::encode(&msg).unwrap();
173        // JSON output should be valid UTF-8 string
174        let json_str = std::str::from_utf8(&encoded).unwrap();
175        assert!(json_str.contains("\"id\":1"));
176        assert!(json_str.contains("\"content\":\"test\""));
177    }
178}