Add into_full_event and From<> impls to convert between sync and full events
This commit is contained in:
		
							parent
							
								
									3329dc7345
								
							
						
					
					
						commit
						4be63127f7
					
				| @ -4,13 +4,13 @@ use proc_macro2::{Span, TokenStream}; | ||||
| use quote::quote; | ||||
| use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident}; | ||||
| 
 | ||||
| use crate::event_parse::{to_kind_variation, EventKindVariation}; | ||||
| use crate::event_parse::{to_kind_variation, EventKind, EventKindVariation}; | ||||
| 
 | ||||
| /// Derive `Event` macro code generation.
 | ||||
| pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> { | ||||
|     let ident = &input.ident; | ||||
| 
 | ||||
|     let (_kind, var) = to_kind_variation(ident).ok_or_else(|| { | ||||
|     let (kind, var) = to_kind_variation(ident).ok_or_else(|| { | ||||
|         syn::Error::new(Span::call_site(), "not a valid ruma event struct identifier") | ||||
|     })?; | ||||
| 
 | ||||
| @ -99,9 +99,13 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> { | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     let deserialize_impl = expand_deserialize_event(input, &var, fields, is_generic)?; | ||||
|     let deserialize_impl = expand_deserialize_event(&input, &var, &fields, is_generic)?; | ||||
| 
 | ||||
|     let conversion_impl = expand_from_into(&input, &kind, &var, &fields); | ||||
| 
 | ||||
|     Ok(quote! { | ||||
|         #conversion_impl | ||||
| 
 | ||||
|         #serialize_impl | ||||
| 
 | ||||
|         #deserialize_impl | ||||
| @ -109,9 +113,9 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> { | ||||
| } | ||||
| 
 | ||||
| fn expand_deserialize_event( | ||||
|     input: DeriveInput, | ||||
|     input: &DeriveInput, | ||||
|     var: &EventKindVariation, | ||||
|     fields: Vec<Field>, | ||||
|     fields: &[Field], | ||||
|     is_generic: bool, | ||||
| ) -> syn::Result<TokenStream> { | ||||
|     let ident = &input.ident; | ||||
| @ -309,6 +313,58 @@ fn expand_deserialize_event( | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| fn expand_from_into( | ||||
|     input: &DeriveInput, | ||||
|     kind: &EventKind, | ||||
|     var: &EventKindVariation, | ||||
|     fields: &[Field], | ||||
| ) -> Option<TokenStream> { | ||||
|     let ident = &input.ident; | ||||
| 
 | ||||
|     let (impl_generics, ty_gen, where_clause) = input.generics.split_for_impl(); | ||||
| 
 | ||||
|     let fields = fields.iter().flat_map(|f| &f.ident).collect::<Vec<_>>(); | ||||
| 
 | ||||
|     let fields_without_unsigned = | ||||
|         fields.iter().filter(|id| id.to_string().as_str() != "unsigned").collect::<Vec<_>>(); | ||||
| 
 | ||||
|     let (into, into_full_event) = if var.is_redacted() { | ||||
|         (quote! { unsigned: unsigned.into(), }, quote! { unsigned: unsigned.into_full(room_id), }) | ||||
|     } else if kind == &EventKind::Ephemeral { | ||||
|         (TokenStream::new(), TokenStream::new()) | ||||
|     } else { | ||||
|         (quote! { unsigned, }, quote! { unsigned, }) | ||||
|     }; | ||||
| 
 | ||||
|     if let EventKindVariation::Sync | EventKindVariation::RedactedSync = var { | ||||
|         let full_struct = kind.to_event_ident(&var.to_full_variation()); | ||||
|         Some(quote! { | ||||
|             impl #impl_generics From<#full_struct #ty_gen> for #ident #ty_gen #where_clause { | ||||
|                 fn from(event: #full_struct #ty_gen) -> Self { | ||||
|                     let #full_struct { | ||||
|                         #( #fields, )* .. | ||||
|                     } = event; | ||||
|                     Self { #( #fields_without_unsigned, )* #into } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             impl #impl_generics #ident #ty_gen #where_clause { | ||||
|                 /// Convert this sync event into a full event, one with a room_id field.
 | ||||
|                 pub fn into_full_event(self, room_id: ::ruma_identifiers::RoomId) -> #full_struct #ty_gen { | ||||
|                     let Self { #( #fields, )* } = self; | ||||
|                     #full_struct { | ||||
|                         #( #fields_without_unsigned, )* | ||||
|                         room_id: room_id.clone(), | ||||
|                         #into_full_event | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     } else { | ||||
|         None | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// CamelCase's a field ident like "foo_bar" to "FooBar".
 | ||||
| fn to_camel_case(name: &Ident) -> Ident { | ||||
|     let span = name.span(); | ||||
|  | ||||
| @ -122,6 +122,8 @@ fn expand_any_with_deser( | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     let event_enum_to_from_sync = expand_conversion_impl(kind, var, &variants); | ||||
| 
 | ||||
|     let redacted_enum = expand_redacted_enum(kind, var); | ||||
| 
 | ||||
|     let field_accessor_impl = accessor_methods(kind, var, &variants); | ||||
| @ -131,6 +133,8 @@ fn expand_any_with_deser( | ||||
|     Some(quote! { | ||||
|         #any_enum | ||||
| 
 | ||||
|         #event_enum_to_from_sync | ||||
| 
 | ||||
|         #field_accessor_impl | ||||
| 
 | ||||
|         #redact_impl | ||||
| @ -141,6 +145,104 @@ fn expand_any_with_deser( | ||||
|     }) | ||||
| } | ||||
| 
 | ||||
| fn expand_conversion_impl( | ||||
|     kind: &EventKind, | ||||
|     var: &EventKindVariation, | ||||
|     variants: &[Ident], | ||||
| ) -> Option<TokenStream> { | ||||
|     let ident = kind.to_event_enum_ident(var)?; | ||||
|     let variants = &variants | ||||
|         .iter() | ||||
|         .filter(|id| { | ||||
|             // We filter this variant out only for non redacted events.
 | ||||
|             // The type of the struct held in the enum variant is different in this case
 | ||||
|             // so we construct the variant manually.
 | ||||
|             !(id.to_string().as_str() == "RoomRedaction" | ||||
|                 && matches!(var, EventKindVariation::Full | EventKindVariation::Sync)) | ||||
|         }) | ||||
|         .collect::<Vec<_>>(); | ||||
| 
 | ||||
|     match var { | ||||
|         EventKindVariation::Full | EventKindVariation::Redacted => { | ||||
|             // the opposite event variation full -> sync, redacted -> redacted sync
 | ||||
|             let variation = if var == &EventKindVariation::Full { | ||||
|                 EventKindVariation::Sync | ||||
|             } else { | ||||
|                 EventKindVariation::RedactedSync | ||||
|             }; | ||||
| 
 | ||||
|             let sync = kind.to_event_enum_ident(&variation)?; | ||||
|             let sync_struct = kind.to_event_ident(&variation)?; | ||||
| 
 | ||||
|             let redaction = if let (EventKind::Message, EventKindVariation::Full) = (kind, var) { | ||||
|                 quote! { | ||||
|                     #ident::RoomRedaction(event) => { | ||||
|                         Self::RoomRedaction(::ruma_events::room::redaction::SyncRedactionEvent::from(event)) | ||||
|                     }, | ||||
|                 } | ||||
|             } else { | ||||
|                 TokenStream::new() | ||||
|             }; | ||||
| 
 | ||||
|             Some(quote! { | ||||
|                 impl From<#ident> for #sync { | ||||
|                     fn from(event: #ident) -> Self { | ||||
|                         match event { | ||||
|                             #( | ||||
|                                 #ident::#variants(event) => { | ||||
|                                     Self::#variants(::ruma_events::#sync_struct::from(event)) | ||||
|                                 }, | ||||
|                             )* | ||||
|                             #redaction | ||||
|                             #ident::Custom(event) => { | ||||
|                                 Self::Custom(::ruma_events::#sync_struct::from(event)) | ||||
|                             }, | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|         EventKindVariation::Sync | EventKindVariation::RedactedSync => { | ||||
|             let variation = if var == &EventKindVariation::Sync { | ||||
|                 EventKindVariation::Full | ||||
|             } else { | ||||
|                 EventKindVariation::Redacted | ||||
|             }; | ||||
|             let full = kind.to_event_enum_ident(&variation)?; | ||||
| 
 | ||||
|             let redaction = if let (EventKind::Message, EventKindVariation::Sync) = (kind, var) { | ||||
|                 quote! { | ||||
|                     Self::RoomRedaction(event) => { | ||||
|                         #full::RoomRedaction(event.into_full_event(room_id)) | ||||
|                     }, | ||||
|                 } | ||||
|             } else { | ||||
|                 TokenStream::new() | ||||
|             }; | ||||
| 
 | ||||
|             Some(quote! { | ||||
|                 impl #ident { | ||||
|                     /// Convert this sync event into a full event, one with a room_id field.
 | ||||
|                     pub fn into_full_event(self, room_id: ::ruma_identifiers::RoomId) -> #full { | ||||
|                         match self { | ||||
|                             #( | ||||
|                                 Self::#variants(event) => { | ||||
|                                     #full::#variants(event.into_full_event(room_id)) | ||||
|                                 }, | ||||
|                             )* | ||||
|                             #redaction | ||||
|                             Self::Custom(event) => { | ||||
|                                 #full::Custom(event.into_full_event(room_id)) | ||||
|                             }, | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|         _ => None, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Generates the 3 redacted state enums, 2 redacted message enums,
 | ||||
| /// and `Deserialize` implementations.
 | ||||
| ///
 | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
| 
 | ||||
| use std::fmt; | ||||
| 
 | ||||
| use matches::matches; | ||||
| use proc_macro2::Span; | ||||
| use quote::format_ident; | ||||
| use syn::{ | ||||
| @ -44,9 +43,21 @@ impl EventKindVariation { | ||||
|     pub fn is_redacted(&self) -> bool { | ||||
|         matches!(self, Self::Redacted | Self::RedactedSync | Self::RedactedStripped) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_full_variation(&self) -> Self { | ||||
|         match self { | ||||
|             EventKindVariation::Redacted | ||||
|             | EventKindVariation::RedactedSync | ||||
|             | EventKindVariation::RedactedStripped => EventKindVariation::Redacted, | ||||
|             EventKindVariation::Full | EventKindVariation::Sync | EventKindVariation::Stripped => { | ||||
|                 EventKindVariation::Full | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // If the variants of this enum change `to_event_path` needs to be updated as well.
 | ||||
| #[derive(Debug, Eq, PartialEq)] | ||||
| pub enum EventKind { | ||||
|     Basic, | ||||
|     Ephemeral, | ||||
|  | ||||
| @ -121,6 +121,7 @@ use std::fmt::Debug; | ||||
| 
 | ||||
| use js_int::Int; | ||||
| use ruma_common::Raw; | ||||
| use ruma_identifiers::RoomId; | ||||
| use serde::{ | ||||
|     de::{self, IgnoredAny}, | ||||
|     Deserialize, Serialize, | ||||
| @ -246,6 +247,32 @@ pub struct RedactedSyncUnsigned { | ||||
|     pub redacted_because: Option<Box<SyncRedactionEvent>>, | ||||
| } | ||||
| 
 | ||||
| impl From<RedactedUnsigned> for RedactedSyncUnsigned { | ||||
|     fn from(redacted: RedactedUnsigned) -> Self { | ||||
|         match redacted.redacted_because.map(|b| *b) { | ||||
|             Some(RedactionEvent { | ||||
|                 sender, | ||||
|                 event_id, | ||||
|                 origin_server_ts, | ||||
|                 redacts, | ||||
|                 unsigned, | ||||
|                 content, | ||||
|                 .. | ||||
|             }) => Self { | ||||
|                 redacted_because: Some(Box::new(SyncRedactionEvent { | ||||
|                     sender, | ||||
|                     event_id, | ||||
|                     origin_server_ts, | ||||
|                     redacts, | ||||
|                     unsigned, | ||||
|                     content, | ||||
|                 })), | ||||
|             }, | ||||
|             _ => Self { redacted_because: None }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl RedactedSyncUnsigned { | ||||
|     /// Whether this unsigned data is empty (`redacted_because` is `None`).
 | ||||
|     ///
 | ||||
| @ -256,6 +283,32 @@ impl RedactedSyncUnsigned { | ||||
|     pub fn is_empty(&self) -> bool { | ||||
|         self.redacted_because.is_none() | ||||
|     } | ||||
| 
 | ||||
|     /// Convert a `RedactedSyncUnsigned` into `RedactedUnsigned`, converting the
 | ||||
|     /// underlying sync redaction event to a full redaction event (with room_id).
 | ||||
|     pub fn into_full(self, room_id: RoomId) -> RedactedUnsigned { | ||||
|         match self.redacted_because.map(|b| *b) { | ||||
|             Some(SyncRedactionEvent { | ||||
|                 sender, | ||||
|                 event_id, | ||||
|                 origin_server_ts, | ||||
|                 redacts, | ||||
|                 unsigned, | ||||
|                 content, | ||||
|             }) => RedactedUnsigned { | ||||
|                 redacted_because: Some(Box::new(RedactionEvent { | ||||
|                     room_id, | ||||
|                     sender, | ||||
|                     event_id, | ||||
|                     origin_server_ts, | ||||
|                     redacts, | ||||
|                     unsigned, | ||||
|                     content, | ||||
|                 })), | ||||
|             }, | ||||
|             _ => RedactedUnsigned { redacted_because: None }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The base trait that all event content types implement.
 | ||||
|  | ||||
| @ -3,14 +3,14 @@ use std::{ | ||||
|     time::{Duration, UNIX_EPOCH}, | ||||
| }; | ||||
| 
 | ||||
| use js_int::UInt; | ||||
| use js_int::{uint, UInt}; | ||||
| use matches::assert_matches; | ||||
| use ruma_common::Raw; | ||||
| use ruma_events::{ | ||||
|     call::{answer::AnswerEventContent, SessionDescription, SessionDescriptionType}, | ||||
|     room::{ImageInfo, ThumbnailInfo}, | ||||
|     sticker::StickerEventContent, | ||||
|     AnyMessageEventContent, MessageEvent, RawExt, Unsigned, | ||||
|     AnyMessageEventContent, AnySyncMessageEvent, MessageEvent, RawExt, Unsigned, | ||||
| }; | ||||
| use ruma_identifiers::{EventId, RoomId, UserId}; | ||||
| use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; | ||||
| @ -222,3 +222,58 @@ fn deserialize_message_sticker() { | ||||
|             && unsigned.is_empty() | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn deserialize_message_then_convert_to_full() { | ||||
|     let rid = RoomId::try_from("!roomid:room.com").unwrap(); | ||||
|     let json_data = json!({ | ||||
|         "content": { | ||||
|             "answer": { | ||||
|                 "type": "answer", | ||||
|                 "sdp": "Hello" | ||||
|             }, | ||||
|             "call_id": "foofoo", | ||||
|             "version": 1 | ||||
|         }, | ||||
|         "event_id": "$h29iv0s8:example.com", | ||||
|         "origin_server_ts": 1, | ||||
|         "sender": "@carl:example.com", | ||||
|         "type": "m.call.answer" | ||||
|     }); | ||||
| 
 | ||||
|     let sync_ev = | ||||
|         from_json_value::<Raw<AnySyncMessageEvent>>(json_data).unwrap().deserialize().unwrap(); | ||||
| 
 | ||||
|     // Test conversion method
 | ||||
|     let full = sync_ev.into_full_event(rid); | ||||
|     let full_json = to_json_value(full).unwrap(); | ||||
| 
 | ||||
|     assert_matches!( | ||||
|         from_json_value::<Raw<MessageEvent<AnyMessageEventContent>>>(full_json) | ||||
|             .unwrap() | ||||
|             .deserialize() | ||||
|             .unwrap(), | ||||
|         MessageEvent { | ||||
|             content: AnyMessageEventContent::CallAnswer(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!(1) | ||||
|             && event_id == "$h29iv0s8:example.com" | ||||
|             && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) | ||||
|             && room_id == "!roomid:room.com" | ||||
|             && sender == "@carl:example.com" | ||||
|             && unsigned.is_empty() | ||||
|     ); | ||||
| } | ||||
|  | ||||
| @ -8,8 +8,8 @@ use matches::assert_matches; | ||||
| use ruma_common::Raw; | ||||
| use ruma_events::{ | ||||
|     room::{aliases::AliasesEventContent, avatar::AvatarEventContent, ImageInfo, ThumbnailInfo}, | ||||
|     AnyRoomEvent, AnyStateEvent, AnyStateEventContent, RawExt, StateEvent, SyncStateEvent, | ||||
|     Unsigned, | ||||
|     AnyRoomEvent, AnyStateEvent, AnyStateEventContent, AnySyncStateEvent, RawExt, StateEvent, | ||||
|     SyncStateEvent, Unsigned, | ||||
| }; | ||||
| use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; | ||||
| use serde_json::{ | ||||
| @ -134,6 +134,7 @@ fn deserialize_aliases_with_prev_content() { | ||||
| 
 | ||||
| #[test] | ||||
| fn deserialize_aliases_sync_with_room_id() { | ||||
|     // The same JSON can be used to create a sync event, it just ignores the `room_id` field
 | ||||
|     let json_data = aliases_event_with_prev_content(); | ||||
| 
 | ||||
|     assert_matches!( | ||||
| @ -284,3 +285,36 @@ fn deserialize_member_event_with_top_level_membership_field() { | ||||
|             && content.displayname == Some("example".into()) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn deserialize_full_event_convert_to_sync() { | ||||
|     let json_data = aliases_event_with_prev_content(); | ||||
| 
 | ||||
|     let full_ev = from_json_value::<Raw<AnyStateEvent>>(json_data).unwrap().deserialize().unwrap(); | ||||
| 
 | ||||
|     // Test conversion to sync event (without room_id field)
 | ||||
|     let sync: AnySyncStateEvent = full_ev.into(); | ||||
|     let sync_json = to_json_value(sync).unwrap(); | ||||
| 
 | ||||
|     assert_matches!( | ||||
|         from_json_value::<Raw<AnySyncStateEvent>>(sync_json) | ||||
|             .unwrap() | ||||
|             .deserialize() | ||||
|             .unwrap(), | ||||
|         AnySyncStateEvent::RoomAliases(SyncStateEvent { | ||||
|             content, | ||||
|             event_id, | ||||
|             origin_server_ts, | ||||
|             prev_content: Some(prev_content), | ||||
|             sender, | ||||
|             state_key, | ||||
|             unsigned, | ||||
|         }) if content.aliases == vec![RoomAliasId::try_from("#somewhere:localhost").unwrap()] | ||||
|             && event_id == "$h29iv0s8:example.com" | ||||
|             && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) | ||||
|             && prev_content.aliases == vec![RoomAliasId::try_from("#inner:localhost").unwrap()] | ||||
|             && sender == "@carl:example.com" | ||||
|             && state_key == "" | ||||
|             && unsigned.is_empty() | ||||
|     ); | ||||
| } | ||||
|  | ||||
| @ -385,8 +385,6 @@ mod tests { | ||||
|     #[cfg(feature = "serde")] | ||||
|     #[test] | ||||
|     fn deserialize_official_room_id() { | ||||
|         use matches::assert_matches; | ||||
| 
 | ||||
|         let deserialized = | ||||
|             from_str::<RoomVersionId>(r#""1""#).expect("Failed to convert RoomVersionId to JSON."); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user