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, Serialize, Deserialize)]
17pub struct Frame {
18 pub mode: Mode,
19 #[serde(rename = "entity")]
20 pub export: String,
21 pub op: &'static str,
22 pub key: String,
23 pub data: serde_json::Value,
24 #[serde(skip_serializing_if = "Vec::is_empty", default)]
25 pub append: Vec<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SnapshotEntity {
31 pub key: String,
32 pub data: serde_json::Value,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct SnapshotFrame {
38 pub mode: Mode,
39 #[serde(rename = "entity")]
40 pub export: String,
41 pub op: &'static str,
42 pub data: Vec<SnapshotEntity>,
43 #[serde(default = "default_complete")]
47 pub complete: bool,
48}
49
50fn default_complete() -> bool {
51 true
52}
53
54impl Frame {
55 pub fn entity(&self) -> &str {
56 &self.export
57 }
58
59 pub fn key(&self) -> &str {
60 &self.key
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_frame_entity_key_accessors() {
70 let frame = Frame {
71 mode: Mode::List,
72 export: "SettlementGame/list".to_string(),
73 op: "upsert",
74 key: "123".to_string(),
75 data: serde_json::json!({}),
76 append: vec![],
77 };
78
79 assert_eq!(frame.entity(), "SettlementGame/list");
80 assert_eq!(frame.key(), "123");
81 }
82
83 #[test]
84 fn test_frame_serialization() {
85 let frame = Frame {
86 mode: Mode::List,
87 export: "SettlementGame/list".to_string(),
88 op: "upsert",
89 key: "123".to_string(),
90 data: serde_json::json!({"gameId": "123"}),
91 append: vec![],
92 };
93
94 let json = serde_json::to_value(&frame).unwrap();
95 assert_eq!(json["op"], "upsert");
96 assert_eq!(json["mode"], "list");
97 assert_eq!(json["entity"], "SettlementGame/list");
98 assert_eq!(json["key"], "123");
99 }
100
101 #[test]
102 fn test_snapshot_frame_complete_serialization() {
103 let frame = SnapshotFrame {
104 mode: Mode::List,
105 export: "tokens/list".to_string(),
106 op: "snapshot",
107 data: vec![SnapshotEntity {
108 key: "abc".to_string(),
109 data: serde_json::json!({"id": "abc"}),
110 }],
111 complete: false,
112 };
113
114 let json = serde_json::to_value(&frame).unwrap();
115 assert_eq!(json["complete"], false);
116 assert_eq!(json["op"], "snapshot");
117 }
118
119 #[test]
120 fn test_snapshot_frame_complete_defaults_to_true_on_deserialize() {
121 #[derive(Debug, Deserialize)]
122 struct TestSnapshotFrame {
123 #[allow(dead_code)]
124 mode: Mode,
125 #[allow(dead_code)]
126 #[serde(rename = "entity")]
127 export: String,
128 #[allow(dead_code)]
129 op: String,
130 #[allow(dead_code)]
131 data: Vec<SnapshotEntity>,
132 #[serde(default = "super::default_complete")]
133 complete: bool,
134 }
135
136 let json_without_complete = serde_json::json!({
137 "mode": "list",
138 "entity": "tokens/list",
139 "op": "snapshot",
140 "data": []
141 });
142
143 let frame: TestSnapshotFrame = serde_json::from_value(json_without_complete).unwrap();
144 assert!(frame.complete);
145 }
146
147 #[test]
148 fn test_snapshot_frame_batching_fields() {
149 let first_batch = SnapshotFrame {
150 mode: Mode::List,
151 export: "tokens/list".to_string(),
152 op: "snapshot",
153 data: vec![],
154 complete: false,
155 };
156
157 let final_batch = SnapshotFrame {
158 mode: Mode::List,
159 export: "tokens/list".to_string(),
160 op: "snapshot",
161 data: vec![],
162 complete: true,
163 };
164
165 assert!(!first_batch.complete);
166 assert!(final_batch.complete);
167 }
168}