1use 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, Default, PartialEq, Eq)]
33pub struct WireFormat {
34 pub wide_int_paths: Vec<Vec<String>>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SubscribedFrame {
40 pub op: &'static str,
42 pub view: String,
44 pub mode: Mode,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub sort: Option<SortConfig>,
49}
50
51impl SubscribedFrame {
52 pub fn new(view: String, mode: Mode, sort: Option<SortConfig>) -> Self {
53 Self {
54 op: "subscribed",
55 view,
56 mode,
57 sort,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Frame {
65 pub mode: Mode,
66 #[serde(rename = "entity")]
67 pub export: String,
68 pub op: &'static str,
69 pub key: String,
70 pub data: serde_json::Value,
71 #[serde(skip_serializing_if = "Vec::is_empty", default)]
72 pub append: Vec<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub seq: Option<String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct SnapshotEntity {
81 pub key: String,
82 pub data: serde_json::Value,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SnapshotFrame {
88 pub mode: Mode,
89 #[serde(rename = "entity")]
90 pub export: String,
91 pub op: &'static str,
92 pub data: Vec<SnapshotEntity>,
93 #[serde(default = "default_complete")]
97 pub complete: bool,
98}
99
100fn default_complete() -> bool {
101 true
102}
103
104pub fn apply_wire_format(value: &mut serde_json::Value, wire_format: &WireFormat) {
105 for path in &wire_format.wide_int_paths {
106 stringify_value_at_path(value, path);
107 }
108}
109
110fn stringify_value_at_path(value: &mut serde_json::Value, path: &[String]) {
111 if path.is_empty() {
112 stringify_wide_int_value(value);
113 return;
114 }
115
116 match value {
117 serde_json::Value::Object(map) => {
118 if let Some(child) = map.get_mut(&path[0]) {
119 stringify_value_at_path(child, &path[1..]);
120 }
121 }
122 serde_json::Value::Array(values) => {
123 for child in values {
124 stringify_value_at_path(child, path);
125 }
126 }
127 _ => {}
128 }
129}
130
131fn stringify_wide_int_value(value: &mut serde_json::Value) {
132 match value {
133 serde_json::Value::Number(number) => {
134 if let Some(unsigned) = number.as_u64() {
135 *value = serde_json::Value::String(unsigned.to_string());
136 } else if let Some(signed) = number.as_i64() {
137 *value = serde_json::Value::String(signed.to_string());
138 }
139 }
140 serde_json::Value::Array(values) => {
141 for child in values {
142 stringify_wide_int_value(child);
143 }
144 }
145 _ => {}
146 }
147}
148
149impl Frame {
150 pub fn entity(&self) -> &str {
151 &self.export
152 }
153
154 pub fn key(&self) -> &str {
155 &self.key
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_frame_entity_key_accessors() {
165 let frame = Frame {
166 mode: Mode::List,
167 export: "SettlementGame/list".to_string(),
168 op: "upsert",
169 key: "123".to_string(),
170 data: serde_json::json!({}),
171 append: vec![],
172 seq: None,
173 };
174
175 assert_eq!(frame.entity(), "SettlementGame/list");
176 assert_eq!(frame.key(), "123");
177 }
178
179 #[test]
180 fn test_frame_serialization() {
181 let frame = Frame {
182 mode: Mode::List,
183 export: "SettlementGame/list".to_string(),
184 op: "upsert",
185 key: "123".to_string(),
186 data: serde_json::json!({"gameId": "123"}),
187 append: vec![],
188 seq: None,
189 };
190
191 let json = serde_json::to_value(&frame).unwrap();
192 assert_eq!(json["op"], "upsert");
193 assert_eq!(json["mode"], "list");
194 assert_eq!(json["entity"], "SettlementGame/list");
195 assert_eq!(json["key"], "123");
196 }
197
198 #[test]
199 fn test_frame_with_seq() {
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: Some("123456789:000000000042".to_string()),
208 };
209
210 let json = serde_json::to_value(&frame).unwrap();
211 assert_eq!(json["op"], "upsert");
212 assert_eq!(json["seq"], "123456789:000000000042");
213 }
214
215 #[test]
216 fn test_frame_seq_skipped_when_none() {
217 let frame = Frame {
218 mode: Mode::List,
219 export: "SettlementGame/list".to_string(),
220 op: "upsert",
221 key: "123".to_string(),
222 data: serde_json::json!({"gameId": "123"}),
223 append: vec![],
224 seq: None,
225 };
226
227 let json = serde_json::to_value(&frame).unwrap();
228 assert!(json.get("seq").is_none());
229 }
230
231 #[test]
232 fn test_snapshot_frame_complete_serialization() {
233 let frame = SnapshotFrame {
234 mode: Mode::List,
235 export: "tokens/list".to_string(),
236 op: "snapshot",
237 data: vec![SnapshotEntity {
238 key: "abc".to_string(),
239 data: serde_json::json!({"id": "abc"}),
240 }],
241 complete: false,
242 };
243
244 let json = serde_json::to_value(&frame).unwrap();
245 assert_eq!(json["complete"], false);
246 assert_eq!(json["op"], "snapshot");
247 }
248
249 #[test]
250 fn test_snapshot_frame_complete_defaults_to_true_on_deserialize() {
251 #[derive(Debug, Deserialize)]
252 struct TestSnapshotFrame {
253 #[allow(dead_code)]
254 mode: Mode,
255 #[allow(dead_code)]
256 #[serde(rename = "entity")]
257 export: String,
258 #[allow(dead_code)]
259 op: String,
260 #[allow(dead_code)]
261 data: Vec<SnapshotEntity>,
262 #[serde(default = "super::default_complete")]
263 complete: bool,
264 }
265
266 let json_without_complete = serde_json::json!({
267 "mode": "list",
268 "entity": "tokens/list",
269 "op": "snapshot",
270 "data": []
271 });
272
273 let frame: TestSnapshotFrame = serde_json::from_value(json_without_complete).unwrap();
274 assert!(frame.complete);
275 }
276
277 #[test]
278 fn test_snapshot_frame_batching_fields() {
279 let first_batch = SnapshotFrame {
280 mode: Mode::List,
281 export: "tokens/list".to_string(),
282 op: "snapshot",
283 data: vec![],
284 complete: false,
285 };
286
287 let final_batch = SnapshotFrame {
288 mode: Mode::List,
289 export: "tokens/list".to_string(),
290 op: "snapshot",
291 data: vec![],
292 complete: true,
293 };
294
295 assert!(!first_batch.complete);
296 assert!(final_batch.complete);
297 }
298
299 #[test]
300 fn wire_format_stringifies_marked_wide_int_paths() {
301 let wire_format = WireFormat {
302 wide_int_paths: vec![
303 vec!["amount".to_string()],
304 vec!["nested".to_string(), "timestamp".to_string()],
305 vec!["values".to_string()],
306 vec!["positions".to_string(), "liquidity".to_string()],
307 ],
308 };
309
310 let mut value = serde_json::json!({
311 "amount": 42,
312 "nested": { "timestamp": -7 },
313 "values": [1, 2, 3],
314 "positions": [
315 { "liquidity": 9, "label": "a" },
316 { "liquidity": 11, "label": "b" }
317 ],
318 "small": 5,
319 });
320
321 apply_wire_format(&mut value, &wire_format);
322
323 assert_eq!(value["amount"], serde_json::Value::String("42".to_string()));
324 assert_eq!(
325 value["nested"]["timestamp"],
326 serde_json::Value::String("-7".to_string())
327 );
328 assert_eq!(
329 value["values"][0],
330 serde_json::Value::String("1".to_string())
331 );
332 assert_eq!(
333 value["positions"][1]["liquidity"],
334 serde_json::Value::String("11".to_string())
335 );
336 assert_eq!(value["small"], serde_json::json!(5));
337 }
338}