Skip to main content

truffle_core/envelope/
codec.rs

1//! Envelope codecs — pluggable serialization for [`Envelope`](super::Envelope).
2//!
3//! The default codec is [`JsonCodec`], which produces human-readable JSON.
4//! Future codecs (MessagePack, Protobuf) can be added by implementing the
5//! [`EnvelopeCodec`] trait.
6
7use super::{Envelope, EnvelopeError};
8
9// ---------------------------------------------------------------------------
10// EnvelopeCodec trait
11// ---------------------------------------------------------------------------
12
13/// A codec that serializes and deserializes [`Envelope`]s.
14///
15/// Implementations must be `Send + Sync` so they can be shared across
16/// async tasks behind an `Arc`.
17///
18/// # Default implementation
19///
20/// [`JsonCodec`] — produces compact JSON. Suitable for debugging and
21/// low-to-medium throughput applications.
22pub trait EnvelopeCodec: Send + Sync {
23    /// Encode an envelope into a byte buffer.
24    fn encode(&self, envelope: &Envelope) -> Result<Vec<u8>, EnvelopeError>;
25
26    /// Decode an envelope from a byte buffer.
27    fn decode(&self, data: &[u8]) -> Result<Envelope, EnvelopeError>;
28}
29
30// ---------------------------------------------------------------------------
31// JsonCodec
32// ---------------------------------------------------------------------------
33
34/// JSON envelope codec (the default).
35///
36/// Produces compact (non-pretty-printed) JSON. Unknown fields in the input
37/// are silently ignored during deserialization for forward compatibility.
38#[derive(Debug, Clone, Default)]
39pub struct JsonCodec;
40
41impl EnvelopeCodec for JsonCodec {
42    fn encode(&self, envelope: &Envelope) -> Result<Vec<u8>, EnvelopeError> {
43        serde_json::to_vec(envelope).map_err(EnvelopeError::Serialization)
44    }
45
46    fn decode(&self, data: &[u8]) -> Result<Envelope, EnvelopeError> {
47        serde_json::from_slice(data).map_err(EnvelopeError::Deserialization)
48    }
49}
50
51// ---------------------------------------------------------------------------
52// Tests
53// ---------------------------------------------------------------------------
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use serde_json::json;
59
60    #[test]
61    fn test_json_codec_encode_decode_roundtrip() {
62        let codec = JsonCodec;
63
64        let envelope = Envelope::new("chat", "message", json!({"text": "hello world"}));
65        let encoded = codec.encode(&envelope).expect("encode");
66        let decoded = codec.decode(&encoded).expect("decode");
67
68        assert_eq!(decoded.namespace, "chat");
69        assert_eq!(decoded.msg_type, "message");
70        assert_eq!(decoded.payload["text"], "hello world");
71    }
72
73    #[test]
74    fn test_json_codec_preserves_all_fields() {
75        let codec = JsonCodec;
76
77        let envelope = Envelope {
78            namespace: "ft".into(),
79            msg_type: "offer".into(),
80            payload: json!({"file": "data.bin"}),
81            timestamp: Some(1711234567890),
82            from: Some("peer-abc".into()),
83        };
84
85        let encoded = codec.encode(&envelope).expect("encode");
86        let decoded = codec.decode(&encoded).expect("decode");
87
88        assert_eq!(decoded.namespace, "ft");
89        assert_eq!(decoded.msg_type, "offer");
90        assert_eq!(decoded.payload["file"], "data.bin");
91        assert_eq!(decoded.timestamp, Some(1711234567890));
92        assert_eq!(decoded.from.as_deref(), Some("peer-abc"));
93    }
94
95    #[test]
96    fn test_json_codec_unknown_fields_forward_compat() {
97        let codec = JsonCodec;
98
99        // JSON with extra fields a future version might add.
100        let json_bytes = br#"{
101            "namespace": "v2",
102            "msg_type": "new_feature",
103            "payload": {},
104            "priority": "high",
105            "trace_id": "abc-123"
106        }"#;
107
108        let decoded = codec.decode(json_bytes).expect("should decode with unknown fields");
109        assert_eq!(decoded.namespace, "v2");
110        assert_eq!(decoded.msg_type, "new_feature");
111    }
112
113    #[test]
114    fn test_json_codec_decode_error() {
115        let codec = JsonCodec;
116        let result = codec.decode(b"{{invalid");
117        assert!(result.is_err());
118    }
119
120    #[test]
121    fn test_json_codec_empty_payload() {
122        let codec = JsonCodec;
123
124        let envelope = Envelope::new("ping", "request", json!(null));
125        let encoded = codec.encode(&envelope).expect("encode");
126        let decoded = codec.decode(&encoded).expect("decode");
127
128        assert!(decoded.payload.is_null());
129    }
130
131    #[test]
132    fn test_json_codec_is_send_sync() {
133        fn assert_send_sync<T: Send + Sync>() {}
134        assert_send_sync::<JsonCodec>();
135    }
136
137    #[test]
138    fn test_json_codec_trait_object() {
139        // Verify EnvelopeCodec can be used as a trait object behind Arc.
140        let codec: std::sync::Arc<dyn EnvelopeCodec> = std::sync::Arc::new(JsonCodec);
141        let envelope = Envelope::new("test", "msg", json!("data"));
142        let encoded = codec.encode(&envelope).expect("encode");
143        let decoded = codec.decode(&encoded).expect("decode");
144        assert_eq!(decoded.namespace, "test");
145    }
146}