From 184aafa5f6db598d63a203c86697f0234f76e287 Mon Sep 17 00:00:00 2001 From: "Ragotzy.devin" Date: Fri, 19 Jun 2020 05:36:53 -0400 Subject: [PATCH] Revert " Remove event_enum! and only use event_content_enum" This reverts commit 2a91dc1eb7215a762bd2204bc103ef172258d2d9. Also * Add back type defs and Any*Event enums * Move EventDeHelper and from_raw_json_value to lib make pub so event_enum! macro can use them and test. * Fix Any*Event enum deserialization error * Remove event_content_enum! macro and ruma-events-macros/src/content_enum.rs * Use serde's IgnoreAny to skip Unknown field's value * Clean up imports and test names for state_event --- ruma-events-macros/src/event.rs | 4 +- .../src/{content_enum.rs => event_enum.rs} | 131 ++++++++++++++---- ruma-events-macros/src/lib.rs | 13 +- ruma-events/src/enums.rs | 79 +++-------- ruma-events/src/lib.rs | 20 ++- ruma-events/tests/enums.rs | 37 ++--- ruma-events/tests/event_enums.rs | 124 +++++++++++++++++ ruma-events/tests/state_event.rs | 48 ++++++- ruma-events/tests/ui/07-enum-sanity-check.rs | 6 +- ruma-events/tests/ui/08-enum-invalid-path.rs | 6 +- .../tests/ui/08-enum-invalid-path.stderr | 15 +- 11 files changed, 349 insertions(+), 134 deletions(-) rename ruma-events-macros/src/{content_enum.rs => event_enum.rs} (58%) create mode 100644 ruma-events/tests/event_enums.rs diff --git a/ruma-events-macros/src/event.rs b/ruma-events-macros/src/event.rs index 32532529..40bb3ae7 100644 --- a/ruma-events-macros/src/event.rs +++ b/ruma-events-macros/src/event.rs @@ -244,7 +244,9 @@ fn expand_deserialize_event( while let Some(key) = map.next_key()? { match key { - Field::Unknown => continue, + Field::Unknown => { + let _: ::serde::de::IgnoredAny = map.next_value()?; + }, Field::Type => { if event_type.is_some() { return Err(::serde::de::Error::duplicate_field("type")); diff --git a/ruma-events-macros/src/content_enum.rs b/ruma-events-macros/src/event_enum.rs similarity index 58% rename from ruma-events-macros/src/content_enum.rs rename to ruma-events-macros/src/event_enum.rs index 9fcb2cab..8ea4049e 100644 --- a/ruma-events-macros/src/content_enum.rs +++ b/ruma-events-macros/src/event_enum.rs @@ -1,4 +1,4 @@ -//! Implementation of the content_enum type macro. +//! Implementation of event enum and event content enum macros. use proc_macro2::TokenStream; use quote::quote; @@ -7,16 +7,69 @@ use syn::{ Attribute, Expr, ExprLit, Ident, Lit, LitStr, Token, }; -/// Create a content enum from `ContentEnumInput`. -/// -/// This is the internals of the `event_content_enum!` macro. -pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result { +/// Create a content enum from `EventEnumInput`. +pub fn expand_event_enum(input: EventEnumInput) -> syn::Result { let attrs = &input.attrs; let ident = &input.name; - let event_type_str = &input.events; - let variants = input.events.iter().map(to_camel_case).collect::>(); + let variants = input.events.iter().map(to_camel_case).collect::>>()?; + let content = input.events.iter().map(to_event_path).collect::>(); + + let event_enum = quote! { + #( #attrs )* + #[derive(Clone, Debug, ::serde::Serialize)] + #[serde(untagged)] + #[allow(clippy::large_enum_variant)] + pub enum #ident { + #( + #[doc = #event_type_str] + #variants(#content), + )* + } + }; + + let event_deserialize_impl = quote! { + impl<'de> ::serde::de::Deserialize<'de> for #ident { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::de::Deserializer<'de>, + { + use ::serde::de::Error as _; + + let json = Box::<::serde_json::value::RawValue>::deserialize(deserializer)?; + let ::ruma_events::EventDeHelper { ev_type } = ::ruma_events::from_raw_json_value(&json)?; + match ev_type.as_str() { + #( + #event_type_str => { + let event = ::serde_json::from_str::<#content>(json.get()).map_err(D::Error::custom)?; + Ok(#ident::#variants(event)) + }, + )* + _ => Err(D::Error::custom(format!("event type `{}` is not a valid event", ev_type))) + } + } + } + }; + + let event_content_enum = expand_content_enum(input)?; + + Ok(quote! { + #event_enum + + #event_deserialize_impl + + #event_content_enum + }) +} + +/// Create a content enum from `EventEnumInput`. +pub fn expand_content_enum(input: EventEnumInput) -> syn::Result { + let attrs = &input.attrs; + let ident = Ident::new(&format!("{}Content", input.name.to_string()), input.name.span()); + let event_type_str = &input.events; + + let variants = input.events.iter().map(to_camel_case).collect::>>()?; let content = input.events.iter().map(to_event_content_path).collect::>(); let content_enum = quote! { @@ -29,8 +82,8 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result #[doc = #event_type_str] #variants(#content), )* - #[doc = "Represents any event not defined in the Matrix spec."] - Custom(::ruma_events::custom::CustomEventContent) + /// Any custom event. + Custom(::ruma_events::custom::CustomEventContent), } }; @@ -39,14 +92,11 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result fn event_type(&self) -> &str { match self { #( Self::#variants(content) => content.event_type(), )* - #ident::Custom(content) => content.event_type(), + Self::Custom(content) => content.event_type(), } } - fn from_parts( - event_type: &str, - input: Box<::serde_json::value::RawValue>, - ) -> Result { + fn from_parts(event_type: &str, input: Box<::serde_json::value::RawValue>) -> Result { match event_type { #( #event_type_str => { @@ -74,7 +124,7 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result } }; - let marker_trait_impls = marker_traits(ident); + let marker_trait_impls = marker_traits(&ident); Ok(quote! { #content_enum @@ -107,9 +157,28 @@ fn marker_traits(ident: &Ident) -> TokenStream { } } -fn to_event_content_path( - name: &LitStr, -) -> syn::punctuated::Punctuated { +fn to_event_path(name: &LitStr) -> TokenStream { + let span = name.span(); + let name = name.value(); + + assert_eq!(&name[..2], "m."); + + let path = name[2..].split('.').collect::>(); + + let event_str = path.last().unwrap(); + let event = event_str + .split('_') + .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..]) + .collect::(); + + let content_str = Ident::new(&format!("{}Event", event), span); + let path = path.iter().map(|s| Ident::new(s, span)); + quote! { + ::ruma_events::#( #path )::*::#content_str + } +} + +fn to_event_content_path(name: &LitStr) -> TokenStream { let span = name.span(); let name = name.value(); @@ -125,56 +194,60 @@ fn to_event_content_path( let content_str = Ident::new(&format!("{}EventContent", event), span); let path = path.iter().map(|s| Ident::new(s, span)); - syn::parse_quote! { + quote! { ::ruma_events::#( #path )::*::#content_str } } /// Splits the given `event_type` string on `.` and `_` removing the `m.room.` then -/// camel casing to give the `EventContent` struct name. -pub(crate) fn to_camel_case(name: &LitStr) -> Ident { +/// camel casing to give the `Event` struct name. +pub(crate) fn to_camel_case(name: &LitStr) -> syn::Result { let span = name.span(); let name = name.value(); if &name[..2] != "m." { - panic!("well-known matrix events have to start with `m.` found `{}`", name,) + return Err(syn::Error::new( + span, + format!("well-known matrix events have to start with `m.` found `{}`", name), + )); } let s = name[2..] .split(&['.', '_'] as &[char]) .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..]) .collect::(); - Ident::new(&s, span) + + Ok(Ident::new(&s, span)) } -/// Custom keywords for the `event_content_content_enum!` macro +/// Custom keywords for the `event_enum!` macro mod kw { syn::custom_keyword!(name); syn::custom_keyword!(events); } -/// The entire `event_content_content_enum!` macro structure directly as it appears in the source code.. -pub struct ContentEnumInput { +/// The entire `event_enum!` macro structure directly as it appears in the source code. +pub struct EventEnumInput { /// Outer attributes on the field, such as a docstring. pub attrs: Vec, /// The name of the event. pub name: Ident, - /// An array of valid matrix event types. This will generate the variants of the event content type "name". + /// An array of valid matrix event types. This will generate the variants of the event type "name". /// There needs to be a corresponding variant in `ruma_events::EventType` for /// this event (converted to a valid Rust-style type name by stripping `m.`, replacing the /// remaining dots by underscores and then converting from snake_case to CamelCase). pub events: Vec, } -impl Parse for ContentEnumInput { +impl Parse for EventEnumInput { fn parse(input: ParseStream<'_>) -> parse::Result { let attrs = input.call(Attribute::parse_outer)?; // name field input.parse::()?; input.parse::()?; - // the name of our content_enum enum + // the name of our event enum let name: Ident = input.parse()?; input.parse::()?; diff --git a/ruma-events-macros/src/lib.rs b/ruma-events-macros/src/lib.rs index 84b4a5e1..0e5b9866 100644 --- a/ruma-events-macros/src/lib.rs +++ b/ruma-events-macros/src/lib.rs @@ -11,26 +11,27 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; use self::{ - content_enum::{expand_content_enum, ContentEnumInput}, event::expand_event, event_content::{ expand_basic_event_content, expand_ephemeral_room_event_content, expand_event_content, expand_message_event_content, expand_room_event_content, expand_state_event_content, }, + event_enum::{expand_event_enum, EventEnumInput}, }; -mod content_enum; mod event; mod event_content; +mod event_enum; -/// Generates a content enum to represent the various Matrix event types. +/// Generates an enum to represent the various Matrix event types. /// /// This macro also implements the necessary traits for the type to serialize and deserialize /// itself. +// TODO more docs/example #[proc_macro] -pub fn event_content_enum(input: TokenStream) -> TokenStream { - let content_enum_input = syn::parse_macro_input!(input as ContentEnumInput); - expand_content_enum(content_enum_input).unwrap_or_else(|err| err.to_compile_error()).into() +pub fn event_enum(input: TokenStream) -> TokenStream { + let event_enum_input = syn::parse_macro_input!(input as EventEnumInput); + expand_event_enum(event_enum_input).unwrap_or_else(|err| err.to_compile_error()).into() } /// Generates an implementation of `ruma_events::EventContent`. diff --git a/ruma-events/src/enums.rs b/ruma-events/src/enums.rs index 333f8d65..2476cbe2 100644 --- a/ruma-events/src/enums.rs +++ b/ruma-events/src/enums.rs @@ -1,35 +1,32 @@ -use ruma_events_macros::event_content_enum; +use ruma_events_macros::event_enum; use serde::{ - de::{self, DeserializeOwned, Error as _}, - Deserialize, Serialize, + de::{self, Error}, + Serialize, }; use serde_json::value::RawValue as RawJsonValue; use crate::{ - event_kinds::{ - BasicEvent, EphemeralRoomEvent, MessageEvent, MessageEventStub, StateEvent, StateEventStub, - StrippedStateEventStub, ToDeviceEvent, - }, - presence::PresenceEvent, - room::redaction::{RedactionEvent, RedactionEventStub}, + event_kinds::{MessageEventStub, StateEventStub, StrippedStateEventStub}, + from_raw_json_value, EventDeHelper, }; -event_content_enum! { +event_enum! { /// Any basic event. - name: AnyBasicEventContent, + name: AnyBasicEvent, events: [ "m.direct", "m.dummy", "m.ignored_user_list", + "m.presence", "m.push_rules", "m.room_key", "m.tag", ] } -event_content_enum! { +event_enum! { /// Any ephemeral room event. - name: AnyEphemeralRoomEventContent, + name: AnyEphemeralRoomEvent, events: [ "m.fully_read", "m.receipt", @@ -37,24 +34,25 @@ event_content_enum! { ] } -event_content_enum! { +event_enum! { /// Any message event. - name: AnyMessageEventContent, + name: AnyMessageEvent, events: [ "m.call.answer", "m.call.invite", "m.call.hangup", "m.call.candidates", - "m.room.encrypted", "m.room.message", "m.room.message.feedback", + "m.room.redaction", "m.sticker", + ] } -event_content_enum! { +event_enum! { /// Any state event. - name: AnyStateEventContent, + name: AnyStateEvent, events: [ "m.room.aliases", "m.room.avatar", @@ -76,9 +74,9 @@ event_content_enum! { ] } -event_content_enum! { +event_enum! { /// Any to-device event. - name: AnyToDeviceEventContent, + name: AnyToDeviceEvent, events: [ "m.dummy", "m.room_key", @@ -94,21 +92,9 @@ event_content_enum! { ] } -/// Any basic event, one that has no (well-known) fields outside of `content`. -pub type AnyBasicEvent = BasicEvent; - -/// Any ephemeral room event. -pub type AnyEphemeralRoomEvent = EphemeralRoomEvent; - -/// Any message event. -pub type AnyMessageEvent = MessageEvent; - /// Any message event stub (message event without a `room_id`, as returned in `/sync` responses) pub type AnyMessageEventStub = MessageEventStub; -/// Any state event. -pub type AnyStateEvent = StateEvent; - /// Any state event stub (state event without a `room_id`, as returned in `/sync` responses) pub type AnyStateEventStub = StateEventStub; @@ -116,23 +102,16 @@ pub type AnyStateEventStub = StateEventStub; /// been invited to in `/sync` responses) pub type AnyStrippedStateEventStub = StrippedStateEventStub; -/// Any to-device event. -pub type AnyToDeviceEvent = ToDeviceEvent; - /// Any event. #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum AnyEvent { /// Any basic event. Basic(AnyBasicEvent), - /// `"m.presence"`, the only non-room event with a `sender` field. - Presence(PresenceEvent), /// Any ephemeral room event. Ephemeral(AnyEphemeralRoomEvent), /// Any message event. Message(AnyMessageEvent), - /// `"m.room.redaction"`, the only room event with a `redacts` field. - Redaction(RedactionEvent), /// Any state event. State(AnyStateEvent), } @@ -143,8 +122,6 @@ pub enum AnyEvent { pub enum AnyRoomEvent { /// Any message event. Message(AnyMessageEvent), - /// `"m.room.redaction"`, the only room event with a `redacts` field. - Redaction(RedactionEvent), /// Any state event. State(AnyStateEvent), } @@ -155,26 +132,10 @@ pub enum AnyRoomEvent { pub enum AnyRoomEventStub { /// Any message event stub Message(AnyMessageEventStub), - /// `"m.room.redaction"` stub - Redaction(RedactionEventStub), /// Any state event stub State(AnyStateEventStub), } -#[derive(Deserialize)] -struct EventDeHelper { - #[serde(rename = "type")] - ev_type: String, -} - -fn from_raw_json_value(val: &RawJsonValue) -> Result -where - T: DeserializeOwned, - E: de::Error, -{ - serde_json::from_str(val.get()).map_err(E::custom) -} - // FIXME `#[serde(untagged)]` deserialization fails for these enums which // is odd as we are doing basically the same thing here, investigate? impl<'de> de::Deserialize<'de> for AnyEvent { @@ -186,8 +147,6 @@ impl<'de> de::Deserialize<'de> for AnyEvent { let EventDeHelper { ev_type } = from_raw_json_value(&json)?; match ev_type.as_str() { - "m.room.redaction" => Ok(AnyEvent::Redaction(from_raw_json_value(&json)?)), - "m.presence" => Ok(AnyEvent::Presence(from_raw_json_value(&json)?)), ev_type if AnyBasicEventContent::is_compatible(ev_type) => { Ok(AnyEvent::Basic(from_raw_json_value(&json)?)) } @@ -214,7 +173,6 @@ impl<'de> de::Deserialize<'de> for AnyRoomEvent { let EventDeHelper { ev_type } = from_raw_json_value(&json)?; match ev_type.as_str() { - "m.room.redaction" => Ok(AnyRoomEvent::Redaction(from_raw_json_value(&json)?)), ev_type if AnyMessageEventContent::is_compatible(ev_type) => { Ok(AnyRoomEvent::Message(from_raw_json_value(&json)?)) } @@ -235,7 +193,6 @@ impl<'de> de::Deserialize<'de> for AnyRoomEventStub { let EventDeHelper { ev_type } = from_raw_json_value(&json)?; match ev_type.as_str() { - "m.room.redaction" => Ok(AnyRoomEventStub::Redaction(from_raw_json_value(&json)?)), ev_type if AnyMessageEventContent::is_compatible(ev_type) => { Ok(AnyRoomEventStub::Message(from_raw_json_value(&json)?)) } diff --git a/ruma-events/src/lib.rs b/ruma-events/src/lib.rs index 91810ed7..8d3fcec5 100644 --- a/ruma-events/src/lib.rs +++ b/ruma-events/src/lib.rs @@ -120,7 +120,7 @@ use std::fmt::Debug; use js_int::Int; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; use self::room::redaction::RedactionEvent; @@ -234,3 +234,21 @@ pub trait MessageEventContent: RoomEventContent {} /// Marker trait for the content of a state event. pub trait StateEventContent: RoomEventContent {} + +/// Helper struct to obtain the event type from a serde_json::value::RawValue. +#[doc(hidden)] +#[derive(Debug, Deserialize)] +pub struct EventDeHelper { + #[serde(rename = "type")] + pub ev_type: String, +} + +/// Helper function for serde_json::value::RawValue deserialization. +#[doc(hidden)] +pub fn from_raw_json_value(val: &RawJsonValue) -> Result +where + T: de::DeserializeOwned, + E: de::Error, +{ + serde_json::from_str(val.get()).map_err(E::custom) +} diff --git a/ruma-events/tests/enums.rs b/ruma-events/tests/enums.rs index 733c03ba..50ce88c3 100644 --- a/ruma-events/tests/enums.rs +++ b/ruma-events/tests/enums.rs @@ -10,8 +10,9 @@ use ruma_events::{ message::{MessageEventContent, TextMessageEventContent}, power_levels::PowerLevelsEventContent, }, - AnyEvent, AnyMessageEventContent, AnyRoomEvent, AnyRoomEventStub, AnyStateEventContent, - MessageEvent, MessageEventStub, StateEvent, StateEventStub, + AnyEvent, AnyMessageEvent, AnyMessageEventContent, AnyRoomEvent, AnyRoomEventStub, + AnyStateEvent, AnyStateEventContent, MessageEvent, MessageEventStub, StateEvent, + StateEventStub, }; fn message_event() -> JsonValue { @@ -175,14 +176,14 @@ fn message_room_event_deserialization() { assert_matches!( from_json_value::(json_data), Ok(AnyRoomEvent::Message( - MessageEvent { - content: AnyMessageEventContent::RoomMessage(MessageEventContent::Text(TextMessageEventContent { + AnyMessageEvent::RoomMessage(MessageEvent { + content: MessageEventContent::Text(TextMessageEventContent { body, formatted: Some(formatted), relates_to: None, - })), + }), .. - } + }) )) if body == "baba" && formatted.body == "baba" ); @@ -195,12 +196,12 @@ fn alias_room_event_deserialization() { assert_matches!( from_json_value::(json_data), Ok(AnyRoomEvent::State( - StateEvent { - content: AnyStateEventContent::RoomAliases(AliasesEventContent { + AnyStateEvent::RoomAliases(StateEvent { + content: AliasesEventContent { aliases, - }), + }, .. - } + }) )) if aliases == vec![ RoomAliasId::try_from("#somewhere:localhost").unwrap() ] ); @@ -213,14 +214,14 @@ fn message_event_deserialization() { assert_matches!( from_json_value::(json_data), Ok(AnyEvent::Message( - MessageEvent { - content: AnyMessageEventContent::RoomMessage(MessageEventContent::Text(TextMessageEventContent { + AnyMessageEvent::RoomMessage(MessageEvent { + content: MessageEventContent::Text(TextMessageEventContent { body, formatted: Some(formatted), relates_to: None, - })), + }), .. - } + }) )) if body == "baba" && formatted.body == "baba" ); @@ -233,12 +234,12 @@ fn alias_event_deserialization() { assert_matches!( from_json_value::(json_data), Ok(AnyEvent::State( - StateEvent { - content: AnyStateEventContent::RoomAliases(AliasesEventContent { + AnyStateEvent::RoomAliases(StateEvent { + content: AliasesEventContent { aliases, - }), + }, .. - } + }) )) if aliases == vec![ RoomAliasId::try_from("#somewhere:localhost").unwrap() ] ); diff --git a/ruma-events/tests/event_enums.rs b/ruma-events/tests/event_enums.rs new file mode 100644 index 00000000..c1791016 --- /dev/null +++ b/ruma-events/tests/event_enums.rs @@ -0,0 +1,124 @@ +use std::{ + convert::TryFrom, + time::{Duration, UNIX_EPOCH}, +}; + +use js_int::UInt; +use matches::assert_matches; +use ruma_identifiers::{EventId, RoomId, UserId}; +use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; + +use ruma_events::{ + call::{answer::AnswerEventContent, SessionDescription, SessionDescriptionType}, + room::{ImageInfo, ThumbnailInfo}, + sticker::StickerEventContent, + AnyMessageEvent, MessageEvent, UnsignedData, +}; + +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.pass("tests/ui/07-enum-sanity-check.rs"); + t.compile_fail("tests/ui/08-enum-invalid-path.rs"); +} + +#[test] +fn deserialize_message_event() { + let json_data = json!({ + "content": { + "answer": { + "type": "answer", + "sdp": "Hello" + }, + "call_id": "foofoo", + "version": 1 + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 1, + "room_id": "!roomid:room.com", + "sender": "@carl:example.com", + "type": "m.call.answer" + }); + + assert_matches!( + from_json_value::(json_data) + .unwrap(), + AnyMessageEvent::CallAnswer(MessageEvent { + content: AnswerEventContent { + answer: SessionDescription { + session_type: SessionDescriptionType::Answer, + sdp, + }, + call_id, + version, + }, + event_id, + origin_server_ts, + room_id, + sender, + unsigned, + }) if sdp == "Hello" && call_id == "foofoo" && version == UInt::new(1).unwrap() + && event_id == EventId::try_from("$h29iv0s8:example.com").unwrap() + && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) + && room_id == RoomId::try_from("!roomid:room.com").unwrap() + && sender == UserId::try_from("@carl:example.com").unwrap() + && unsigned.is_empty() + ); +} + +#[test] +fn serialize_message_event() { + let aliases_event = AnyMessageEvent::Sticker(MessageEvent { + content: StickerEventContent { + body: "Hello".into(), + info: ImageInfo { + height: UInt::new(423), + width: UInt::new(1011), + mimetype: Some("image/png".into()), + size: UInt::new(84242), + thumbnail_info: Some(Box::new(ThumbnailInfo { + width: UInt::new(800), + height: UInt::new(334), + mimetype: Some("image/png".into()), + size: UInt::new(82595), + })), + thumbnail_url: Some("mxc://matrix.org".into()), + thumbnail_file: None, + }, + url: "http://www.matrix.org".into(), + }, + event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), + origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), + room_id: RoomId::try_from("!roomid: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": "Hello", + "info": { + "h": 423, + "mimetype": "image/png", + "size": 84242, + "thumbnail_info": { + "h": 334, + "mimetype": "image/png", + "size": 82595, + "w": 800 + }, + "thumbnail_url": "mxc://matrix.org", + "w": 1011 + }, + "url": "http://www.matrix.org" + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 1, + "room_id": "!roomid:room.com", + "sender": "@carl:example.com", + "type": "m.sticker", + }); + + assert_eq!(actual, expected); +} diff --git a/ruma-events/tests/state_event.rs b/ruma-events/tests/state_event.rs index a303fad7..e18db062 100644 --- a/ruma-events/tests/state_event.rs +++ b/ruma-events/tests/state_event.rs @@ -7,7 +7,8 @@ use js_int::UInt; use matches::assert_matches; use ruma_events::{ room::{aliases::AliasesEventContent, avatar::AvatarEventContent, ImageInfo, ThumbnailInfo}, - AnyStateEventContent, EventJson, StateEvent, StateEventStub, UnsignedData, + AnyRoomEvent, AnyStateEvent, AnyStateEventContent, EventJson, StateEvent, StateEventStub, + UnsignedData, }; use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; use serde_json::{ @@ -236,3 +237,48 @@ fn deserialize_avatar_without_prev_content() { && unsigned.is_empty() ); } + +#[test] +fn deserialize_member_event_with_top_level_membership_field() { + let json_data = json!({ + "content": { + "avatar_url": null, + "displayname": "example", + "membership": "join" + }, + "event_id": "$h29iv0s8:example.com", + "membership": "join", + "room_id": "!room:localhost", + "origin_server_ts": 1, + "sender": "@example:localhost", + "state_key": "@example:localhost", + "type": "m.room.member", + "unsigned": { + "age": 1, + "replaces_state": "$151800111315tsynI:localhost", + "prev_content": { + "avatar_url": null, + "displayname": "example", + "membership": "invite" + } + } + }); + + assert_matches!( + from_json_value::(json_data) + .unwrap(), + AnyRoomEvent::State( + AnyStateEvent::RoomMember(StateEvent { + content, + event_id, + origin_server_ts, + prev_content: None, + sender, + .. + } + )) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap() + && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) + && sender == UserId::try_from("@example:localhost").unwrap() + && content.displayname == Some("example".to_string()) + ); +} diff --git a/ruma-events/tests/ui/07-enum-sanity-check.rs b/ruma-events/tests/ui/07-enum-sanity-check.rs index 916d7570..633589ff 100644 --- a/ruma-events/tests/ui/07-enum-sanity-check.rs +++ b/ruma-events/tests/ui/07-enum-sanity-check.rs @@ -1,8 +1,8 @@ -use ruma_events_macros::event_content_enum; +use ruma_events_macros::event_enum; -event_content_enum! { +event_enum! { /// Any basic event. - name: AnyBasicEventContent, + name: AnyBasicEvent, events: [ "m.direct", "m.dummy", diff --git a/ruma-events/tests/ui/08-enum-invalid-path.rs b/ruma-events/tests/ui/08-enum-invalid-path.rs index 8d9a6bf1..410b7efc 100644 --- a/ruma-events/tests/ui/08-enum-invalid-path.rs +++ b/ruma-events/tests/ui/08-enum-invalid-path.rs @@ -1,13 +1,13 @@ -use ruma_events_macros::event_content_enum; +use ruma_events_macros::event_enum; -event_content_enum! { +event_enum! { name: InvalidEvent, events: [ "m.not.a.path", ] } -event_content_enum! { +event_enum! { name: InvalidEvent, events: [ "not.a.path", diff --git a/ruma-events/tests/ui/08-enum-invalid-path.stderr b/ruma-events/tests/ui/08-enum-invalid-path.stderr index fe3b34c6..48062907 100644 --- a/ruma-events/tests/ui/08-enum-invalid-path.stderr +++ b/ruma-events/tests/ui/08-enum-invalid-path.stderr @@ -1,15 +1,8 @@ -error: proc macro panicked - --> $DIR/08-enum-invalid-path.rs:10:1 +error: well-known matrix events have to start with `m.` found `not.a.path` + --> $DIR/08-enum-invalid-path.rs:13:9 | -10 | / event_content_enum! { -11 | | name: InvalidEvent, -12 | | events: [ -13 | | "not.a.path", -14 | | ] -15 | | } - | |_^ - | - = help: message: well-known matrix events have to start with `m.` found `not.a.path` +13 | "not.a.path", + | ^^^^^^^^^^^^ error[E0433]: failed to resolve: could not find `not` in `ruma_events` --> $DIR/08-enum-invalid-path.rs:6:9