use serde_with::serde_as;
use std::collections::HashMap;
use serde::Deserialize;
type PostId = String;
type TeamId = String;
type UserId = String;
type ChannelId = String;
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(untagged)]
pub enum WebsocketMessage {
Update(WebsocketUpdate),
Reply(WebsocketReply),
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct WebsocketReply {
pub status: String,
pub seq_reply: u32,
}
#[serde_as]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Broadcast {
#[serde_as(as = "serde_with::NoneAsEmptyString")]
pub channel_id: Option<ChannelId>,
#[serde_as(as = "serde_with::NoneAsEmptyString")]
pub connection_id: Option<String>,
#[serde_as(as = "serde_with::NoneAsEmptyString")]
pub omit_connection_id: Option<String>,
#[serde(default)]
pub omit_users: Option<HashMap<UserId, bool>>,
#[serde_as(as = "serde_with::NoneAsEmptyString")]
pub team_id: Option<TeamId>,
#[serde_as(as = "serde_with::NoneAsEmptyString")]
pub user_id: Option<UserId>,
#[serde(default)]
pub contains_sanitized_data: bool,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct WebsocketUpdateFields {
pub broadcast: Broadcast,
pub seq: u32,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct WebsocketUpdate {
#[serde(flatten)]
pub stuff: WebsocketUpdateFields,
#[serde(flatten)]
pub update: Update,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(tag = "event", content = "data")]
pub enum Update {
#[serde(rename = "hello")]
Hello(Hello),
#[serde(rename = "new_user")]
NewUser { user_id: UserId },
#[serde(rename = "user_updated")]
UserUpdated(UserUpdated),
#[serde(rename = "posted")]
Posted(Posted),
#[serde(rename = "status_change")]
StatusChange {},
#[serde(rename = "typing")]
Typing {},
#[serde(rename = "channel_viewed")]
ChannelViewed {},
#[serde(rename = "multiple_channels_viewed")]
MultipleChannelViewed {},
// #[serde(other)]
// Other,
}
#[serde_as]
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Posted {
pub channel_display_name: String,
pub channel_name: String,
pub channel_type: String,
#[serde_as(as = "serde_with::json::JsonString")]
#[serde(default)]
pub mentions: Vec<String>,
#[serde_as(as = "serde_with::json::JsonString")]
pub post: Post,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Post {
pub id: PostId,
pub create_at: u64,
pub update_at: u64,
pub edit_at: u64,
pub delete_at: u64,
pub is_pinned: bool,
pub user_id: UserId,
pub channel_id: ChannelId,
// pub hashtags: String,
// pub last_reply_at: u64,
pub message: String,
// pub metadata:,
// pub original_id: PostId,
// pub participants:,
// pub pending_post_id: PostId,
// pub preops
// pub reply_count: u64,
pub root_id: String,
#[serde(rename = "type")]
pub ttype: String,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct UserUpdated {
pub id: UserId,
pub create_at: u64,
pub update_at: u64,
pub delete_at: u64,
pub username: String,
pub authdata: String,
pub auth_service: String,
pub email: String,
pub nickname: String,
pub first_name: String,
pub last_name: String,
pub position: String,
pub roles: String,
pub locale: String,
pub timezone: Timezone,
pub disable_welcome_email: bool,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Timezone {
#[serde(rename = "automaticTimezone")]
pub automatic_timezone: String,
#[serde(rename = "manualTimezone")]
pub manual_timezone: String,
#[serde(rename = "useAutomaticTimezone")]
pub use_automatic_timezone: String,
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Hello {
pub connection_id: String,
pub server_version: String,
}
#[cfg(test)]
mod tests {
use serde_json::Value;
use crate::mattermost::model::{
Broadcast, Post, Update, WebsocketMessage, WebsocketUpdate, WebsocketUpdateFields,
};
#[test]
fn parse_post() {
let post = Post {
id: "a".to_owned(),
create_at: 1,
update_at: 2,
edit_at: 3,
delete_at: 4,
is_pinned: true,
user_id: "b".to_owned(),
channel_id: "c".to_owned(),
message: "d".to_owned(),
root_id: "e".to_owned(),
ttype: "f".to_owned(),
};
let post_str = r#"{
"id":"a",
"create_at":1,
"update_at":2,
"edit_at":3,
"delete_at":4,
"is_pinned":true,
"user_id":"b",
"channel_id":"c",
"message":"d",
"root_id":"e",
"type":"f"
}"#;
let d: Result<Post, _> = serde_json::from_str(post_str);
assert_eq!(d.unwrap(), post);
let data = format!(
"\"{}\"",
post_str.replace('\n', "\\n").replace('\"', "\\\"")
);
let x: Value = serde_json::from_str(&data).unwrap();
let s = x.as_str().unwrap();
let d: Result<Post, _> = serde_json::from_str(s);
assert_eq!(d.unwrap(), post);
}
#[test]
fn parse_update() {
let post = Post {
id: "a".to_owned(),
create_at: 1,
update_at: 2,
edit_at: 3,
delete_at: 4,
is_pinned: true,
user_id: "b".to_owned(),
channel_id: "c".to_owned(),
message: "d".to_owned(),
root_id: "e".to_owned(),
ttype: "f".to_owned(),
};
let data = r#"
{
"data":{
"connection_id": "aaaa",
"server_version": "2137"
},
"event":"hello"
}"#;
let update: Result<Update, _> = serde_json::from_str(data);
assert_eq!(
update.unwrap(),
Update::Hello(super::Hello {
connection_id: "aaaa".to_owned(),
server_version: "2137".to_owned()
})
);
let data = r#"
{
"data":{
"channel_display_name":"a",
"channel_name":"b",
"channel_type":"c",
"mentions":"[\"d\"]",
"post": "{\"id\":\"a\",\"create_at\":1,\"update_at\":2,\"edit_at\":3,\"delete_at\":4,\"is_pinned\":true,\"user_id\":\"b\",\"channel_id\":\"c\",\"message\":\"d\",\"root_id\":\"e\",\"type\":\"f\"}"
},
"event":"posted"
}"#;
let update: Result<Update, _> = serde_json::from_str(&data);
assert_eq!(
update.unwrap(),
Update::Posted(super::Posted {
channel_display_name: "a".to_owned(),
channel_name: "b".to_owned(),
channel_type: "c".to_owned(),
mentions: vec!["d".to_owned()],
post: post
})
);
}
#[test]
fn parse_websocketupdate() {
let broadcast = Broadcast {
channel_id: Some("a".to_owned()),
connection_id: Some("b".to_owned()),
omit_connection_id: Some("c".to_owned()),
team_id: Some("d".to_owned()),
user_id: Some("e".to_owned()),
omit_users: None,
contains_sanitized_data: false,
};
let post = Post {
id: "a".to_owned(),
create_at: 1,
update_at: 2,
edit_at: 3,
delete_at: 4,
is_pinned: true,
user_id: "b".to_owned(),
channel_id: "c".to_owned(),
message: "d".to_owned(),
root_id: "e".to_owned(),
ttype: "f".to_owned(),
};
let data = r#"
{
"broadcast":{
"channel_id":"a",
"connection_id":"b",
"omit_connection_id":"c",
"team_id":"d",
"user_id":"e"
},
"data":{
"connection_id": "aaaa",
"server_version": "2137"
},
"event":"hello",
"seq":2137
}"#;
let update: Result<WebsocketUpdate, _> = serde_json::from_str(data);
assert_eq!(
update.unwrap(),
WebsocketUpdate {
stuff: WebsocketUpdateFields {
broadcast: broadcast.clone(),
seq: 2137,
},
update: Update::Hello(super::Hello {
connection_id: "aaaa".to_owned(),
server_version: "2137".to_owned()
})
}
);
let data = r#"
{
"broadcast":{
"channel_id":"a",
"connection_id":"b",
"omit_connection_id":"c",
"team_id":"d",
"user_id":"e"
},
"data":{
"channel_display_name":"a",
"channel_name":"b",
"channel_type":"c",
"mentions":"[\"d\"]",
"post": "{\"id\":\"a\",\"create_at\":1,\"update_at\":2,\"edit_at\":3,\"delete_at\":4,\"is_pinned\":true,\"user_id\":\"b\",\"channel_id\":\"c\",\"message\":\"d\",\"root_id\":\"e\",\"type\":\"f\"}"
},
"event":"posted",
"seq": 2137
}"#;
let update: Result<WebsocketUpdate, _> = serde_json::from_str(&data);
assert_eq!(
update.unwrap(),
WebsocketUpdate {
stuff: WebsocketUpdateFields {
broadcast: broadcast.clone(),
seq: 2137,
},
update: Update::Posted(super::Posted {
channel_display_name: "a".to_owned(),
channel_name: "b".to_owned(),
channel_type: "c".to_owned(),
mentions: vec!["d".to_owned()],
post: post
})
}
);
}
#[test]
fn parse_realexamples() {
let data = r#"{"event":"posted","data":{"channel_display_name":"a","channel_name":"a","channel_type":"O","post":"{\"id\":\"b\",\"create_at\":1,\"update_at\":2,\"edit_at\":0,\"delete_at\":0,\"is_pinned\":false,\"user_id\":\"b\",\"channel_id\":\"c\",\"root_id\":\"\",\"original_id\":\"\",\"message\":\"blah\",\"type\":\"\",\"props\":{\"disable_group_highlight\":true},\"hashtags\":\"\",\"pending_post_id\":\"d\",\"reply_count\":0,\"last_reply_at\":0,\"participants\":null,\"metadata\":{}}","sender_name":"@blah","set_online":true,"team_id":"e"},"broadcast":{"omit_users":null,"user_id":"","channel_id":"f","team_id":"","connection_id":"","omit_connection_id":""},"seq":3}"#;
serde_json::from_str::<WebsocketMessage>(data).unwrap();
let data = r#"{"event": "multiple_channels_viewed", "data": {"channel_times":{"aaaa":1706319279901}}, "broadcast": {"omit_users":null,"user_id":"bbbb","channel_id":"","team_id":"","connection_id":"","omit_connection_id":""}, "seq": 6}"#;
serde_json::from_str::<WebsocketMessage>(data).unwrap();
}
}