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
96impl Frame {
97 pub fn entity(&self) -> &str {
98 &self.export
99 }
100
101 pub fn key(&self) -> &str {
102 &self.key
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_frame_entity_key_accessors() {
112 let frame = Frame {
113 mode: Mode::List,
114 export: "SettlementGame/list".to_string(),
115 op: "upsert",
116 key: "123".to_string(),
117 data: serde_json::json!({}),
118 append: vec![],
119 };
120
121 assert_eq!(frame.entity(), "SettlementGame/list");
122 assert_eq!(frame.key(), "123");
123 }
124
125 #[test]
126 fn test_frame_serialization() {
127 let frame = Frame {
128 mode: Mode::List,
129 export: "SettlementGame/list".to_string(),
130 op: "upsert",
131 key: "123".to_string(),
132 data: serde_json::json!({"gameId": "123"}),
133 append: vec![],
134 };
135
136 let json = serde_json::to_value(&frame).unwrap();
137 assert_eq!(json["op"], "upsert");
138 assert_eq!(json["mode"], "list");
139 assert_eq!(json["entity"], "SettlementGame/list");
140 assert_eq!(json["key"], "123");
141 }
142
143 #[test]
144 fn test_snapshot_frame_complete_serialization() {
145 let frame = SnapshotFrame {
146 mode: Mode::List,
147 export: "tokens/list".to_string(),
148 op: "snapshot",
149 data: vec![SnapshotEntity {
150 key: "abc".to_string(),
151 data: serde_json::json!({"id": "abc"}),
152 }],
153 complete: false,
154 };
155
156 let json = serde_json::to_value(&frame).unwrap();
157 assert_eq!(json["complete"], false);
158 assert_eq!(json["op"], "snapshot");
159 }
160
161 #[test]
162 fn test_snapshot_frame_complete_defaults_to_true_on_deserialize() {
163 #[derive(Debug, Deserialize)]
164 struct TestSnapshotFrame {
165 #[allow(dead_code)]
166 mode: Mode,
167 #[allow(dead_code)]
168 #[serde(rename = "entity")]
169 export: String,
170 #[allow(dead_code)]
171 op: String,
172 #[allow(dead_code)]
173 data: Vec<SnapshotEntity>,
174 #[serde(default = "super::default_complete")]
175 complete: bool,
176 }
177
178 let json_without_complete = serde_json::json!({
179 "mode": "list",
180 "entity": "tokens/list",
181 "op": "snapshot",
182 "data": []
183 });
184
185 let frame: TestSnapshotFrame = serde_json::from_value(json_without_complete).unwrap();
186 assert!(frame.complete);
187 }
188
189 #[test]
190 fn test_snapshot_frame_batching_fields() {
191 let first_batch = SnapshotFrame {
192 mode: Mode::List,
193 export: "tokens/list".to_string(),
194 op: "snapshot",
195 data: vec![],
196 complete: false,
197 };
198
199 let final_batch = SnapshotFrame {
200 mode: Mode::List,
201 export: "tokens/list".to_string(),
202 op: "snapshot",
203 data: vec![],
204 complete: true,
205 };
206
207 assert!(!first_batch.complete);
208 assert!(final_batch.complete);
209 }
210}