hyperstack_server/websocket/
subscription.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5#[serde(tag = "type", rename_all = "lowercase")]
6pub enum ClientMessage {
7 Subscribe(Subscription),
9 Unsubscribe(Unsubscription),
11 Ping,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Subscription {
18 pub view: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub key: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub partition: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Unsubscription {
28 pub view: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub key: Option<String>,
31}
32
33impl Unsubscription {
34 pub fn sub_key(&self) -> String {
36 match &self.key {
37 Some(k) => format!("{}:{}", self.view, k),
38 None => format!("{}:*", self.view),
39 }
40 }
41}
42
43impl Subscription {
44 pub fn matches_view(&self, view_id: &str) -> bool {
45 self.view == view_id
46 }
47
48 pub fn matches_key(&self, key: &str) -> bool {
49 self.key.as_ref().is_none_or(|k| k == key)
50 }
51
52 pub fn matches(&self, view_id: &str, key: &str) -> bool {
53 self.matches_view(view_id) && self.matches_key(key)
54 }
55
56 pub fn sub_key(&self) -> String {
57 match &self.key {
58 Some(k) => format!("{}:{}", self.view, k),
59 None => format!("{}:*", self.view),
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use serde_json::json;
68
69 #[test]
70 fn test_subscription_parse() {
71 let json = json!({
72 "view": "SettlementGame/list",
73 "key": "835"
74 });
75
76 let sub: Subscription = serde_json::from_value(json).unwrap();
77 assert_eq!(sub.view, "SettlementGame/list");
78 assert_eq!(sub.key, Some("835".to_string()));
79 }
80
81 #[test]
82 fn test_subscription_no_key() {
83 let json = json!({
84 "view": "SettlementGame/list"
85 });
86
87 let sub: Subscription = serde_json::from_value(json).unwrap();
88 assert_eq!(sub.view, "SettlementGame/list");
89 assert!(sub.key.is_none());
90 }
91
92 #[test]
93 fn test_subscription_matches() {
94 let sub = Subscription {
95 view: "SettlementGame/list".to_string(),
96 key: Some("835".to_string()),
97 partition: None,
98 };
99
100 assert!(sub.matches("SettlementGame/list", "835"));
101 assert!(!sub.matches("SettlementGame/list", "836"));
102 assert!(!sub.matches("SettlementGame/state", "835"));
103 }
104
105 #[test]
106 fn test_subscription_matches_all_keys() {
107 let sub = Subscription {
108 view: "SettlementGame/list".to_string(),
109 key: None,
110 partition: None,
111 };
112
113 assert!(sub.matches("SettlementGame/list", "835"));
114 assert!(sub.matches("SettlementGame/list", "836"));
115 assert!(!sub.matches("SettlementGame/state", "835"));
116 }
117
118 #[test]
119 fn test_client_message_subscribe_parse() {
120 let json = json!({
121 "type": "subscribe",
122 "view": "SettlementGame/list",
123 "key": "835"
124 });
125
126 let msg: ClientMessage = serde_json::from_value(json).unwrap();
127 match msg {
128 ClientMessage::Subscribe(sub) => {
129 assert_eq!(sub.view, "SettlementGame/list");
130 assert_eq!(sub.key, Some("835".to_string()));
131 }
132 _ => panic!("Expected Subscribe"),
133 }
134 }
135
136 #[test]
137 fn test_client_message_unsubscribe_parse() {
138 let json = json!({
139 "type": "unsubscribe",
140 "view": "SettlementGame/list",
141 "key": "835"
142 });
143
144 let msg: ClientMessage = serde_json::from_value(json).unwrap();
145 match msg {
146 ClientMessage::Unsubscribe(unsub) => {
147 assert_eq!(unsub.view, "SettlementGame/list");
148 assert_eq!(unsub.key, Some("835".to_string()));
149 }
150 _ => panic!("Expected Unsubscribe"),
151 }
152 }
153
154 #[test]
155 fn test_client_message_ping_parse() {
156 let json = json!({ "type": "ping" });
157
158 let msg: ClientMessage = serde_json::from_value(json).unwrap();
159 assert!(matches!(msg, ClientMessage::Ping));
160 }
161
162 #[test]
163 fn test_legacy_subscription_parse_as_subscribe() {
164 let json = json!({
165 "view": "SettlementGame/list",
166 "key": "835"
167 });
168
169 let sub: Subscription = serde_json::from_value(json).unwrap();
170 assert_eq!(sub.view, "SettlementGame/list");
171 assert_eq!(sub.key, Some("835".to_string()));
172 }
173
174 #[test]
175 fn test_sub_key_with_key() {
176 let sub = Subscription {
177 view: "SettlementGame/list".to_string(),
178 key: Some("835".to_string()),
179 partition: None,
180 };
181 assert_eq!(sub.sub_key(), "SettlementGame/list:835");
182 }
183
184 #[test]
185 fn test_sub_key_without_key() {
186 let sub = Subscription {
187 view: "SettlementGame/list".to_string(),
188 key: None,
189 partition: None,
190 };
191 assert_eq!(sub.sub_key(), "SettlementGame/list:*");
192 }
193
194 #[test]
195 fn test_unsubscription_sub_key() {
196 let unsub = Unsubscription {
197 view: "SettlementGame/list".to_string(),
198 key: Some("835".to_string()),
199 };
200 assert_eq!(unsub.sub_key(), "SettlementGame/list:835");
201
202 let unsub_all = Unsubscription {
203 view: "SettlementGame/list".to_string(),
204 key: None,
205 };
206 assert_eq!(unsub_all.sub_key(), "SettlementGame/list:*");
207 }
208}