ferro_broadcast/
channel.rs1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashSet;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum ChannelType {
11 Public,
13 Private,
15 Presence,
17}
18
19impl ChannelType {
20 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 pub fn requires_auth(&self) -> bool {
33 matches!(self, Self::Private | Self::Presence)
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct ChannelInfo {
40 pub name: String,
42 pub channel_type: ChannelType,
44 pub subscribers: HashSet<String>,
46 pub members: Vec<PresenceMember>,
48}
49
50impl ChannelInfo {
51 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 pub fn is_empty(&self) -> bool {
65 self.subscribers.is_empty()
66 }
67
68 pub fn subscriber_count(&self) -> usize {
70 self.subscribers.len()
71 }
72
73 pub fn add_subscriber(&mut self, socket_id: String) -> bool {
75 self.subscribers.insert(socket_id)
76 }
77
78 pub fn remove_subscriber(&mut self, socket_id: &str) -> bool {
80 self.subscribers.remove(socket_id)
81 }
82
83 pub fn add_member(&mut self, member: PresenceMember) {
85 self.members.retain(|m| m.user_id != member.user_id);
87 self.members.push(member);
88 }
89
90 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 pub fn get_members(&self) -> &[PresenceMember] {
101 &self.members
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct PresenceMember {
108 pub socket_id: String,
110 pub user_id: String,
112 pub user_info: Value,
114}
115
116impl PresenceMember {
117 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 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}