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, #[serde_as(as = "serde_with::NoneAsEmptyString")] pub connection_id: Option, #[serde_as(as = "serde_with::NoneAsEmptyString")] pub omit_connection_id: Option, #[serde(default)] pub omit_users: Option>, #[serde_as(as = "serde_with::NoneAsEmptyString")] pub team_id: Option, #[serde_as(as = "serde_with::NoneAsEmptyString")] pub user_id: Option, #[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, #[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 = 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 = 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 = 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 = 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 = 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 = 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::(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::(data).unwrap(); } }