hyperstack_server/websocket/
frame.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
5#[serde(rename_all = "lowercase")]
6pub enum Mode {
7 State,
9 Append,
11 List,
13}
14
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "lowercase")]
18pub enum SortOrder {
19 Asc,
20 Desc,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct SortConfig {
26 pub field: Vec<String>,
28 pub order: SortOrder,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct SubscribedFrame {
35 pub op: &'static str,
37 pub view: String,
39 pub mode: Mode,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub sort: Option<SortConfig>,
44}
45
46impl SubscribedFrame {
47 pub fn new(view: String, mode: Mode, sort: Option<SortConfig>) -> Self {
48 Self {
49 op: "subscribed",
50 view,
51 mode,
52 sort,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Frame {
60 pub mode: Mode,
61 #[serde(rename = "entity")]
62 pub export: String,
63 pub op: &'static str,
64 pub key: String,
65 pub data: serde_json::Value,
66 #[serde(skip_serializing_if = "Vec::is_empty", default)]
67 pub append: Vec<String>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub seq: Option<String>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct SnapshotEntity {
76 pub key: String,
77 pub data: serde_json::Value,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct SnapshotFrame {
83 pub mode: Mode,
84 #[serde(rename = "entity")]
85 pub export: String,
86 pub op: &'static str,
87 pub data: Vec<SnapshotEntity>,
88 #[serde(default = "default_complete")]
92 pub complete: bool,
93}
94
95fn default_complete() -> bool {
96 true
97}
98
99pub fn transform_large_u64_to_strings(value: &mut serde_json::Value) {
103 const MAX_SAFE_INTEGER: u64 = 9007199254740991; match value {
106 serde_json::Value::Object(map) => {
107 for (_, v) in map.iter_mut() {
108 transform_large_u64_to_strings(v);
109 }
110 }
111 serde_json::Value::Array(arr) => {
112 for v in arr.iter_mut() {
113 transform_large_u64_to_strings(v);
114 }
115 }
116 serde_json::Value::Number(n) => {
117 if let Some(n_u64) = n.as_u64() {
118 if n_u64 > MAX_SAFE_INTEGER {
119 *value = serde_json::Value::String(n_u64.to_string());
120 }
121 } else if let Some(n_i64) = n.as_i64() {
122 const MIN_SAFE_INTEGER: i64 = -(MAX_SAFE_INTEGER as i64);
123 if n_i64 < MIN_SAFE_INTEGER {
124 *value = serde_json::Value::String(n_i64.to_string());
125 }
126 }
127 }
128 _ => {}
129 }
130}
131
132impl Frame {
133 pub fn entity(&self) -> &str {
134 &self.export
135 }
136
137 pub fn key(&self) -> &str {
138 &self.key
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_frame_entity_key_accessors() {
148 let frame = Frame {
149 mode: Mode::List,
150 export: "SettlementGame/list".to_string(),
151 op: "upsert",
152 key: "123".to_string(),
153 data: serde_json::json!({}),
154 append: vec![],
155 seq: None,
156 };
157
158 assert_eq!(frame.entity(), "SettlementGame/list");
159 assert_eq!(frame.key(), "123");
160 }
161
162 #[test]
163 fn test_frame_serialization() {
164 let frame = Frame {
165 mode: Mode::List,
166 export: "SettlementGame/list".to_string(),
167 op: "upsert",
168 key: "123".to_string(),
169 data: serde_json::json!({"gameId": "123"}),
170 append: vec![],
171 seq: None,
172 };
173
174 let json = serde_json::to_value(&frame).unwrap();
175 assert_eq!(json["op"], "upsert");
176 assert_eq!(json["mode"], "list");
177 assert_eq!(json["entity"], "SettlementGame/list");
178 assert_eq!(json["key"], "123");
179 }
180
181 #[test]
182 fn test_frame_with_seq() {
183 let frame = Frame {
184 mode: Mode::List,
185 export: "SettlementGame/list".to_string(),
186 op: "upsert",
187 key: "123".to_string(),
188 data: serde_json::json!({"gameId": "123"}),
189 append: vec![],
190 seq: Some("123456789:000000000042".to_string()),
191 };
192
193 let json = serde_json::to_value(&frame).unwrap();
194 assert_eq!(json["op"], "upsert");
195 assert_eq!(json["seq"], "123456789:000000000042");
196 }
197
198 #[test]
199 fn test_frame_seq_skipped_when_none() {
200 let frame = Frame {
201 mode: Mode::List,
202 export: "SettlementGame/list".to_string(),
203 op: "upsert",
204 key: "123".to_string(),
205 data: serde_json::json!({"gameId": "123"}),
206 append: vec![],
207 seq: None,
208 };
209
210 let json = serde_json::to_value(&frame).unwrap();
211 assert!(json.get("seq").is_none());
212 }
213
214 #[test]
215 fn test_snapshot_frame_complete_serialization() {
216 let frame = SnapshotFrame {
217 mode: Mode::List,
218 export: "tokens/list".to_string(),
219 op: "snapshot",
220 data: vec![SnapshotEntity {
221 key: "abc".to_string(),
222 data: serde_json::json!({"id": "abc"}),
223 }],
224 complete: false,
225 };
226
227 let json = serde_json::to_value(&frame).unwrap();
228 assert_eq!(json["complete"], false);
229 assert_eq!(json["op"], "snapshot");
230 }
231
232 #[test]
233 fn test_snapshot_frame_complete_defaults_to_true_on_deserialize() {
234 #[derive(Debug, Deserialize)]
235 struct TestSnapshotFrame {
236 #[allow(dead_code)]
237 mode: Mode,
238 #[allow(dead_code)]
239 #[serde(rename = "entity")]
240 export: String,
241 #[allow(dead_code)]
242 op: String,
243 #[allow(dead_code)]
244 data: Vec<SnapshotEntity>,
245 #[serde(default = "super::default_complete")]
246 complete: bool,
247 }
248
249 let json_without_complete = serde_json::json!({
250 "mode": "list",
251 "entity": "tokens/list",
252 "op": "snapshot",
253 "data": []
254 });
255
256 let frame: TestSnapshotFrame = serde_json::from_value(json_without_complete).unwrap();
257 assert!(frame.complete);
258 }
259
260 #[test]
261 fn test_snapshot_frame_batching_fields() {
262 let first_batch = SnapshotFrame {
263 mode: Mode::List,
264 export: "tokens/list".to_string(),
265 op: "snapshot",
266 data: vec![],
267 complete: false,
268 };
269
270 let final_batch = SnapshotFrame {
271 mode: Mode::List,
272 export: "tokens/list".to_string(),
273 op: "snapshot",
274 data: vec![],
275 complete: true,
276 };
277
278 assert!(!first_batch.complete);
279 assert!(final_batch.complete);
280 }
281}