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" { | ||||||
|                 quote! { Box<::serde_json::value::RawValue> } |                 if is_presence_event { | ||||||
|  |                     quote! { #content_ident } | ||||||
|  |                 } else { | ||||||
|  |                     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 { | ||||||
| @ -125,50 +140,65 @@ fn expand_deserialize_event(input: &DeriveInput, fields: Vec<Field>) -> syn::Res | |||||||
|         .collect::<Vec<_>>(); |         .collect::<Vec<_>>(); | ||||||
| 
 | 
 | ||||||
|     let ok_or_else_fields = fields |     let ok_or_else_fields = fields | ||||||
|         .iter() |     .iter() | ||||||
|         .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" { |  | ||||||
|                 quote! { |  | ||||||
|                     let prev_content = if let Some(json) = prev_content { |  | ||||||
|                         Some(C::from_parts(&event_type, json).map_err(A::Error::custom)?) |  | ||||||
|                     } else { |  | ||||||
|                         None |  | ||||||
|                     }; |  | ||||||
|                 } |  | ||||||
|             } else if name == "origin_server_ts" { |  | ||||||
|                 quote! { |  | ||||||
|                     let origin_server_ts = origin_server_ts |  | ||||||
|                         .map(|time| { |  | ||||||
|                             let t = time.into(); |  | ||||||
|                             ::std::time::UNIX_EPOCH + ::std::time::Duration::from_millis(t) |  | ||||||
|                         }) |  | ||||||
|                         .ok_or_else(|| ::serde::de::Error::missing_field("origin_server_ts"))?; |  | ||||||
|                 } |  | ||||||
|             } else if name == "unsigned" { |  | ||||||
|                 quote! { let unsigned = unsigned.unwrap_or_default(); } |  | ||||||
|             } else { |  | ||||||
|                 quote! { |  | ||||||
|                     let #name = #name.ok_or_else(|| { |  | ||||||
|                         ::serde::de::Error::missing_field(stringify!(#name)) |  | ||||||
|                     })?; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }) |         } else if name == "prev_content" { | ||||||
|         .collect::<Vec<_>>(); |             quote! { | ||||||
|  |                 let prev_content = if let Some(json) = prev_content { | ||||||
|  |                     Some(C::from_parts(&event_type, json).map_err(A::Error::custom)?) | ||||||
|  |                 } else { | ||||||
|  |                     None | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         } else if name == "origin_server_ts" { | ||||||
|  |             quote! { | ||||||
|  |                 let origin_server_ts = origin_server_ts | ||||||
|  |                     .map(|time| { | ||||||
|  |                         let t = time.into(); | ||||||
|  |                         ::std::time::UNIX_EPOCH + ::std::time::Duration::from_millis(t) | ||||||
|  |                     }) | ||||||
|  |                     .ok_or_else(|| ::serde::de::Error::missing_field("origin_server_ts"))?; | ||||||
|  |             } | ||||||
|  |         } else if name == "unsigned" { | ||||||
|  |             quote! { let unsigned = unsigned.unwrap_or_default(); } | ||||||
|  |         } else { | ||||||
|  |             quote! { | ||||||
|  |                 let #name = #name.ok_or_else(|| { | ||||||
|  |                     ::serde::de::Error::missing_field(stringify!(#name)) | ||||||
|  |                 })?; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     .collect::<Vec<_>>(); | ||||||
| 
 | 
 | ||||||
|     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,46 +1,53 @@ | |||||||
| //! 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.
 |  | ||||||
|             pub sender: UserId, |  | ||||||
|         }, |  | ||||||
|         content: { |  | ||||||
|             /// The current avatar URL for this user.
 |  | ||||||
|             #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|             pub avatar_url: Option<String>, |  | ||||||
| 
 | 
 | ||||||
|             /// Whether or not the user is currently active.
 |     /// Contains the fully-qualified ID of the user who sent this event.
 | ||||||
|             #[serde(skip_serializing_if = "Option::is_none")] |     pub sender: UserId, | ||||||
|             pub currently_active: Option<bool>, |  | ||||||
| 
 |  | ||||||
|             /// The current display name for this user.
 |  | ||||||
|             #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|             pub displayname: Option<String>, |  | ||||||
| 
 |  | ||||||
|             /// The last time since this user performed some action, in milliseconds.
 |  | ||||||
|             #[serde(skip_serializing_if = "Option::is_none")] |  | ||||||
|             pub last_active_ago: Option<UInt>, |  | ||||||
| 
 |  | ||||||
|             /// The presence state for this user.
 |  | ||||||
|             pub presence: PresenceState, |  | ||||||
| 
 |  | ||||||
|             /// 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; | /// 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>, | ||||||
|  | 
 | ||||||
|  |     /// Whether or not the user is currently active.
 | ||||||
|  |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|  |     pub currently_active: Option<bool>, | ||||||
|  | 
 | ||||||
|  |     /// The current display name for this user.
 | ||||||
|  |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|  |     pub displayname: Option<String>, | ||||||
|  | 
 | ||||||
|  |     /// The last time since this user performed some action, in milliseconds.
 | ||||||
|  |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|  |     pub last_active_ago: Option<UInt>, | ||||||
|  | 
 | ||||||
|  |     /// The presence state for this user.
 | ||||||
|  |     pub presence: PresenceState, | ||||||
|  | 
 | ||||||
|  |     /// An optional description to accompany the presence.
 | ||||||
|  |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|  |     pub status_msg: Option<String>, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #[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 payload for `ReceiptEvent`.
 | ||||||
|             /// The unique identifier for the room associated with this event.
 |     ///
 | ||||||
|             ///
 |     /// A mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of
 | ||||||
|             /// `None` if the room is known through other means (such as this even being part of an
 |     /// the event being acknowledged and *not* an ID for the receipt itself.
 | ||||||
|             /// event list scoped to a room in a `/sync` response)
 |     pub receipts: BTreeMap<EventId, Receipts>, | ||||||
|             pub room_id: Option<RoomId>, |  | ||||||
|         }, |  | ||||||
|         content_type_alias: { |  | ||||||
|             /// 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> |  | ||||||
|         }, |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// 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", |     /// The list of user IDs typing in this room, if any.
 | ||||||
|         fields: { |     pub user_ids: Vec<UserId>, | ||||||
|             /// 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.
 |  | ||||||
|             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