ferro_broadcast/
message.rs

1//! Broadcast message types.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// A message that can be broadcast to channels.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct BroadcastMessage {
9    /// The event name.
10    pub event: String,
11    /// The channel name.
12    pub channel: String,
13    /// The message data.
14    pub data: Value,
15}
16
17impl BroadcastMessage {
18    /// Create a new broadcast message.
19    pub fn new(channel: impl Into<String>, event: impl Into<String>, data: impl Serialize) -> Self {
20        Self {
21            channel: channel.into(),
22            event: event.into(),
23            data: serde_json::to_value(data).unwrap_or(Value::Null),
24        }
25    }
26
27    /// Create with raw JSON data.
28    pub fn with_data(channel: impl Into<String>, event: impl Into<String>, data: Value) -> Self {
29        Self {
30            channel: channel.into(),
31            event: event.into(),
32            data,
33        }
34    }
35
36    /// Serialize to JSON string.
37    pub fn to_json(&self) -> Result<String, serde_json::Error> {
38        serde_json::to_string(self)
39    }
40}
41
42/// A client-to-server message.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(tag = "type", rename_all = "snake_case")]
45pub enum ClientMessage {
46    /// Subscribe to a channel.
47    Subscribe {
48        channel: String,
49        #[serde(default)]
50        auth: Option<String>,
51    },
52    /// Unsubscribe from a channel.
53    Unsubscribe { channel: String },
54    /// Send a message to a channel (client events).
55    Whisper {
56        channel: String,
57        event: String,
58        data: Value,
59    },
60    /// Ping to keep connection alive.
61    Ping,
62}
63
64/// A server-to-client message.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(tag = "type", rename_all = "snake_case")]
67pub enum ServerMessage {
68    /// Connection established.
69    Connected { socket_id: String },
70    /// Subscription successful.
71    Subscribed { channel: String },
72    /// Subscription failed.
73    SubscriptionError { channel: String, error: String },
74    /// Unsubscribed from channel.
75    Unsubscribed { channel: String },
76    /// Broadcast event.
77    Event(BroadcastMessage),
78    /// Member joined (presence channels).
79    MemberAdded {
80        channel: String,
81        user_id: String,
82        user_info: Value,
83    },
84    /// Member left (presence channels).
85    MemberRemoved { channel: String, user_id: String },
86    /// Pong response.
87    Pong,
88    /// Error message.
89    Error { message: String },
90}
91
92impl ServerMessage {
93    /// Serialize to JSON string.
94    pub fn to_json(&self) -> Result<String, serde_json::Error> {
95        serde_json::to_string(self)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_broadcast_message() {
105        let msg = BroadcastMessage::new("orders.1", "OrderUpdated", serde_json::json!({"id": 1}));
106        assert_eq!(msg.channel, "orders.1");
107        assert_eq!(msg.event, "OrderUpdated");
108    }
109
110    #[test]
111    fn test_client_message_serialize() {
112        let msg = ClientMessage::Subscribe {
113            channel: "private-orders.1".into(),
114            auth: Some("auth_token".into()),
115        };
116        let json = serde_json::to_string(&msg).unwrap();
117        assert!(json.contains("subscribe"));
118        assert!(json.contains("private-orders.1"));
119    }
120
121    #[test]
122    fn test_server_message_serialize() {
123        let msg = ServerMessage::Connected {
124            socket_id: "abc123".into(),
125        };
126        let json = serde_json::to_string(&msg).unwrap();
127        assert!(json.contains("connected"));
128        assert!(json.contains("abc123"));
129    }
130}