Skip to main content

dynamo_runtime/transports/event_plane/
codec.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Event plane codec for serializing/deserializing envelopes and payloads.
5
6use anyhow::Result;
7use bytes::Bytes;
8use serde::{Serialize, de::DeserializeOwned};
9
10use super::EventEnvelope;
11
12/// Codec for serializing and deserializing event envelopes and payloads.
13///
14/// Currently only supports MessagePack for all transports.
15#[derive(Debug, Clone, Copy)]
16pub enum Codec {
17    Msgpack(MsgpackCodec),
18}
19
20impl Default for Codec {
21    fn default() -> Self {
22        Codec::Msgpack(MsgpackCodec)
23    }
24}
25
26impl Codec {
27    /// Encode an EventEnvelope to wire bytes
28    pub fn encode_envelope(&self, envelope: &EventEnvelope) -> Result<Bytes> {
29        match self {
30            Codec::Msgpack(c) => c.encode_envelope(envelope),
31        }
32    }
33
34    /// Encode an event envelope while borrowing its immutable topic and payload.
35    pub fn encode_envelope_parts(
36        &self,
37        publisher_id: u64,
38        sequence: u64,
39        published_at: u64,
40        topic: &str,
41        payload: &[u8],
42    ) -> Result<Bytes> {
43        match self {
44            Codec::Msgpack(c) => {
45                c.encode_envelope_parts(publisher_id, sequence, published_at, topic, payload)
46            }
47        }
48    }
49
50    /// Decode wire bytes to an EventEnvelope
51    pub fn decode_envelope(&self, bytes: &Bytes) -> Result<EventEnvelope> {
52        match self {
53            Codec::Msgpack(c) => c.decode_envelope(bytes),
54        }
55    }
56
57    /// Encode a typed payload to bytes (for embedding in envelope)
58    pub fn encode_payload<T: Serialize>(&self, payload: &T) -> Result<Bytes> {
59        match self {
60            Codec::Msgpack(c) => c.encode_payload(payload),
61        }
62    }
63
64    /// Decode payload bytes to a typed value
65    pub fn decode_payload<T: DeserializeOwned>(&self, bytes: &Bytes) -> Result<T> {
66        match self {
67            Codec::Msgpack(c) => c.decode_payload(bytes),
68        }
69    }
70
71    /// Codec name for debugging
72    pub fn name(&self) -> &'static str {
73        match self {
74            Codec::Msgpack(c) => c.name(),
75        }
76    }
77}
78
79#[derive(Debug, Clone, Copy, Default)]
80pub struct MsgpackCodec;
81
82#[derive(Serialize)]
83struct BorrowedEventEnvelope<'a> {
84    publisher_id: u64,
85    sequence: u64,
86    published_at: u64,
87    topic: &'a str,
88    #[serde(serialize_with = "serialize_bytes")]
89    payload: &'a [u8],
90}
91
92fn serialize_bytes<S>(bytes: &&[u8], serializer: S) -> std::result::Result<S::Ok, S::Error>
93where
94    S: serde::Serializer,
95{
96    serializer.serialize_bytes(bytes)
97}
98
99impl MsgpackCodec {
100    pub fn encode_envelope(&self, envelope: &EventEnvelope) -> Result<Bytes> {
101        Ok(Bytes::from(rmp_serde::to_vec_named(envelope)?))
102    }
103
104    pub fn encode_envelope_parts(
105        &self,
106        publisher_id: u64,
107        sequence: u64,
108        published_at: u64,
109        topic: &str,
110        payload: &[u8],
111    ) -> Result<Bytes> {
112        let envelope = BorrowedEventEnvelope {
113            publisher_id,
114            sequence,
115            published_at,
116            topic,
117            payload,
118        };
119        Ok(Bytes::from(rmp_serde::to_vec_named(&envelope)?))
120    }
121
122    pub fn decode_envelope(&self, bytes: &Bytes) -> Result<EventEnvelope> {
123        Ok(rmp_serde::from_slice(bytes)?)
124    }
125
126    pub fn encode_payload<T: Serialize>(&self, payload: &T) -> Result<Bytes> {
127        Ok(Bytes::from(rmp_serde::to_vec_named(payload)?))
128    }
129
130    pub fn decode_payload<T: DeserializeOwned>(&self, bytes: &Bytes) -> Result<T> {
131        Ok(rmp_serde::from_slice(bytes)?)
132    }
133
134    pub fn name(&self) -> &'static str {
135        "msgpack"
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
144    struct TestEvent {
145        worker_id: u64,
146        message: String,
147    }
148
149    #[test]
150    fn test_msgpack_codec_envelope_roundtrip() {
151        let codec = MsgpackCodec;
152
153        let envelope = EventEnvelope {
154            publisher_id: 12345,
155            sequence: 42,
156            published_at: 1700000000000,
157            topic: "test-topic".to_string(),
158            payload: Bytes::from("test payload"),
159        };
160
161        let encoded = codec.encode_envelope(&envelope).unwrap();
162        let decoded = codec.decode_envelope(&encoded).unwrap();
163
164        assert_eq!(decoded.publisher_id, envelope.publisher_id);
165        assert_eq!(decoded.sequence, envelope.sequence);
166        assert_eq!(decoded.published_at, envelope.published_at);
167        assert_eq!(decoded.topic, envelope.topic);
168        assert_eq!(decoded.payload, envelope.payload);
169    }
170
171    #[test]
172    fn test_msgpack_codec_borrowed_envelope_roundtrip() {
173        let codec = MsgpackCodec;
174        let payload = b"borrowed payload";
175
176        let borrowed = codec
177            .encode_envelope_parts(12345, 42, 1700000000000, "test-topic", payload)
178            .unwrap();
179        let owned = codec
180            .encode_envelope(&EventEnvelope {
181                publisher_id: 12345,
182                sequence: 42,
183                published_at: 1700000000000,
184                topic: "test-topic".to_string(),
185                payload: Bytes::from_static(payload),
186            })
187            .unwrap();
188        assert_eq!(borrowed, owned);
189
190        let decoded: EventEnvelope = rmp_serde::from_slice(&borrowed).unwrap();
191
192        assert_eq!(decoded.publisher_id, 12345);
193        assert_eq!(decoded.sequence, 42);
194        assert_eq!(decoded.published_at, 1700000000000);
195        assert_eq!(decoded.topic, "test-topic");
196        assert_eq!(decoded.payload.as_ref(), payload);
197    }
198
199    #[test]
200    fn test_msgpack_codec_payload_roundtrip() {
201        let codec = MsgpackCodec;
202
203        let event = TestEvent {
204            worker_id: 123,
205            message: "hello world".to_string(),
206        };
207
208        let encoded = codec.encode_payload(&event).unwrap();
209        let decoded: TestEvent = codec.decode_payload(&encoded).unwrap();
210
211        assert_eq!(decoded, event);
212    }
213}