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}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct SnapshotEntity {
73 pub key: String,
74 pub data: serde_json::Value,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct SnapshotFrame {
80 pub mode: Mode,
81 #[serde(rename = "entity")]
82 pub export: String,
83 pub op: &'static str,
84 pub data: Vec<SnapshotEntity>,
85 #[serde(default = "default_complete")]
89 pub complete: bool,
90}
91
92fn default_complete() -> bool {
93 true
94}
95
96pub fn transform_large_u64_to_strings(value: &mut serde_json::Value) {
100 const MAX_SAFE_INTEGER: u64 = 9007199254740991; match value {
103 serde_json::Value::Object(map) => {
104 for (_, v) in map.iter_mut() {
105 transform_large_u64_to_strings(v);
106 }
107 }
108 serde_json::Value::Array(arr) => {
109 for v in arr.iter_mut() {
110 transform_large_u64_to_strings(v);
111 }
112 }
113 serde_json::Value::Number(n) => {
114 if let Some(n_u64) = n.as_u64() {
115 if n_u64 > MAX_SAFE_INTEGER {
116 *value = serde_json::Value::String(n_u64.to_string());
117 }
118 } else if let Some(n_i64) = n.as_i64() {
119 const MIN_SAFE_INTEGER: i64 = -(MAX_SAFE_INTEGER as i64);
120 if n_i64 < MIN_SAFE_INTEGER {
121 *value = serde_json::Value::String(n_i64.to_string());
122 }
123 }
124 }
125 _ => {}
126 }
127}
128
129impl Frame {
130 pub fn entity(&self) -> &str {
131 &self.export
132 }
133
134 pub fn key(&self) -> &str {
135 &self.key
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_frame_entity_key_accessors() {
145 let frame = Frame {
146 mode: Mode::List,
147 export: "SettlementGame/list".to_string(),
148 op: "upsert",
149 key: "123".to_string(),
150 data: serde_json::json!({}),
151 append: vec![],
152 };
153
154 assert_eq!(frame.entity(), "SettlementGame/list");
155 assert_eq!(frame.key(), "123");
156 }
157
158 #[test]
159 fn test_frame_serialization() {
160 let frame = Frame {
161 mode: Mode::List,
162 export: "SettlementGame/list".to_string(),
163 op: "upsert",
164 key: "123".to_string(),
165 data: serde_json::json!({"gameId": "123"}),
166 append: vec![],
167 };
168
169 let json = serde_json::to_value(&frame).unwrap();
170 assert_eq!(json["op"], "upsert");
171 assert_eq!(json["mode"], "list");
172 assert_eq!(json["entity"], "SettlementGame/list");
173 assert_eq!(json["key"], "123");
174 }
175
176 #[test]
177 fn test_snapshot_frame_complete_serialization() {
178 let frame = SnapshotFrame {
179 mode: Mode::List,
180 export: "tokens/list".to_string(),
181 op: "snapshot",
182 data: vec![SnapshotEntity {
183 key: "abc".to_string(),
184 data: serde_json::json!({"id": "abc"}),
185 }],
186 complete: false,
187 };
188
189 let json = serde_json::to_value(&frame).unwrap();
190 assert_eq!(json["complete"], false);
191 assert_eq!(json["op"], "snapshot");
192 }
193
194 #[test]
195 fn test_snapshot_frame_complete_defaults_to_true_on_deserialize() {
196 #[derive(Debug, Deserialize)]
197 struct TestSnapshotFrame {
198 #[allow(dead_code)]
199 mode: Mode,
200 #[allow(dead_code)]
201 #[serde(rename = "entity")]
202 export: String,
203 #[allow(dead_code)]
204 op: String,
205 #[allow(dead_code)]
206 data: Vec<SnapshotEntity>,
207 #[serde(default = "super::default_complete")]
208 complete: bool,
209 }
210
211 let json_without_complete = serde_json::json!({
212 "mode": "list",
213 "entity": "tokens/list",
214 "op": "snapshot",
215 "data": []
216 });
217
218 let frame: TestSnapshotFrame = serde_json::from_value(json_without_complete).unwrap();
219 assert!(frame.complete);
220 }
221
222 #[test]
223 fn test_snapshot_frame_batching_fields() {
224 let first_batch = SnapshotFrame {
225 mode: Mode::List,
226 export: "tokens/list".to_string(),
227 op: "snapshot",
228 data: vec![],
229 complete: false,
230 };
231
232 let final_batch = SnapshotFrame {
233 mode: Mode::List,
234 export: "tokens/list".to_string(),
235 op: "snapshot",
236 data: vec![],
237 complete: true,
238 };
239
240 assert!(!first_batch.complete);
241 assert!(final_batch.complete);
242 }
243}