Implement PresenceEvent and EphemeralEvent

* Add derive for PresenceEventContent and create struct AnyPresenceEventContent
since there is only one content type
* Add derive for Ephemeral event and create enum AnyEphemeralEventContent, convert
receipt and typing to use derive(EphemeralEventContent) over ruma_api!
This commit is contained in:
Ragotzy.devin 2020-06-07 16:56:43 -04:00 committed by GitHub
parent ef3a6787a0
commit 800fba7c32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 362 additions and 150 deletions

View File

@ -16,6 +16,9 @@ fn marker_traits(ident: &Ident) -> TokenStream {
impl ::ruma_events::RoomEventContent for #ident {}
impl ::ruma_events::MessageEventContent for #ident {}
},
"AnyEphemeralRoomEventContent" => quote! {
impl ::ruma_events::EphemeralRoomEventContent for #ident {}
},
_ => TokenStream::new(),
}
}

View File

@ -7,6 +7,9 @@ use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident};
/// Derive `Event` macro code generation.
pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = &input.ident;
let (impl_gen, ty_gen, where_clause) = input.generics.split_for_impl();
let is_presence_event = ident == "PresenceEvent";
let fields = if let Data::Struct(DataStruct { fields, .. }) = input.data.clone() {
if let Fields::Named(FieldsNamed { named, .. }) = fields {
if !named.iter().any(|f| f.ident.as_ref().unwrap() == "content") {
@ -30,8 +33,6 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
));
};
let content_trait = Ident::new(&format!("{}Content", ident), input.ident.span());
let serialize_fields = fields
.iter()
.map(|field| {
@ -66,20 +67,25 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
})
.collect::<Vec<_>>();
let event_ty = if is_presence_event {
quote! {
"m.presence";
}
} else {
quote! { self.content.event_type(); }
};
let serialize_impl = quote! {
impl<C> ::serde::ser::Serialize for #ident<C>
where
C: ::ruma_events::#content_trait,
{
impl #impl_gen ::serde::ser::Serialize for #ident #ty_gen #where_clause {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::ser::Serializer,
{
use ::serde::ser::SerializeStruct as _;
let event_type = self.content.event_type();
let event_type = #event_ty;
let mut state = serializer.serialize_struct("StateEvent", 7)?;
let mut state = serializer.serialize_struct(stringify!(#ident), 7)?;
state.serialize_field("type", event_type)?;
#( #serialize_fields )*
@ -88,7 +94,7 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
}
};
let deserialize_impl = expand_deserialize_event(&input, fields)?;
let deserialize_impl = expand_deserialize_event(is_presence_event, input, fields)?;
Ok(quote! {
#serialize_impl
@ -97,9 +103,14 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
})
}
fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Result<TokenStream> {
fn expand_deserialize_event(
is_presence_event: bool,
input: DeriveInput,
fields: Vec<Field>,
) -> syn::Result<TokenStream> {
let ident = &input.ident;
let content_ident = Ident::new(&format!("{}Content", ident), input.ident.span());
let (impl_generics, ty_gen, where_clause) = input.generics.split_for_impl();
let enum_variants = fields
.iter()
@ -115,7 +126,11 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
let name = field.ident.as_ref().unwrap();
let ty = &field.ty;
if name == "content" || name == "prev_content" {
if is_presence_event {
quote! { #content_ident }
} else {
quote! { Box<::serde_json::value::RawValue> }
}
} else if name == "origin_server_ts" {
quote! { ::js_int::UInt }
} else {
@ -129,10 +144,16 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
.map(|field| {
let name = field.ident.as_ref().unwrap();
if name == "content" {
if is_presence_event {
quote! {
let content = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
}
} else {
quote! {
let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
let content = C::from_parts(&event_type, json).map_err(A::Error::custom)?;
}
}
} else if name == "prev_content" {
quote! {
let prev_content = if let Some(json) = prev_content {
@ -164,11 +185,20 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
let field_names = fields.iter().flat_map(|f| &f.ident).collect::<Vec<_>>();
let deserialize_impl_gen = if is_presence_event {
quote! { <'de> }
} else {
let gen = &input.generics.params;
quote! { <'de, #gen> }
};
let deserialize_phantom_type = if is_presence_event {
quote! {}
} else {
quote! { ::std::marker::PhantomData }
};
Ok(quote! {
impl<'de, C> ::serde::de::Deserialize<'de> for #ident<C>
where
C: ::ruma_events::#content_ident,
{
impl #deserialize_impl_gen ::serde::de::Deserialize<'de> for #ident #ty_gen #where_clause {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
@ -183,13 +213,10 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
/// Visits the fields of an event struct to handle deserialization of
/// the `content` and `prev_content` fields.
struct EventVisitor<C>(::std::marker::PhantomData<C>);
struct EventVisitor #impl_generics (#deserialize_phantom_type #ty_gen);
impl<'de, C> ::serde::de::Visitor<'de> for EventVisitor<C>
where
C: ::ruma_events::#content_ident,
{
type Value = #ident<C>;
impl #deserialize_impl_gen ::serde::de::Visitor<'de> for EventVisitor #ty_gen #where_clause {
type Value = #ident #ty_gen;
fn expecting(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(formatter, "struct implementing {}", stringify!(#content_ident))
@ -232,7 +259,7 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
}
}
deserializer.deserialize_map(EventVisitor(::std::marker::PhantomData))
deserializer.deserialize_map(EventVisitor(#deserialize_phantom_type))
}
}
})

View File

@ -23,10 +23,7 @@ impl Parse for EventMeta {
}
}
/// Create a `RoomEventContent` implementation for a struct.
///
/// This is used internally for code sharing as `RoomEventContent` is not derivable.
fn expand_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
fn expand_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = &input.ident;
let event_type_attr = input
@ -47,7 +44,7 @@ fn expand_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
lit
};
let event_content_impl = quote! {
Ok(quote! {
impl ::ruma_events::EventContent for #ident {
fn event_type(&self) -> &str {
#event_type
@ -64,7 +61,15 @@ fn expand_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
::serde_json::from_str(content.get()).map_err(|e| e.to_string())
}
}
};
})
}
/// Create a `RoomEventContent` implementation for a struct.
///
/// This is used internally for code sharing as `RoomEventContent` is not derivable.
fn expand_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input)?;
Ok(quote! {
#event_content_impl
@ -85,7 +90,7 @@ pub fn expand_message_event_content(input: DeriveInput) -> syn::Result<TokenStre
})
}
/// Create a `MessageEventContent` implementation for a struct
/// Create a `StateEventContent` implementation for a struct
pub fn expand_state_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let room_ev_content = expand_room_event_content(input)?;
@ -96,3 +101,27 @@ pub fn expand_state_event_content(input: DeriveInput) -> syn::Result<TokenStream
impl ::ruma_events::StateEventContent for #ident { }
})
}
/// Create a `PresenceEventContent` implementation for a struct
pub fn expand_presence_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::PresenceEventContent for #ident { }
})
}
/// Create a `EphemeralRoomEventContent` implementation for a struct
pub fn expand_ephemeral_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::EphemeralRoomEventContent for #ident { }
})
}

View File

@ -17,7 +17,10 @@ use syn::{parse_macro_input, DeriveInput};
use self::{
content_enum::{expand_content_enum, parse::ContentEnumInput},
event::expand_event,
event_content::{expand_message_event_content, expand_state_event_content},
event_content::{
expand_ephemeral_event_content, expand_message_event_content,
expand_presence_event_content, expand_state_event_content,
},
gen::RumaEvent,
parse::RumaEventInput,
};
@ -152,6 +155,24 @@ pub fn derive_state_event_content(input: TokenStream) -> TokenStream {
.into()
}
/// Generates an implementation of `ruma_events::PresenceEventContent` and it's super traits.
#[proc_macro_derive(PresenceEventContent, attributes(ruma_event))]
pub fn derive_presence_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_presence_event_content(input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
/// Generates an implementation of `ruma_events::EphemeralRoomEventContent` and it's super traits.
#[proc_macro_derive(EphemeralRoomEventContent, attributes(ruma_event))]
pub fn derive_ephemeral_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_ephemeral_event_content(input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
/// Generates implementations needed to serialize and deserialize Matrix events.
#[proc_macro_derive(Event, attributes(ruma_event))]
pub fn derive_state_event(input: TokenStream) -> TokenStream {

View File

@ -34,3 +34,9 @@ event_content_enum! {
"m.room.topic",
]
}
event_content_enum! {
/// An ephemeral room event.
name: AnyEphemeralRoomEventContent,
events: [ "m.typing", "m.receipt" ]
}

View File

@ -1,18 +1,11 @@
use std::{
convert::TryFrom,
time::{SystemTime, UNIX_EPOCH},
};
use std::{convert::TryFrom, time::SystemTime};
use js_int::UInt;
use ruma_events_macros::Event;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde::{
ser::{Error, SerializeStruct},
Serialize, Serializer,
};
use serde::ser::Error;
use crate::{
BasicEventContent, MessageEventContent, RoomEventContent, StateEventContent,
BasicEventContent, EphemeralRoomEventContent, MessageEventContent, StateEventContent,
ToDeviceEventContent, UnsignedData,
};
@ -22,6 +15,16 @@ pub struct BasicEvent<C: BasicEventContent> {
pub content: C,
}
/// Ephemeral room event.
#[derive(Clone, Debug, Event)]
pub struct EphemeralRoomEvent<C: EphemeralRoomEventContent> {
/// Data specific to the event type.
pub content: C,
/// The ID of the room associated with this event.
pub room_id: RoomId,
}
/// Message event.
#[derive(Clone, Debug, Event)]
pub struct MessageEvent<C: MessageEventContent> {

View File

@ -151,7 +151,7 @@ pub mod forwarded_room_key;
pub mod fully_read;
// pub mod ignored_user_list;
pub mod key;
// pub mod presence;
pub mod presence;
// pub mod push_rules;
pub mod receipt;
pub mod room;
@ -165,10 +165,10 @@ pub mod typing;
pub use self::{
algorithm::Algorithm,
content_enums::{AnyMessageEventContent, AnyStateEventContent},
content_enums::{AnyEphemeralRoomEventContent, AnyMessageEventContent, AnyStateEventContent},
error::{FromStrError, InvalidEvent, InvalidInput},
event_enums::AnyStateEvent,
event_kinds::{MessageEvent, StateEvent},
event_kinds::{EphemeralRoomEvent, MessageEvent, StateEvent},
event_type::EventType,
json::EventJson,
};
@ -217,6 +217,9 @@ pub trait EventContent: Sized + Serialize {
fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, String>;
}
/// Marker trait for the content of an ephemeral room event.
pub trait EphemeralRoomEventContent: EventContent {}
/// Marker trait for the content of a basic event.
pub trait BasicEventContent: EventContent {}

View File

@ -1,19 +1,30 @@
//! Types for the *m.presence* event.
//! A presence event is represented by a parameterized struct.
//!
//! There is only one type that will satisfy the bounds of `PresenceEventContent`
//! as this event has only one possible content value according to Matrix spec.
use js_int::UInt;
use ruma_events_macros::ruma_event;
pub use ruma_common::presence::PresenceState;
use ruma_events_macros::Event;
use ruma_identifiers::UserId;
use serde::{Deserialize, Serialize};
ruma_event! {
/// Informs the client of a user's presence state change.
PresenceEvent {
kind: Event,
event_type: "m.presence",
fields: {
/// The unique identifier for the user associated with this event.
/// Presence event.
#[derive(Clone, Debug, Event)]
pub struct PresenceEvent {
/// Data specific to the event type.
pub content: PresenceEventContent,
/// Contains the fully-qualified ID of the user who sent this event.
pub sender: UserId,
},
content: {
}
/// Informs the room of members presence.
///
/// This is the only event content a `PresenceEvent` can contain as it's
/// `content` field.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PresenceEventContent {
/// The current avatar URL for this user.
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar_url: Option<String>,
@ -36,12 +47,8 @@ ruma_event! {
/// An optional description to accompany the presence.
#[serde(skip_serializing_if = "Option::is_none")]
pub status_msg: Option<String>,
},
}
}
pub use ruma_common::presence::PresenceState;
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
@ -51,7 +58,7 @@ mod tests {
use ruma_identifiers::UserId;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{PresenceEventContent, PresenceState};
use super::{PresenceEvent, PresenceEventContent, PresenceState};
use crate::EventJson;
#[test]

View File

@ -2,30 +2,20 @@
use std::{collections::BTreeMap, time::SystemTime};
use ruma_events_macros::ruma_event;
use ruma_events_macros::EphemeralRoomEventContent;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde::{Deserialize, Serialize};
ruma_event! {
/// Informs the client of new receipts.
ReceiptEvent {
kind: Event,
event_type: "m.receipt",
fields: {
/// The unique identifier for the room associated with this event.
///
/// `None` if the room is known through other means (such as this even being part of an
/// event list scoped to a room in a `/sync` response)
pub room_id: Option<RoomId>,
},
content_type_alias: {
/// Informs the client who has read a message specified by it's event id.
#[derive(Clone, Debug, Deserialize, Serialize, EphemeralRoomEventContent)]
#[ruma_event(type = "m.receipt")]
#[serde(transparent)]
pub struct ReceiptEventContent {
/// The payload for `ReceiptEvent`.
///
/// A mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of
/// the event being acknowledged and *not* an ID for the receipt itself.
BTreeMap<EventId, Receipts>
},
}
pub receipts: BTreeMap<EventId, Receipts>,
}
/// A collection of receipts.

View File

@ -1,23 +1,13 @@
//! Types for the *m.typing* event.
use ruma_events_macros::ruma_event;
use ruma_events_macros::EphemeralRoomEventContent;
use ruma_identifiers::{RoomId, UserId};
use serde::{Deserialize, Serialize};
ruma_event! {
/// Informs the client of the list of users currently typing.
TypingEvent {
kind: Event,
event_type: "m.typing",
fields: {
/// The unique identifier for the room associated with this event.
///
/// `None` if the room is known through other means (such as this even being part of an
/// event list scoped to a room in a `/sync` response)
pub room_id: Option<RoomId>,
},
content: {
/// Informs the client who is currently typing in a given room.
#[derive(Clone, Debug, Deserialize, Serialize, EphemeralRoomEventContent)]
#[ruma_event(type = "m.typing")]
pub struct TypingEventContent {
/// The list of user IDs typing in this room, if any.
pub user_ids: Vec<UserId>,
},
}
}

133
tests/ephemeral_event.rs Normal file
View File

@ -0,0 +1,133 @@
use std::{
convert::TryFrom,
time::{Duration, UNIX_EPOCH},
};
use maplit::btreemap;
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::{
receipt::{Receipt, ReceiptEventContent, Receipts},
typing::TypingEventContent,
AnyEphemeralRoomEventContent, EphemeralRoomEvent, EventJson,
};
#[test]
fn ephemeral_serialize_typing() {
let aliases_event = EphemeralRoomEvent {
content: AnyEphemeralRoomEventContent::Typing(TypingEventContent {
user_ids: vec![UserId::try_from("@carl:example.com").unwrap()],
}),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
};
let actual = to_json_value(&aliases_event).unwrap();
let expected = json!({
"content": {
"user_ids": [ "@carl:example.com" ]
},
"room_id": "!roomid:room.com",
"type": "m.typing",
});
assert_eq!(actual, expected);
}
#[test]
fn deserialize_ephemeral_typing() {
let json_data = json!({
"content": {
"user_ids": [ "@carl:example.com" ]
},
"room_id": "!roomid:room.com",
"type": "m.typing"
});
assert_matches!(
from_json_value::<EventJson<EphemeralRoomEvent<AnyEphemeralRoomEventContent>>>(json_data)
.unwrap()
.deserialize()
.unwrap(),
EphemeralRoomEvent {
content: AnyEphemeralRoomEventContent::Typing(TypingEventContent {
user_ids,
}),
room_id,
} if user_ids[0] == UserId::try_from("@carl:example.com").unwrap()
&& room_id == RoomId::try_from("!roomid:room.com").unwrap()
);
}
#[test]
fn ephemeral_serialize_receipt() {
let event_id = EventId::try_from("$h29iv0s8:example.com").unwrap();
let user_id = UserId::try_from("@carl:example.com").unwrap();
let aliases_event = EphemeralRoomEvent {
content: AnyEphemeralRoomEventContent::Receipt(ReceiptEventContent {
receipts: btreemap! {
event_id => Receipts {
read: Some(btreemap! {
user_id => Receipt { ts: Some(UNIX_EPOCH + Duration::from_millis(1)) },
}),
},
},
}),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
};
let actual = to_json_value(&aliases_event).unwrap();
let expected = json!({
"content": {
"$h29iv0s8:example.com": {
"m.read": {
"@carl:example.com": { "ts": 1 }
}
}
},
"room_id": "!roomid:room.com",
"type": "m.receipt"
});
assert_eq!(actual, expected);
}
#[test]
fn deserialize_ephemeral_receipt() {
let event_id = EventId::try_from("$h29iv0s8:example.com").unwrap();
let user_id = UserId::try_from("@carl:example.com").unwrap();
let json_data = json!({
"content": {
"$h29iv0s8:example.com": {
"m.read": {
"@carl:example.com": { "ts": 1 }
}
}
},
"room_id": "!roomid:room.com",
"type": "m.receipt"
});
assert_matches!(
from_json_value::<EventJson<EphemeralRoomEvent<AnyEphemeralRoomEventContent>>>(json_data)
.unwrap()
.deserialize()
.unwrap(),
EphemeralRoomEvent {
content: AnyEphemeralRoomEventContent::Receipt(ReceiptEventContent {
receipts,
}),
room_id,
} if !receipts.is_empty() && receipts.contains_key(&event_id)
&& room_id == RoomId::try_from("!roomid:room.com").unwrap()
&& receipts
.get(&event_id)
.map(|r| r.read.as_ref().unwrap().get(&user_id).unwrap().clone())
.map(|r| r.ts)
.unwrap()
== Some(UNIX_EPOCH + Duration::from_millis(1))
);
}