//! Implementation of event enum and event content enum macros. use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::{Attribute, Data, DataEnum, DeriveInput, Ident, LitStr}; use super::{ event_parse::{EventEnumDecl, EventEnumEntry, EventKind, EventKindVariation}, util::{has_prev_content, is_non_stripped_room_event, EVENT_FIELDS}, }; use crate::util::m_prefix_name_to_type_name; /// Create a content enum from `EventEnumInput`. pub fn expand_event_enums(input: &EventEnumDecl) -> syn::Result { use EventKindVariation as V; let ruma_common = crate::import_ruma_common(); let mut res = TokenStream::new(); let kind = input.kind; let attrs = &input.attrs; let events: Vec<_> = input.events.iter().map(|entry| entry.ev_type.clone()).collect(); let variants: Vec<_> = input.events.iter().map(EventEnumEntry::to_variant).collect::>()?; let events = &events; let variants = &variants; let ruma_common = &ruma_common; res.extend(expand_event_enum(kind, V::Full, events, attrs, variants, ruma_common)); res.extend(expand_content_enum(kind, events, attrs, variants, ruma_common)); if matches!(kind, EventKind::Ephemeral | EventKind::MessageLike | EventKind::State) { res.extend(expand_event_enum(kind, V::Sync, events, attrs, variants, ruma_common)); res.extend(expand_from_full_event(kind, V::Full, variants)); res.extend(expand_into_full_event(kind, V::Sync, variants, ruma_common)); } if matches!(kind, EventKind::State) { res.extend(expand_event_enum(kind, V::Stripped, events, attrs, variants, ruma_common)); res.extend(expand_event_enum(kind, V::Initial, events, attrs, variants, ruma_common)); } if matches!(kind, EventKind::MessageLike | EventKind::State) { res.extend(expand_event_enum(kind, V::Redacted, events, attrs, variants, ruma_common)); res.extend(expand_event_enum(kind, V::RedactedSync, events, attrs, variants, ruma_common)); res.extend(expand_redact(kind, V::Full, variants, ruma_common)); res.extend(expand_redact(kind, V::Sync, variants, ruma_common)); res.extend(expand_possibly_redacted_enum(kind, V::Full, ruma_common)); res.extend(expand_possibly_redacted_enum(kind, V::Sync, ruma_common)); res.extend(expand_from_full_event(kind, V::Redacted, variants)); res.extend(expand_into_full_event(kind, V::RedactedSync, variants, ruma_common)); } Ok(res) } fn expand_event_enum( kind: EventKind, var: EventKindVariation, events: &[LitStr], attrs: &[Attribute], variants: &[EventEnumVariant], ruma_common: &TokenStream, ) -> TokenStream { let event_struct = kind.to_event_ident(var); let ident = kind.to_event_enum_ident(var); let variant_decls = variants.iter().map(|v| v.decl()); let content: Vec<_> = events.iter().map(|event| to_event_path(event, kind, var, ruma_common)).collect(); let custom_ty = format_ident!("Custom{}Content", kind); let deserialize_impl = expand_deserialize_impl(kind, var, events, variants, ruma_common); let field_accessor_impl = expand_accessor_methods(kind, var, variants, ruma_common); let from_impl = expand_from_impl(&ident, &content, variants); quote! { #( #attrs )* #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub enum #ident { #( #[doc = #events] #variant_decls(#content), )* /// An event not defined by the Matrix specification #[doc(hidden)] _Custom( #ruma_common::events::#event_struct<#ruma_common::events::_custom::#custom_ty>, ), } #deserialize_impl #field_accessor_impl #from_impl } } fn expand_deserialize_impl( kind: EventKind, var: EventKindVariation, events: &[LitStr], variants: &[EventEnumVariant], ruma_common: &TokenStream, ) -> TokenStream { let serde = quote! { #ruma_common::exports::serde }; let serde_json = quote! { #ruma_common::exports::serde_json }; let ident = kind.to_event_enum_ident(var); let variant_attrs = variants.iter().map(|v| { let attrs = &v.attrs; quote! { #(#attrs)* } }); let self_variants = variants.iter().map(|v| v.ctor(quote! { Self })); let content = events.iter().map(|event| to_event_path(event, kind, var, ruma_common)); quote! { impl<'de> #serde::de::Deserialize<'de> for #ident { fn deserialize(deserializer: D) -> Result where D: #serde::de::Deserializer<'de>, { use #serde::de::Error as _; let json = Box::<#serde_json::value::RawValue>::deserialize(deserializer)?; let #ruma_common::events::EventTypeDeHelper { ev_type, .. } = #ruma_common::serde::from_raw_json_value(&json)?; match &*ev_type { #( #variant_attrs #events => { let event = #serde_json::from_str::<#content>(json.get()) .map_err(D::Error::custom)?; Ok(#self_variants(event)) }, )* _ => { let event = #serde_json::from_str(json.get()).map_err(D::Error::custom)?; Ok(Self::_Custom(event)) }, } } } } } fn expand_from_impl( ty: &Ident, content: &[TokenStream], variants: &[EventEnumVariant], ) -> TokenStream { let from_impls = content.iter().zip(variants).map(|(content, variant)| { let ident = &variant.ident; let attrs = &variant.attrs; quote! { #[automatically_derived] #(#attrs)* impl ::std::convert::From<#content> for #ty { fn from(c: #content) -> Self { Self::#ident(c) } } } }); quote! { #( #from_impls )* } } fn expand_from_full_event( kind: EventKind, var: EventKindVariation, variants: &[EventEnumVariant], ) -> TokenStream { let ident = kind.to_event_enum_ident(var); let sync = kind.to_event_enum_ident(var.to_sync()); let ident_variants = variants.iter().map(|v| v.match_arm(&ident)); let self_variants = variants.iter().map(|v| v.ctor(quote! { Self })); quote! { #[automatically_derived] impl ::std::convert::From<#ident> for #sync { fn from(event: #ident) -> Self { match event { #( #ident_variants(event) => { #self_variants(::std::convert::From::from(event)) }, )* #ident::_Custom(event) => { Self::_Custom(::std::convert::From::from(event)) }, } } } } } fn expand_into_full_event( kind: EventKind, var: EventKindVariation, variants: &[EventEnumVariant], ruma_common: &TokenStream, ) -> TokenStream { let ident = kind.to_event_enum_ident(var); let full = kind.to_event_enum_ident(var.to_full()); let self_variants = variants.iter().map(|v| v.match_arm(quote! { Self })); let full_variants = variants.iter().map(|v| v.ctor(&full)); quote! { #[automatically_derived] impl #ident { /// Convert this sync event into a full event (one with a `room_id` field). pub fn into_full_event( self, room_id: ::std::boxed::Box<#ruma_common::RoomId>, ) -> #full { match self { #( #self_variants(event) => { #full_variants(event.into_full_event(room_id)) }, )* Self::_Custom(event) => { #full::_Custom(event.into_full_event(room_id)) }, } } } } } /// Create a content enum from `EventEnumInput`. fn expand_content_enum( kind: EventKind, event_types: &[LitStr], attrs: &[Attribute], variants: &[EventEnumVariant], ruma_common: &TokenStream, ) -> TokenStream { let serde = quote! { #ruma_common::exports::serde }; let serde_json = quote! { #ruma_common::exports::serde_json }; let ident = kind.to_content_enum(); let event_type_enum = kind.to_event_type_enum(); let content: Vec<_> = event_types.iter().map(|ev| to_event_content_path(kind, ev, None, ruma_common)).collect(); let event_type_match_arms = event_types.iter().map(|s| { if let Some(prefix) = s.value().strip_suffix(".*") { quote! { _s if _s.starts_with(#prefix) } } else { quote! { #s } } }); let variant_decls = variants.iter().map(|v| v.decl()).collect::>(); let variant_attrs = variants.iter().map(|v| { let attrs = &v.attrs; quote! { #(#attrs)* } }); let variant_arms = variants.iter().map(|v| v.match_arm(quote! { Self })).collect::>(); let variant_ctors = variants.iter().map(|v| v.ctor(quote! { Self })); let from_impl = expand_from_impl(&ident, &content, variants); let serialize_custom_event_error_path = quote! { #ruma_common::events::serialize_custom_event_error }.to_string(); quote! { #( #attrs )* #[derive(Clone, Debug, #serde::Serialize)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub enum #ident { #( #[doc = #event_types] #variant_decls(#content), )* #[doc(hidden)] #[serde(serialize_with = #serialize_custom_event_error_path)] _Custom { event_type: crate::PrivOwnedStr, }, } #[automatically_derived] impl #ruma_common::events::EventContent for #ident { type EventType = #ruma_common::events::#event_type_enum; fn event_type(&self) -> Self::EventType { match self { #( #variant_arms(content) => content.event_type(), )* Self::_Custom { event_type } => ::std::convert::From::from(&event_type.0[..]), } } fn from_parts( event_type: &::std::primitive::str, input: &#serde_json::value::RawValue, ) -> #serde_json::Result { match event_type { #( #variant_attrs #event_type_match_arms => { let content = #content::from_parts(event_type, input)?; ::std::result::Result::Ok(#variant_ctors(content)) } )* ty => { ::std::result::Result::Ok(Self::_Custom { event_type: crate::PrivOwnedStr(ty.into()), }) } } } } #from_impl } } fn expand_redact( kind: EventKind, var: EventKindVariation, variants: &[EventEnumVariant], ruma_common: &TokenStream, ) -> TokenStream { let ident = kind.to_event_enum_ident(var); let redacted_enum = kind.to_event_enum_ident(var.to_redacted()); let self_variants = variants.iter().map(|v| v.match_arm(quote! { Self })); let redacted_variants = variants.iter().map(|v| v.ctor(&redacted_enum)); quote! { #[automatically_derived] impl #ruma_common::events::Redact for #ident { type Redacted = #redacted_enum; fn redact( self, redaction: #ruma_common::events::room::redaction::SyncRoomRedactionEvent, version: &#ruma_common::RoomVersionId, ) -> #redacted_enum { match self { #( #self_variants(event) => #redacted_variants( #ruma_common::events::Redact::redact(event, redaction, version), ), )* Self::_Custom(event) => #redacted_enum::_Custom( #ruma_common::events::Redact::redact(event, redaction, version), ) } } } } } fn expand_possibly_redacted_enum( kind: EventKind, var: EventKindVariation, ruma_common: &TokenStream, ) -> TokenStream { let serde = quote! { #ruma_common::exports::serde }; let serde_json = quote! { #ruma_common::exports::serde_json }; let ident = format_ident!("AnyPossiblyRedacted{}", kind.to_event_ident(var)); let regular_enum_ident = kind.to_event_enum_ident(var); let redacted_enum_ident = kind.to_event_enum_ident(var.to_redacted()); quote! { /// An enum that holds either regular un-redacted events or redacted events. #[derive(Clone, Debug)] #[allow(clippy::exhaustive_enums)] pub enum #ident { /// An un-redacted event. Regular(#regular_enum_ident), /// A redacted event. Redacted(#redacted_enum_ident), } impl<'de> #serde::de::Deserialize<'de> for #ident { fn deserialize(deserializer: D) -> Result where D: #serde::de::Deserializer<'de>, { let json = Box::<#serde_json::value::RawValue>::deserialize(deserializer)?; let #ruma_common::events::RedactionDeHelper { unsigned } = #ruma_common::serde::from_raw_json_value(&json)?; Ok(match unsigned { Some(unsigned) if unsigned.redacted_because.is_some() => { Self::Redacted(#ruma_common::serde::from_raw_json_value(&json)?) } _ => Self::Regular(#ruma_common::serde::from_raw_json_value(&json)?), }) } } } } fn expand_accessor_methods( kind: EventKind, var: EventKindVariation, variants: &[EventEnumVariant], ruma_common: &TokenStream, ) -> TokenStream { let ident = kind.to_event_enum_ident(var); let event_type_enum = format_ident!("{}Type", kind); let self_variants: Vec<_> = variants.iter().map(|v| v.match_arm(quote! { Self })).collect(); let content_accessors = (!var.is_redacted()).then(|| { let content_enum = kind.to_content_enum(); let content_variants: Vec<_> = variants.iter().map(|v| v.ctor(&content_enum)).collect(); let unsigned = if has_prev_content(kind, var) { quote! { /// Returns this event's unsigned field. pub fn unsigned(&self) -> #ruma_common::events::StateUnsigned<#content_enum> { match self { #( #self_variants(event) => { event.unsigned._map_prev_unsigned(|c| { #content_variants(c.clone()) }) }, )* Self::_Custom(event) => { event.unsigned._map_prev_unsigned(|c| #content_enum::_Custom { event_type: crate::PrivOwnedStr( ::std::convert::From::from( ::std::string::ToString::to_string( &#ruma_common::events::EventContent::event_type(c) ) ), ), }) }, } } } } else if is_non_stripped_room_event(kind, var) { let field_type = field_return_type("unsigned", var, ruma_common); let variants = variants.iter().map(|v| v.match_arm(quote! { Self })); quote! { /// Returns this event's unsigned field. pub fn unsigned(&self) -> &#field_type { match self { #( #variants(event) => &event.unsigned, )* Self::_Custom(event) => &event.unsigned, } } } } else { quote! {} }; quote! { /// Returns the content for this event. pub fn content(&self) -> #content_enum { match self { #( #self_variants(event) => #content_variants(event.content.clone()), )* Self::_Custom(event) => #content_enum::_Custom { event_type: crate::PrivOwnedStr( ::std::convert::From::from( ::std::string::ToString::to_string( &#ruma_common::events::EventContent::event_type(&event.content) ) ), ), }, } } #unsigned } }); let methods = EVENT_FIELDS.iter().map(|(name, has_field)| { has_field(kind, var).then(|| { let docs = format!("Returns this event's {} field.", name); let ident = Ident::new(name, Span::call_site()); let field_type = field_return_type(name, var, ruma_common); let variants = variants.iter().map(|v| v.match_arm(quote! { Self })); quote! { #[doc = #docs] pub fn #ident(&self) -> &#field_type { match self { #( #variants(event) => &event.#ident, )* Self::_Custom(event) => &event.#ident, } } } }) }); quote! { #[automatically_derived] impl #ident { /// Returns the `type` of this event. pub fn event_type(&self) -> #ruma_common::events::#event_type_enum { match self { #( #self_variants(event) => #ruma_common::events::EventContent::event_type(&event.content), )* Self::_Custom(event) => ::std::convert::From::from( #ruma_common::events::EventContent::event_type(&event.content), ), } } #content_accessors #( #methods )* } } } fn to_event_path( name: &LitStr, kind: EventKind, var: EventKindVariation, ruma_common: &TokenStream, ) -> TokenStream { let path = event_module_path(name); let event = m_prefix_name_to_type_name(name).unwrap(); let event_name = if kind == EventKind::ToDevice { assert_eq!(var, EventKindVariation::Full); format_ident!("ToDevice{}Event", event) } else { format_ident!("{}{}Event", var, event) }; quote! { #ruma_common::events::#( #path )::*::#event_name } } fn to_event_content_path( kind: EventKind, name: &LitStr, prefix: Option<&str>, ruma_common: &TokenStream, ) -> TokenStream { let path = event_module_path(name); let event = m_prefix_name_to_type_name(name).unwrap(); let content_str = match kind { EventKind::ToDevice => { format_ident!("ToDevice{}{}EventContent", prefix.unwrap_or(""), event) } _ => format_ident!("{}{}EventContent", prefix.unwrap_or(""), event), }; quote! { #ruma_common::events::#( #path )::*::#content_str } } fn event_module_path(name: &LitStr) -> Vec { let value = name.value(); let value = value.strip_prefix("m.").unwrap(); value .strip_suffix(".*") .unwrap_or(value) .split('.') .map(|s| Ident::new(s, name.span())) .collect() } fn field_return_type( name: &str, var: EventKindVariation, ruma_common: &TokenStream, ) -> TokenStream { match name { "origin_server_ts" => quote! { #ruma_common::MilliSecondsSinceUnixEpoch }, "room_id" => quote! { #ruma_common::RoomId }, "event_id" => quote! { #ruma_common::EventId }, "sender" => quote! { #ruma_common::UserId }, "state_key" => quote! { ::std::primitive::str }, "unsigned" => { if var.is_redacted() { quote! { #ruma_common::events::RedactedUnsigned } } else { quote! { #ruma_common::events::MessageLikeUnsigned } } } _ => panic!("the `ruma_macros::event_enum::EVENT_FIELD` const was changed"), } } pub(crate) struct EventEnumVariant { pub attrs: Vec, pub ident: Ident, } impl EventEnumVariant { pub(crate) fn to_tokens(&self, prefix: Option, with_attrs: bool) -> TokenStream where T: ToTokens, { let mut tokens = TokenStream::new(); if with_attrs { for attr in &self.attrs { attr.to_tokens(&mut tokens); } } if let Some(p) = prefix { tokens.extend(quote! { #p :: }) } self.ident.to_tokens(&mut tokens); tokens } pub(crate) fn decl(&self) -> TokenStream { self.to_tokens::(None, true) } pub(crate) fn match_arm(&self, prefix: impl ToTokens) -> TokenStream { self.to_tokens(Some(prefix), true) } pub(crate) fn ctor(&self, prefix: impl ToTokens) -> TokenStream { self.to_tokens(Some(prefix), false) } } impl EventEnumEntry { pub(crate) fn has_type_fragment(&self) -> bool { self.ev_type.value().ends_with(".*") } pub(crate) fn to_variant(&self) -> syn::Result { let attrs = self.attrs.clone(); let ident = m_prefix_name_to_type_name(&self.ev_type)?; Ok(EventEnumVariant { attrs, ident }) } } pub(crate) fn expand_from_impls_derived(input: DeriveInput) -> TokenStream { let variants = match &input.data { Data::Enum(DataEnum { variants, .. }) => variants, _ => panic!("this derive macro only works with enums"), }; let from_impls = variants.iter().map(|variant| match &variant.fields { syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { let inner_struct = &fields.unnamed.first().unwrap().ty; let var_ident = &variant.ident; let id = &input.ident; quote! { #[automatically_derived] impl ::std::convert::From<#inner_struct> for #id { fn from(c: #inner_struct) -> Self { Self::#var_ident(c) } } } } _ => { panic!("this derive macro only works with enum variants with a single unnamed field") } }); quote! { #( #from_impls )* } }