runecast_protocol/protocol/
envelope.rs1use serde::{Deserialize, Serialize};
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Envelope<T> {
36 pub seq: u64,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
43 pub ack: Option<u64>,
44
45 #[serde(rename = "ts")]
48 pub timestamp: u64,
49
50 pub payload: T,
52}
53
54impl<T> Envelope<T> {
55 pub fn new(seq: u64, payload: T) -> Self {
57 Self {
58 seq,
59 ack: None,
60 timestamp: Self::now_millis(),
61 payload,
62 }
63 }
64
65 pub fn with_ack(seq: u64, ack: u64, payload: T) -> Self {
67 Self {
68 seq,
69 ack: Some(ack),
70 timestamp: Self::now_millis(),
71 payload,
72 }
73 }
74
75 fn now_millis() -> u64 {
77 use std::time::{SystemTime, UNIX_EPOCH};
78 SystemTime::now()
79 .duration_since(UNIX_EPOCH)
80 .map_or(0, |d| u64::try_from(d.as_millis()).unwrap_or(0))
81 }
82}
83
84impl<T> Envelope<T>
85where
86 T: Clone,
87{
88 pub fn map<U, F>(self, f: F) -> Envelope<U>
90 where
91 F: FnOnce(T) -> U,
92 {
93 Envelope {
94 seq: self.seq,
95 ack: self.ack,
96 timestamp: self.timestamp,
97 payload: f(self.payload),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
107#[serde(untagged)]
108pub enum MaybeEnveloped<T> {
109 Enveloped(Envelope<T>),
111 Raw(T),
113}
114
115impl<T> MaybeEnveloped<T> {
116 pub fn into_payload(self) -> T {
118 match self {
119 MaybeEnveloped::Enveloped(env) => env.payload,
120 MaybeEnveloped::Raw(payload) => payload,
121 }
122 }
123
124 pub fn seq(&self) -> Option<u64> {
126 match self {
127 MaybeEnveloped::Enveloped(env) => Some(env.seq),
128 MaybeEnveloped::Raw(_) => None,
129 }
130 }
131
132 pub fn ack(&self) -> Option<u64> {
134 match self {
135 MaybeEnveloped::Enveloped(env) => env.ack,
136 MaybeEnveloped::Raw(_) => None,
137 }
138 }
139
140 pub fn is_enveloped(&self) -> bool {
142 matches!(self, MaybeEnveloped::Enveloped(_))
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use serde_json::json;
150
151 #[test]
152 fn test_envelope_serialization() {
153 let envelope = Envelope::new(42, json!({"type": "heartbeat"}));
154 let json = serde_json::to_string(&envelope).unwrap();
155
156 assert!(json.contains("\"seq\":42"));
157 assert!(json.contains("\"ts\":"));
158 assert!(json.contains("\"payload\""));
159 }
160
161 #[test]
162 fn test_envelope_with_ack() {
163 let envelope = Envelope::with_ack(42, 41, "test");
164 assert_eq!(envelope.seq, 42);
165 assert_eq!(envelope.ack, Some(41));
166 }
167
168 #[test]
169 fn test_maybe_enveloped_raw() {
170 let raw: MaybeEnveloped<String> = MaybeEnveloped::Raw("hello".to_string());
171 assert!(!raw.is_enveloped());
172 assert_eq!(raw.seq(), None);
173 assert_eq!(raw.into_payload(), "hello");
174 }
175
176 #[test]
177 fn test_maybe_enveloped_envelope() {
178 let env = MaybeEnveloped::Enveloped(Envelope::new(1, "hello".to_string()));
179 assert!(env.is_enveloped());
180 assert_eq!(env.seq(), Some(1));
181 }
182
183 #[test]
184 fn test_deserialize_raw_message() {
185 let json = r#"{"type": "heartbeat"}"#;
186 let result: MaybeEnveloped<serde_json::Value> = serde_json::from_str(json).unwrap();
187 assert!(!result.is_enveloped());
188 }
189
190 #[test]
191 fn test_deserialize_enveloped_message() {
192 let json = r#"{"seq": 1, "ts": 12345, "payload": {"type": "heartbeat"}}"#;
193 let result: MaybeEnveloped<serde_json::Value> = serde_json::from_str(json).unwrap();
194 assert!(result.is_enveloped());
195 assert_eq!(result.seq(), Some(1));
196 }
197}