ferro_broadcast/
message.rs1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use tokio_tungstenite::tungstenite::protocol::Message as WsMessage;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct BroadcastMessage {
10 pub event: String,
12 pub channel: String,
14 pub data: Value,
16}
17
18impl BroadcastMessage {
19 pub fn new(channel: impl Into<String>, event: impl Into<String>, data: impl Serialize) -> Self {
21 Self {
22 channel: channel.into(),
23 event: event.into(),
24 data: serde_json::to_value(data).unwrap_or(Value::Null),
25 }
26 }
27
28 pub fn with_data(channel: impl Into<String>, event: impl Into<String>, data: Value) -> Self {
30 Self {
31 channel: channel.into(),
32 event: event.into(),
33 data,
34 }
35 }
36
37 pub fn to_json(&self) -> Result<String, serde_json::Error> {
39 serde_json::to_string(self)
40 }
41
42 pub fn to_ws_message(&self) -> Result<WsMessage, serde_json::Error> {
44 let json = serde_json::to_string(self)?;
45 Ok(WsMessage::Text(json.into()))
46 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(tag = "type", rename_all = "snake_case")]
52pub enum ClientMessage {
53 Subscribe {
55 channel: String,
56 #[serde(default)]
57 auth: Option<String>,
58 #[serde(default)]
60 channel_data: Option<Value>,
61 },
62 Unsubscribe { channel: String },
64 Whisper {
66 channel: String,
67 event: String,
68 data: Value,
69 },
70 Ping,
72}
73
74impl ClientMessage {
75 pub fn from_ws_text(text: &str) -> Result<Self, serde_json::Error> {
77 serde_json::from_str(text)
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum ServerMessage {
85 Connected { socket_id: String },
87 Subscribed { channel: String },
89 SubscriptionError { channel: String, error: String },
91 Unsubscribed { channel: String },
93 Event(BroadcastMessage),
95 MemberAdded {
97 channel: String,
98 user_id: String,
99 user_info: Value,
100 },
101 MemberRemoved { channel: String, user_id: String },
103 Pong,
105 Error { message: String },
107}
108
109impl ServerMessage {
110 pub fn to_json(&self) -> Result<String, serde_json::Error> {
112 serde_json::to_string(self)
113 }
114
115 pub fn to_ws_message(&self) -> Result<WsMessage, serde_json::Error> {
117 let json = serde_json::to_string(self)?;
118 Ok(WsMessage::Text(json.into()))
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_broadcast_message() {
128 let msg = BroadcastMessage::new("orders.1", "OrderUpdated", serde_json::json!({"id": 1}));
129 assert_eq!(msg.channel, "orders.1");
130 assert_eq!(msg.event, "OrderUpdated");
131 }
132
133 #[test]
134 fn test_client_message_serialize() {
135 let msg = ClientMessage::Subscribe {
136 channel: "private-orders.1".into(),
137 auth: Some("auth_token".into()),
138 channel_data: None,
139 };
140 let json = serde_json::to_string(&msg).unwrap();
141 assert!(json.contains("subscribe"));
142 assert!(json.contains("private-orders.1"));
143 }
144
145 #[test]
146 fn test_subscribe_with_channel_data() {
147 let json = r#"{"type":"subscribe","channel":"presence-nearby","auth":"ok","channel_data":{"user_id":"42","user_info":{"name":"Alice"}}}"#;
148 let msg: ClientMessage = serde_json::from_str(json).unwrap();
149 match msg {
150 ClientMessage::Subscribe {
151 channel,
152 auth,
153 channel_data,
154 } => {
155 assert_eq!(channel, "presence-nearby");
156 assert_eq!(auth.unwrap(), "ok");
157 let data = channel_data.unwrap();
158 assert_eq!(data["user_id"], "42");
159 assert_eq!(data["user_info"]["name"], "Alice");
160 }
161 _ => panic!("Expected Subscribe"),
162 }
163 }
164
165 #[test]
166 fn test_subscribe_without_channel_data() {
167 let json = r#"{"type":"subscribe","channel":"presence-nearby","auth":"ok"}"#;
168 let msg: ClientMessage = serde_json::from_str(json).unwrap();
169 match msg {
170 ClientMessage::Subscribe { channel_data, .. } => {
171 assert!(channel_data.is_none());
172 }
173 _ => panic!("Expected Subscribe"),
174 }
175 }
176
177 #[test]
178 fn test_server_message_serialize() {
179 let msg = ServerMessage::Connected {
180 socket_id: "abc123".into(),
181 };
182 let json = serde_json::to_string(&msg).unwrap();
183 assert!(json.contains("connected"));
184 assert!(json.contains("abc123"));
185 }
186}