Skip to main content

ferro_broadcast/
channel.rs

1//! Channel types and management.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashSet;
6
7/// Channel type based on prefix.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum ChannelType {
11    /// Public channel - anyone can subscribe.
12    Public,
13    /// Private channel - requires authorization.
14    Private,
15    /// Presence channel - tracks online users.
16    Presence,
17}
18
19impl ChannelType {
20    /// Determine channel type from channel name.
21    pub fn from_name(name: &str) -> Self {
22        if name.starts_with("private-") {
23            Self::Private
24        } else if name.starts_with("presence-") {
25            Self::Presence
26        } else {
27            Self::Public
28        }
29    }
30
31    /// Check if this channel type requires authorization.
32    pub fn requires_auth(&self) -> bool {
33        matches!(self, Self::Private | Self::Presence)
34    }
35}
36
37/// Information about a channel.
38#[derive(Debug, Clone)]
39pub struct ChannelInfo {
40    /// Channel name.
41    pub name: String,
42    /// Channel type.
43    pub channel_type: ChannelType,
44    /// Connected client socket IDs.
45    pub subscribers: HashSet<String>,
46    /// Presence members (for presence channels).
47    pub members: Vec<PresenceMember>,
48}
49
50impl ChannelInfo {
51    /// Create a new channel.
52    pub fn new(name: impl Into<String>) -> Self {
53        let name = name.into();
54        let channel_type = ChannelType::from_name(&name);
55        Self {
56            name,
57            channel_type,
58            subscribers: HashSet::new(),
59            members: Vec::new(),
60        }
61    }
62
63    /// Check if channel has any subscribers.
64    pub fn is_empty(&self) -> bool {
65        self.subscribers.is_empty()
66    }
67
68    /// Get subscriber count.
69    pub fn subscriber_count(&self) -> usize {
70        self.subscribers.len()
71    }
72
73    /// Add a subscriber.
74    pub fn add_subscriber(&mut self, socket_id: String) -> bool {
75        self.subscribers.insert(socket_id)
76    }
77
78    /// Remove a subscriber.
79    pub fn remove_subscriber(&mut self, socket_id: &str) -> bool {
80        self.subscribers.remove(socket_id)
81    }
82
83    /// Add a presence member.
84    pub fn add_member(&mut self, member: PresenceMember) {
85        // Remove existing member with same user_id if present
86        self.members.retain(|m| m.user_id != member.user_id);
87        self.members.push(member);
88    }
89
90    /// Remove a presence member by socket ID.
91    pub fn remove_member(&mut self, socket_id: &str) -> Option<PresenceMember> {
92        if let Some(idx) = self.members.iter().position(|m| m.socket_id == socket_id) {
93            Some(self.members.remove(idx))
94        } else {
95            None
96        }
97    }
98
99    /// Get presence members.
100    pub fn get_members(&self) -> &[PresenceMember] {
101        &self.members
102    }
103}
104
105/// A member in a presence channel.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct PresenceMember {
108    /// The socket ID.
109    pub socket_id: String,
110    /// The user ID.
111    pub user_id: String,
112    /// Additional user information.
113    pub user_info: Value,
114}
115
116impl PresenceMember {
117    /// Create a new presence member.
118    pub fn new(socket_id: impl Into<String>, user_id: impl Into<String>) -> Self {
119        Self {
120            socket_id: socket_id.into(),
121            user_id: user_id.into(),
122            user_info: Value::Null,
123        }
124    }
125
126    /// Set user info.
127    pub fn with_info(mut self, info: impl Serialize) -> Self {
128        self.user_info = serde_json::to_value(info).unwrap_or(Value::Null);
129        self
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_channel_type_from_name() {
139        assert_eq!(ChannelType::from_name("orders"), ChannelType::Public);
140        assert_eq!(
141            ChannelType::from_name("private-orders.1"),
142            ChannelType::Private
143        );
144        assert_eq!(
145            ChannelType::from_name("presence-chat.1"),
146            ChannelType::Presence
147        );
148    }
149
150    #[test]
151    fn test_channel_requires_auth() {
152        assert!(!ChannelType::Public.requires_auth());
153        assert!(ChannelType::Private.requires_auth());
154        assert!(ChannelType::Presence.requires_auth());
155    }
156
157    #[test]
158    fn test_channel_info() {
159        let mut channel = ChannelInfo::new("private-orders.1");
160        assert_eq!(channel.channel_type, ChannelType::Private);
161        assert!(channel.is_empty());
162
163        channel.add_subscriber("socket_1".into());
164        assert!(!channel.is_empty());
165        assert_eq!(channel.subscriber_count(), 1);
166
167        channel.remove_subscriber("socket_1");
168        assert!(channel.is_empty());
169    }
170
171    #[test]
172    fn test_presence_members() {
173        let mut channel = ChannelInfo::new("presence-chat.1");
174        let member = PresenceMember::new("socket_1", "user_1")
175            .with_info(serde_json::json!({"name": "Alice"}));
176
177        channel.add_subscriber("socket_1".into());
178        channel.add_member(member);
179
180        assert_eq!(channel.get_members().len(), 1);
181        assert_eq!(channel.get_members()[0].user_id, "user_1");
182    }
183}