use serde::Deserialize; type PostId = String; type UserId = String; #[derive(Deserialize, Debug, PartialEq, Eq)] #[serde(untagged)] pub enum WebsocketMessage { Update(WebsocketUpdate), Reply(WebsocketReply), } #[derive(Deserialize, Debug, PartialEq, Eq)] pub struct WebsocketReply { pub status: String, pub seq_reply: u32, } #[derive(Deserialize, Debug, PartialEq, Eq)] pub struct Broadcast { pub channel_id: String, pub connection_id: String, pub omit_connection_id: String, // omit_users: pub team_id: String, pub user_id: String, } #[derive(Deserialize, Debug, PartialEq, Eq)] pub struct WebsocketUpdateStuff { pub broadcast: Broadcast, pub seq: u32, } #[derive(Deserialize, Debug, PartialEq, Eq)] pub struct WebsocketUpdate { #[serde(flatten)] pub stuff: WebsocketUpdateStuff, #[serde(flatten)] pub update: Update, } #[derive(Deserialize, Debug, PartialEq, Eq)] #[serde(tag = "event", content = "data")] pub enum Update { #[serde(rename = "posted")] Posted(Posted), #[serde(rename = "hello")] Hello(Hello), #[serde(rename = "status_change")] StatusChange {}, #[serde(rename = "typing")] Typing {}, #[serde(rename = "channel_viewed")] ChannelViewed {}, #[serde(other)] Other, } #[derive(Deserialize, Debug, PartialEq, Eq)] pub struct Posted { pub channel_display_name: String, pub channel_name: String, pub channel_type: String, #[serde(deserialize_with = "deserialize_json_field", default)] pub mentions: Vec, #[serde(deserialize_with = "deserialize_json_field")] pub post: Post, } fn deserialize_json_field<'de, D, T>(deserializer: D) -> Result where D: serde::Deserializer<'de>, T: serde::de::DeserializeOwned, { let buf: String = String::deserialize(deserializer)?; serde_json::from_str(&buf).map_err(|x| { serde::de::Error::custom(format!("Error while deserializing json encoded field {x}")) }) } #[derive(Deserialize, Debug, PartialEq, Eq)] pub struct Post { pub id: String, pub create_at: u64, pub update_at: u64, pub edit_at: u64, pub delete_at: u64, pub is_pinned: bool, pub user_id: String, pub channel_id: String, // pub hashtags: String, // pub last_reply_at: u64, pub message: String, // pub metadata:, // pub original_id: String, // pub participants:, // pub pending_post_id: String, // pub preops // pub reply_count: u64, pub root_id: String, #[serde(rename = "type")] pub ttype: String, } #[derive(Deserialize, Debug, PartialEq, Eq)] 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, WebsocketUpdate, WebsocketUpdateStuff, }; #[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 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: WebsocketUpdateStuff { broadcast: Broadcast { channel_id: "a".to_owned(), connection_id: "b".to_owned(), omit_connection_id: "c".to_owned(), team_id: "d".to_owned(), user_id: "e".to_owned() }, 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: WebsocketUpdateStuff { broadcast: Broadcast { channel_id: "a".to_owned(), connection_id: "b".to_owned(), omit_connection_id: "c".to_owned(), team_id: "d".to_owned(), user_id: "e".to_owned() }, 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(); } }