From 7c934e1b8f5f3415c0409df6346ad0c25c0d5da6 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 16 Jun 2020 17:27:39 -0400 Subject: [PATCH] Implement EventContent for CustomEventConent and include in Any*EventContent enums --- ruma-events-macros/src/content_enum.rs | 14 +- ruma-events/src/custom.rs | 18 ++- ruma-events/tests/custom.rs | 190 +++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 ruma-events/tests/custom.rs diff --git a/ruma-events-macros/src/content_enum.rs b/ruma-events-macros/src/content_enum.rs index e54bb999..9fcb2cab 100644 --- a/ruma-events-macros/src/content_enum.rs +++ b/ruma-events-macros/src/content_enum.rs @@ -27,8 +27,10 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result pub enum #ident { #( #[doc = #event_type_str] - #variants(#content) - ),* + #variants(#content), + )* + #[doc = "Represents any event not defined in the Matrix spec."] + Custom(::ruma_events::custom::CustomEventContent) } }; @@ -36,7 +38,8 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result impl ::ruma_events::EventContent for #ident { fn event_type(&self) -> &str { match self { - #( Self::#variants(content) => content.event_type() ),* + #( Self::#variants(content) => content.event_type(), )* + #ident::Custom(content) => content.event_type(), } } @@ -51,7 +54,10 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result Ok(#ident::#variants(content)) }, )* - ev => Err(::serde::de::Error::custom(format!("event not supported {}", ev))), + ev_type => { + let content = ::ruma_events::custom::CustomEventContent::from_parts(ev_type, input)?; + Ok(#ident::Custom(content)) + }, } } } diff --git a/ruma-events/src/custom.rs b/ruma-events/src/custom.rs index 8f2e88e3..74dd8a9f 100644 --- a/ruma-events/src/custom.rs +++ b/ruma-events/src/custom.rs @@ -4,11 +4,9 @@ use std::time::SystemTime; use ruma_identifiers::{EventId, RoomId, UserId}; use serde::Serialize; -use serde_json::Value as JsonValue; +use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue}; -use crate::UnsignedData; - -// TODO: (De)serialization +use crate::{EventContent, UnsignedData}; /// A custom event's type and `content` JSON object. #[derive(Clone, Debug, Serialize)] @@ -18,9 +16,21 @@ pub struct CustomEventContent { pub event_type: String, /// The actual `content` JSON object. + #[serde(flatten)] pub json: JsonValue, } +impl EventContent for CustomEventContent { + fn event_type(&self) -> &str { + &self.event_type + } + + fn from_parts(event_type: &str, content: Box) -> Result { + let json = serde_json::from_str(content.get())?; + Ok(Self { event_type: event_type.to_string(), json }) + } +} + /// A custom event not covered by the Matrix specification. #[derive(Clone, Debug)] pub struct CustomBasicEvent { diff --git a/ruma-events/tests/custom.rs b/ruma-events/tests/custom.rs new file mode 100644 index 00000000..251764f9 --- /dev/null +++ b/ruma-events/tests/custom.rs @@ -0,0 +1,190 @@ +use std::{ + convert::TryFrom, + time::{Duration, UNIX_EPOCH}, +}; + +use matches::assert_matches; +use ruma_events::{ + custom::CustomEventContent, AnyMessageEventContent, AnyStateEventContent, EventJson, + MessageEvent, StateEvent, StateEventStub, UnsignedData, +}; +use ruma_identifiers::{EventId, RoomId, UserId}; +use serde_json::{ + from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, +}; + +fn custom_state_event() -> JsonValue { + json!({ + "content": { + "m.relates_to": { + "event_id": "$MDitXXXXXX", + "key": "👍", + "rel_type": "m.annotation" + } + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 10, + "room_id": "!room:room.com", + "sender": "@carl:example.com", + "state_key": "", + "type": "m.reaction", + "unsigned": { + "age": 85 + } + }) +} + +#[test] +fn serialize_custom_message_event() { + let aliases_event = MessageEvent { + content: AnyMessageEventContent::Custom(CustomEventContent { + json: json!({ + "body": " * edited message", + "m.new_content": { + "body": "edited message", + "msgtype": "m.text" + }, + "m.relates_to": { + "event_id": "some event id", + "rel_type": "m.replace" + }, + "msgtype": "m.text" + }), + event_type: "m.room.message".to_string(), + }), + event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), + origin_server_ts: UNIX_EPOCH + Duration::from_millis(10), + room_id: RoomId::try_from("!room:room.com").unwrap(), + sender: UserId::try_from("@carl:example.com").unwrap(), + unsigned: UnsignedData::default(), + }; + + let actual = to_json_value(&aliases_event).unwrap(); + let expected = json!({ + "content": { + "body": " * edited message", + "m.new_content": { + "body": "edited message", + "msgtype": "m.text" + }, + "m.relates_to": { + "event_id": "some event id", + "rel_type": "m.replace" + }, + "msgtype": "m.text" + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 10, + "sender": "@carl:example.com", + "room_id": "!room:room.com", + "type": "m.room.message", + }); + + assert_eq!(actual, expected); +} + +#[test] +fn serialize_custom_state_event() { + let aliases_event = StateEvent { + content: AnyStateEventContent::Custom(CustomEventContent { + json: json!({ + "custom": 10 + }), + event_type: "m.made.up".to_string(), + }), + event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), + origin_server_ts: UNIX_EPOCH + Duration::from_millis(10), + prev_content: None, + room_id: RoomId::try_from("!roomid:room.com").unwrap(), + sender: UserId::try_from("@carl:example.com").unwrap(), + state_key: "".to_string(), + unsigned: UnsignedData::default(), + }; + + let actual = to_json_value(&aliases_event).unwrap(); + let expected = json!({ + "content": { + "custom": 10 + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 10, + "room_id": "!roomid:room.com", + "sender": "@carl:example.com", + "state_key": "", + "type": "m.made.up", + }); + + assert_eq!(actual, expected); +} + +#[test] +fn deserialize_custom_state_event() { + let json_data = custom_state_event(); + + let expected_content = json!({ + "m.relates_to": { + "event_id": "$MDitXXXXXX", + "key": "👍", + "rel_type": "m.annotation" + } + }); + + assert_matches!( + from_json_value::>>(json_data) + .unwrap() + .deserialize() + .unwrap(), + StateEvent { + content: AnyStateEventContent::Custom(CustomEventContent { + json, event_type, + }), + event_id, + origin_server_ts, + sender, + room_id, + prev_content: None, + state_key, + unsigned, + } if json == expected_content && event_type == "m.reaction" + && event_id == EventId::try_from("$h29iv0s8:example.com").unwrap() + && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10) + && sender == UserId::try_from("@carl:example.com").unwrap() + && room_id == RoomId::try_from("!room:room.com").unwrap() + && state_key == "" + && !unsigned.is_empty() + ); +} + +#[test] +fn deserialize_custom_state_stub_event() { + let json_data = custom_state_event(); + + let expected_content = json!({ + "m.relates_to": { + "event_id": "$MDitXXXXXX", + "key": "👍", + "rel_type": "m.annotation" + } + }); + + assert_matches!( + from_json_value::>(json_data) + .unwrap(), + StateEventStub { + content: AnyStateEventContent::Custom(CustomEventContent { + json, event_type, + }), + event_id, + origin_server_ts, + sender, + prev_content: None, + state_key, + unsigned, + } if json == expected_content && event_type == "m.reaction" + && event_id == EventId::try_from("$h29iv0s8:example.com").unwrap() + && origin_server_ts == UNIX_EPOCH + Duration::from_millis(10) + && sender == UserId::try_from("@carl:example.com").unwrap() + && state_key == "" + && !unsigned.is_empty() + ); +}