use serde::{Deserialize, Serialize};
use crate::AuthRule;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChannelDefinition {
pub channel: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth: Option<AuthRule>,
#[serde(default)]
pub rooms: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hooks: Option<ChannelHooks>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChannelHooks {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub on_connect: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub on_disconnect: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub on_message: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "lowercase")]
pub enum WsClientMessage {
Subscribe { room: String },
Unsubscribe { room: String },
Message {
room: String,
data: serde_json::Value,
},
Pong,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum WsServerMessage {
Broadcast {
room: String,
event: String,
data: serde_json::Value,
},
Subscribed { room: String },
Unsubscribed { room: String },
Error { message: String },
Ping,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn channel_definition_minimal() {
let yaml = r#"{"channel": "notifications"}"#;
let cd: ChannelDefinition = serde_json::from_str(yaml).unwrap();
assert_eq!(cd.channel, "notifications");
assert!(cd.auth.is_none());
assert!(!cd.rooms);
assert!(cd.hooks.is_none());
}
#[test]
fn channel_definition_full() {
let json = r#"{
"channel": "updates",
"auth": ["member", "admin"],
"rooms": true,
"hooks": {
"on_connect": ["log_connect"],
"on_disconnect": ["log_disconnect"],
"on_message": ["validate_message"]
}
}"#;
let cd: ChannelDefinition = serde_json::from_str(json).unwrap();
assert_eq!(cd.channel, "updates");
assert!(cd.rooms);
let hooks = cd.hooks.as_ref().unwrap();
assert_eq!(hooks.on_connect.as_ref().unwrap(), &["log_connect"]);
assert_eq!(hooks.on_disconnect.as_ref().unwrap(), &["log_disconnect"]);
assert_eq!(hooks.on_message.as_ref().unwrap(), &["validate_message"]);
}
#[test]
fn channel_definition_serde_roundtrip() {
let cd = ChannelDefinition {
channel: "chat".to_string(),
auth: Some(AuthRule::Roles(vec!["member".to_string()])),
rooms: true,
hooks: Some(ChannelHooks {
on_connect: Some(vec!["log_connect".to_string()]),
on_disconnect: None,
on_message: None,
}),
};
let json = serde_json::to_string(&cd).unwrap();
let back: ChannelDefinition = serde_json::from_str(&json).unwrap();
assert_eq!(cd, back);
}
#[test]
fn ws_client_message_subscribe() {
let json = r#"{"action": "subscribe", "room": "org:123"}"#;
let msg: WsClientMessage = serde_json::from_str(json).unwrap();
match msg {
WsClientMessage::Subscribe { room } => assert_eq!(room, "org:123"),
_ => panic!("Expected Subscribe"),
}
}
#[test]
fn ws_client_message_unsubscribe() {
let json = r#"{"action": "unsubscribe", "room": "org:123"}"#;
let msg: WsClientMessage = serde_json::from_str(json).unwrap();
match msg {
WsClientMessage::Unsubscribe { room } => assert_eq!(room, "org:123"),
_ => panic!("Expected Unsubscribe"),
}
}
#[test]
fn ws_client_message_message() {
let json = r#"{"action": "message", "room": "org:123", "data": {"text": "hello"}}"#;
let msg: WsClientMessage = serde_json::from_str(json).unwrap();
match msg {
WsClientMessage::Message { room, data } => {
assert_eq!(room, "org:123");
assert_eq!(data["text"], "hello");
}
_ => panic!("Expected Message"),
}
}
#[test]
fn ws_client_message_pong() {
let json = r#"{"action": "pong"}"#;
let msg: WsClientMessage = serde_json::from_str(json).unwrap();
assert!(matches!(msg, WsClientMessage::Pong));
}
#[test]
fn ws_server_message_broadcast() {
let msg = WsServerMessage::Broadcast {
room: "org:123".to_string(),
event: "user.created".to_string(),
data: serde_json::json!({"id": "abc"}),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"broadcast\""));
assert!(json.contains("\"room\":\"org:123\""));
}
#[test]
fn ws_server_message_subscribed() {
let msg = WsServerMessage::Subscribed {
room: "org:123".to_string(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"subscribed\""));
}
#[test]
fn ws_server_message_error() {
let msg = WsServerMessage::Error {
message: "bad request".to_string(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"error\""));
assert!(json.contains("bad request"));
}
#[test]
fn ws_server_message_ping() {
let msg = WsServerMessage::Ping;
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("\"type\":\"ping\""));
}
}