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:
parent
ef3a6787a0
commit
800fba7c32
@ -16,6 +16,9 @@ fn marker_traits(ident: &Ident) -> TokenStream {
|
|||||||
impl ::ruma_events::RoomEventContent for #ident {}
|
impl ::ruma_events::RoomEventContent for #ident {}
|
||||||
impl ::ruma_events::MessageEventContent for #ident {}
|
impl ::ruma_events::MessageEventContent for #ident {}
|
||||||
},
|
},
|
||||||
|
"AnyEphemeralRoomEventContent" => quote! {
|
||||||
|
impl ::ruma_events::EphemeralRoomEventContent for #ident {}
|
||||||
|
},
|
||||||
_ => TokenStream::new(),
|
_ => TokenStream::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident};
|
|||||||
/// Derive `Event` macro code generation.
|
/// Derive `Event` macro code generation.
|
||||||
pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
|
pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
|
||||||
let ident = &input.ident;
|
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() {
|
let fields = if let Data::Struct(DataStruct { fields, .. }) = input.data.clone() {
|
||||||
if let Fields::Named(FieldsNamed { named, .. }) = fields {
|
if let Fields::Named(FieldsNamed { named, .. }) = fields {
|
||||||
if !named.iter().any(|f| f.ident.as_ref().unwrap() == "content") {
|
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
|
let serialize_fields = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
@ -66,20 +67,25 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let event_ty = if is_presence_event {
|
||||||
|
quote! {
|
||||||
|
"m.presence";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! { self.content.event_type(); }
|
||||||
|
};
|
||||||
|
|
||||||
let serialize_impl = quote! {
|
let serialize_impl = quote! {
|
||||||
impl<C> ::serde::ser::Serialize for #ident<C>
|
impl #impl_gen ::serde::ser::Serialize for #ident #ty_gen #where_clause {
|
||||||
where
|
|
||||||
C: ::ruma_events::#content_trait,
|
|
||||||
{
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: ::serde::ser::Serializer,
|
S: ::serde::ser::Serializer,
|
||||||
{
|
{
|
||||||
use ::serde::ser::SerializeStruct as _;
|
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)?;
|
state.serialize_field("type", event_type)?;
|
||||||
#( #serialize_fields )*
|
#( #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! {
|
Ok(quote! {
|
||||||
#serialize_impl
|
#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 ident = &input.ident;
|
||||||
let content_ident = Ident::new(&format!("{}Content", ident), input.ident.span());
|
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
|
let enum_variants = fields
|
||||||
.iter()
|
.iter()
|
||||||
@ -115,7 +126,11 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
|
|||||||
let name = field.ident.as_ref().unwrap();
|
let name = field.ident.as_ref().unwrap();
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
if name == "content" || name == "prev_content" {
|
if name == "content" || name == "prev_content" {
|
||||||
|
if is_presence_event {
|
||||||
|
quote! { #content_ident }
|
||||||
|
} else {
|
||||||
quote! { Box<::serde_json::value::RawValue> }
|
quote! { Box<::serde_json::value::RawValue> }
|
||||||
|
}
|
||||||
} else if name == "origin_server_ts" {
|
} else if name == "origin_server_ts" {
|
||||||
quote! { ::js_int::UInt }
|
quote! { ::js_int::UInt }
|
||||||
} else {
|
} else {
|
||||||
@ -129,10 +144,16 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res
|
|||||||
.map(|field| {
|
.map(|field| {
|
||||||
let name = field.ident.as_ref().unwrap();
|
let name = field.ident.as_ref().unwrap();
|
||||||
if name == "content" {
|
if name == "content" {
|
||||||
|
if is_presence_event {
|
||||||
|
quote! {
|
||||||
|
let content = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
|
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)?;
|
let content = C::from_parts(&event_type, json).map_err(A::Error::custom)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if name == "prev_content" {
|
} else if name == "prev_content" {
|
||||||
quote! {
|
quote! {
|
||||||
let prev_content = if let Some(json) = prev_content {
|
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 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! {
|
Ok(quote! {
|
||||||
impl<'de, C> ::serde::de::Deserialize<'de> for #ident<C>
|
impl #deserialize_impl_gen ::serde::de::Deserialize<'de> for #ident #ty_gen #where_clause {
|
||||||
where
|
|
||||||
C: ::ruma_events::#content_ident,
|
|
||||||
{
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: ::serde::de::Deserializer<'de>,
|
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
|
/// Visits the fields of an event struct to handle deserialization of
|
||||||
/// the `content` and `prev_content` fields.
|
/// 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>
|
impl #deserialize_impl_gen ::serde::de::Visitor<'de> for EventVisitor #ty_gen #where_clause {
|
||||||
where
|
type Value = #ident #ty_gen;
|
||||||
C: ::ruma_events::#content_ident,
|
|
||||||
{
|
|
||||||
type Value = #ident<C>;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
fn expecting(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
write!(formatter, "struct implementing {}", stringify!(#content_ident))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -23,10 +23,7 @@ impl Parse for EventMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `RoomEventContent` implementation for a struct.
|
fn expand_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
|
||||||
///
|
|
||||||
/// 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;
|
let ident = &input.ident;
|
||||||
|
|
||||||
let event_type_attr = input
|
let event_type_attr = input
|
||||||
@ -47,7 +44,7 @@ fn expand_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
lit
|
lit
|
||||||
};
|
};
|
||||||
|
|
||||||
let event_content_impl = quote! {
|
Ok(quote! {
|
||||||
impl ::ruma_events::EventContent for #ident {
|
impl ::ruma_events::EventContent for #ident {
|
||||||
fn event_type(&self) -> &str {
|
fn event_type(&self) -> &str {
|
||||||
#event_type
|
#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())
|
::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! {
|
Ok(quote! {
|
||||||
#event_content_impl
|
#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> {
|
pub fn expand_state_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
|
||||||
let ident = input.ident.clone();
|
let ident = input.ident.clone();
|
||||||
let room_ev_content = expand_room_event_content(input)?;
|
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 { }
|
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 { }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -17,7 +17,10 @@ use syn::{parse_macro_input, DeriveInput};
|
|||||||
use self::{
|
use self::{
|
||||||
content_enum::{expand_content_enum, parse::ContentEnumInput},
|
content_enum::{expand_content_enum, parse::ContentEnumInput},
|
||||||
event::expand_event,
|
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,
|
gen::RumaEvent,
|
||||||
parse::RumaEventInput,
|
parse::RumaEventInput,
|
||||||
};
|
};
|
||||||
@ -152,6 +155,24 @@ pub fn derive_state_event_content(input: TokenStream) -> TokenStream {
|
|||||||
.into()
|
.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.
|
/// Generates implementations needed to serialize and deserialize Matrix events.
|
||||||
#[proc_macro_derive(Event, attributes(ruma_event))]
|
#[proc_macro_derive(Event, attributes(ruma_event))]
|
||||||
pub fn derive_state_event(input: TokenStream) -> TokenStream {
|
pub fn derive_state_event(input: TokenStream) -> TokenStream {
|
||||||
|
@ -34,3 +34,9 @@ event_content_enum! {
|
|||||||
"m.room.topic",
|
"m.room.topic",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event_content_enum! {
|
||||||
|
/// An ephemeral room event.
|
||||||
|
name: AnyEphemeralRoomEventContent,
|
||||||
|
events: [ "m.typing", "m.receipt" ]
|
||||||
|
}
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
use std::{
|
use std::{convert::TryFrom, time::SystemTime};
|
||||||
convert::TryFrom,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
use js_int::UInt;
|
|
||||||
use ruma_events_macros::Event;
|
use ruma_events_macros::Event;
|
||||||
use ruma_identifiers::{EventId, RoomId, UserId};
|
use ruma_identifiers::{EventId, RoomId, UserId};
|
||||||
use serde::{
|
use serde::ser::Error;
|
||||||
ser::{Error, SerializeStruct},
|
|
||||||
Serialize, Serializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BasicEventContent, MessageEventContent, RoomEventContent, StateEventContent,
|
BasicEventContent, EphemeralRoomEventContent, MessageEventContent, StateEventContent,
|
||||||
ToDeviceEventContent, UnsignedData,
|
ToDeviceEventContent, UnsignedData,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,6 +15,16 @@ pub struct BasicEvent<C: BasicEventContent> {
|
|||||||
pub content: C,
|
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.
|
/// Message event.
|
||||||
#[derive(Clone, Debug, Event)]
|
#[derive(Clone, Debug, Event)]
|
||||||
pub struct MessageEvent<C: MessageEventContent> {
|
pub struct MessageEvent<C: MessageEventContent> {
|
||||||
|
@ -151,7 +151,7 @@ pub mod forwarded_room_key;
|
|||||||
pub mod fully_read;
|
pub mod fully_read;
|
||||||
// pub mod ignored_user_list;
|
// pub mod ignored_user_list;
|
||||||
pub mod key;
|
pub mod key;
|
||||||
// pub mod presence;
|
pub mod presence;
|
||||||
// pub mod push_rules;
|
// pub mod push_rules;
|
||||||
pub mod receipt;
|
pub mod receipt;
|
||||||
pub mod room;
|
pub mod room;
|
||||||
@ -165,10 +165,10 @@ pub mod typing;
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
algorithm::Algorithm,
|
algorithm::Algorithm,
|
||||||
content_enums::{AnyMessageEventContent, AnyStateEventContent},
|
content_enums::{AnyEphemeralRoomEventContent, AnyMessageEventContent, AnyStateEventContent},
|
||||||
error::{FromStrError, InvalidEvent, InvalidInput},
|
error::{FromStrError, InvalidEvent, InvalidInput},
|
||||||
event_enums::AnyStateEvent,
|
event_enums::AnyStateEvent,
|
||||||
event_kinds::{MessageEvent, StateEvent},
|
event_kinds::{EphemeralRoomEvent, MessageEvent, StateEvent},
|
||||||
event_type::EventType,
|
event_type::EventType,
|
||||||
json::EventJson,
|
json::EventJson,
|
||||||
};
|
};
|
||||||
@ -217,6 +217,9 @@ pub trait EventContent: Sized + Serialize {
|
|||||||
fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, String>;
|
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.
|
/// Marker trait for the content of a basic event.
|
||||||
pub trait BasicEventContent: EventContent {}
|
pub trait BasicEventContent: EventContent {}
|
||||||
|
|
||||||
|
@ -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 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 ruma_identifiers::UserId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
ruma_event! {
|
/// Presence event.
|
||||||
/// Informs the client of a user's presence state change.
|
#[derive(Clone, Debug, Event)]
|
||||||
PresenceEvent {
|
pub struct PresenceEvent {
|
||||||
kind: Event,
|
/// Data specific to the event type.
|
||||||
event_type: "m.presence",
|
pub content: PresenceEventContent,
|
||||||
fields: {
|
|
||||||
/// The unique identifier for the user associated with this event.
|
/// Contains the fully-qualified ID of the user who sent this event.
|
||||||
pub sender: UserId,
|
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.
|
/// The current avatar URL for this user.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub avatar_url: Option<String>,
|
pub avatar_url: Option<String>,
|
||||||
@ -36,11 +47,7 @@ ruma_event! {
|
|||||||
/// An optional description to accompany the presence.
|
/// An optional description to accompany the presence.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub status_msg: Option<String>,
|
pub status_msg: Option<String>,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub use ruma_common::presence::PresenceState;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@ -51,7 +58,7 @@ mod tests {
|
|||||||
use ruma_identifiers::UserId;
|
use ruma_identifiers::UserId;
|
||||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
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;
|
use crate::EventJson;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2,30 +2,20 @@
|
|||||||
|
|
||||||
use std::{collections::BTreeMap, time::SystemTime};
|
use std::{collections::BTreeMap, time::SystemTime};
|
||||||
|
|
||||||
use ruma_events_macros::ruma_event;
|
use ruma_events_macros::EphemeralRoomEventContent;
|
||||||
use ruma_identifiers::{EventId, RoomId, UserId};
|
use ruma_identifiers::{EventId, RoomId, UserId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
ruma_event! {
|
/// Informs the client who has read a message specified by it's event id.
|
||||||
/// Informs the client of new receipts.
|
#[derive(Clone, Debug, Deserialize, Serialize, EphemeralRoomEventContent)]
|
||||||
ReceiptEvent {
|
#[ruma_event(type = "m.receipt")]
|
||||||
kind: Event,
|
#[serde(transparent)]
|
||||||
event_type: "m.receipt",
|
pub struct ReceiptEventContent {
|
||||||
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: {
|
|
||||||
/// The payload for `ReceiptEvent`.
|
/// 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
|
/// 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.
|
/// the event being acknowledged and *not* an ID for the receipt itself.
|
||||||
BTreeMap<EventId, Receipts>
|
pub receipts: BTreeMap<EventId, Receipts>,
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of receipts.
|
/// A collection of receipts.
|
||||||
|
@ -1,23 +1,13 @@
|
|||||||
//! Types for the *m.typing* event.
|
//! Types for the *m.typing* event.
|
||||||
|
|
||||||
use ruma_events_macros::ruma_event;
|
use ruma_events_macros::EphemeralRoomEventContent;
|
||||||
use ruma_identifiers::{RoomId, UserId};
|
use ruma_identifiers::{RoomId, UserId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
ruma_event! {
|
/// Informs the client who is currently typing in a given room.
|
||||||
/// Informs the client of the list of users currently typing.
|
#[derive(Clone, Debug, Deserialize, Serialize, EphemeralRoomEventContent)]
|
||||||
TypingEvent {
|
#[ruma_event(type = "m.typing")]
|
||||||
kind: Event,
|
pub struct TypingEventContent {
|
||||||
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: {
|
|
||||||
/// The list of user IDs typing in this room, if any.
|
/// The list of user IDs typing in this room, if any.
|
||||||
pub user_ids: Vec<UserId>,
|
pub user_ids: Vec<UserId>,
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
133
tests/ephemeral_event.rs
Normal file
133
tests/ephemeral_event.rs
Normal 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))
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user