use std::ops::Deref;
use proc_macro2::{Span, TokenStream};
use quote::quote;
mod content;
use self::content::EventContentEnums;
use super::{EventEnumData, EventEnumEntry, EventEnumKind, util::expand_json_castable_impl};
use crate::{
events::common::{CommonEventField, EventContentTraitVariation, EventType, EventVariation},
util::{RumaEvents, RumaEventsReexport},
};
pub(super) struct EventEnum<'a> {
data: &'a EventEnumData,
ruma_events: &'a RumaEvents,
variant_attrs: Vec<&'a [syn::Attribute]>,
variants: Vec<&'a syn::Ident>,
variant_docs: Vec<TokenStream>,
event_type_string_match_arms: Vec<Vec<TokenStream>>,
event_type_enum: syn::Ident,
custom_content_struct: syn::Ident,
}
impl<'a> EventEnum<'a> {
pub(super) fn new(data: &'a EventEnumData, ruma_events: &'a RumaEvents) -> Self {
let variant_attrs = data.events.iter().map(|event| event.attrs.as_slice()).collect();
let variants = data.events.iter().map(|event| &event.ident).collect();
let variant_docs = data.events.iter().map(EventEnumEntry::docs).collect();
let event_type_string_match_arms = data
.events
.iter()
.map(|event| event.types.iter().map(EventType::as_match_arm).collect())
.collect();
let kind = data.kind;
let event_type_enum = kind.to_event_type_enum();
let custom_content_struct = kind.to_custom_content_ident();
Self {
data,
ruma_events,
variant_attrs,
variants,
variant_docs,
event_type_string_match_arms,
event_type_enum,
custom_content_struct,
}
}
}
impl EventEnum<'_> {
pub(super) fn expand(&self) -> syn::Result<TokenStream> {
let variations = self.kind.event_enum_variations();
if variations.is_empty() {
return Err(syn::Error::new(
Span::call_site(),
format!("The {:?} kind is not supported", self.kind),
));
}
let mut event_content_enums = EventContentEnums::new(self);
let mut tokens = variations
.iter()
.map(|variation| {
EventEnumVariation::new(self, *variation)
.expand_event_enum(&mut event_content_enums)
})
.collect::<TokenStream>();
tokens.extend(event_content_enums.expand());
Ok(tokens)
}
fn expand_from_impl(&self, ident: &syn::Ident, event_types: &[syn::Path]) -> TokenStream {
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
quote! {
#(
#[allow(unused_qualifications)]
#[automatically_derived]
#( #variant_attrs )*
impl ::std::convert::From<#event_types> for #ident {
fn from(c: #event_types) -> Self {
Self::#variants(c)
}
}
)*
}
}
}
impl Deref for EventEnum<'_> {
type Target = EventEnumData;
fn deref(&self) -> &Self::Target {
self.data
}
}
struct EventEnumVariation<'a> {
inner: &'a EventEnum<'a>,
variation: EventVariation,
ident: syn::Ident,
event_struct: syn::Ident,
event_types: Vec<syn::Path>,
}
impl<'a> EventEnumVariation<'a> {
fn new(inner: &'a EventEnum<'a>, variation: EventVariation) -> Self {
let ident = inner.kind.to_event_enum_ident(variation);
let event_struct = inner.kind.to_event_ident(variation);
let event_types =
inner.events.iter().map(|event| event.to_event_path(inner.kind, variation)).collect();
Self { inner, variation, ident, event_struct, event_types }
}
fn maybe_redacted(&self) -> bool {
self.kind.is_timeline()
&& matches!(self.variation, EventVariation::None | EventVariation::Sync)
}
fn expand_event_enum(&self, event_content_enums: &mut EventContentEnums<'a>) -> TokenStream {
let ruma_events = self.ruma_events;
let ident = &self.ident;
let event_struct = &self.event_struct;
let attrs = &self.attrs;
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
let variant_docs = &self.variant_docs;
let event_types = &self.event_types;
let custom_content_struct = &self.custom_content_struct;
let deserialize_impl = self.expand_deserialize_impl();
let field_accessor_impl = self.expand_accessor_methods(event_content_enums);
let from_impl = self.expand_from_impl(ident, event_types);
let json_castable_impl =
expand_json_castable_impl(ident, self.kind, self.variation, ruma_events);
let from_sync_into_full = self.expand_from_sync_into_full();
quote! {
#( #attrs )*
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant, unused_qualifications)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub enum #ident {
#(
#variant_docs
#( #variant_attrs )*
#variants(#event_types),
)*
#[doc(hidden)]
_Custom(
#ruma_events::#event_struct<
#ruma_events::_custom::#custom_content_struct
>,
),
}
#deserialize_impl
#field_accessor_impl
#from_impl
#json_castable_impl
#from_sync_into_full
}
}
fn expand_deserialize_impl(&self) -> TokenStream {
let ruma_events = self.ruma_events;
let ruma_common = ruma_events.ruma_common();
let serde = ruma_events.reexported(RumaEventsReexport::Serde);
let serde_json = ruma_events.reexported(RumaEventsReexport::SerdeJson);
let ident = &self.ident;
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
let event_type_string_match_arms = &self.event_type_string_match_arms;
let event_types = &self.event_types;
quote! {
#[allow(unused_qualifications)]
impl<'de> #serde::de::Deserialize<'de> for #ident {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: #serde::de::Deserializer<'de>,
{
use #serde::de::Error as _;
let json = Box::<#serde_json::value::RawValue>::deserialize(deserializer)?;
let #ruma_events::EventTypeDeHelper { ev_type, .. } =
#ruma_common::serde::from_raw_json_value(&json)?;
match &*ev_type {
#(
#( #variant_attrs )*
#( #event_type_string_match_arms )|* => {
let event = #serde_json::from_str::<#event_types>(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_accessor_methods(
&self,
event_content_enums: &mut EventContentEnums<'a>,
) -> TokenStream {
let ruma_events = self.ruma_events;
let ident = &self.ident;
let event_type_enum = &self.event_type_enum;
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
let event_type_match_arms = if self.maybe_redacted() {
quote! {
#(
#( #variant_attrs )*
Self::#variants(event) => event.event_type(),
)*
Self::_Custom(event) => event.event_type(),
}
} else if self.variation == EventVariation::Stripped {
let possibly_redacted_event_content_kind_trait =
self.kind.to_content_kind_trait(EventContentTraitVariation::PossiblyRedacted);
quote! {
#(
#( #variant_attrs )*
Self::#variants(event) =>
#ruma_events::#possibly_redacted_event_content_kind_trait::event_type(&event.content),
)*
Self::_Custom(event) => ::std::convert::From::from(
#ruma_events::#possibly_redacted_event_content_kind_trait::event_type(&event.content),
),
}
} else {
let original_event_content_kind_trait =
self.kind.to_content_kind_trait(EventContentTraitVariation::Original);
quote! {
#(
#( #variant_attrs )*
Self::#variants(event) =>
#ruma_events::#original_event_content_kind_trait::event_type(&event.content),
)*
Self::_Custom(event) => ::std::convert::From::from(
#ruma_events::#original_event_content_kind_trait::event_type(&event.content),
),
}
};
let content_accessor = self.expand_content_accessors(event_content_enums);
let field_accessors = self.expand_event_field_accessors();
let state_key_accessor = self.expand_state_key_accessor();
let relations_accessor = self.expand_relations_accessor();
let transaction_id_accessor = self.expand_transaction_id_accessor();
quote! {
#[automatically_derived]
impl #ident {
pub fn event_type(&self) -> #ruma_events::#event_type_enum {
match self { #event_type_match_arms }
}
#content_accessor
#( #field_accessors )*
#relations_accessor
#state_key_accessor
#transaction_id_accessor
}
}
}
fn expand_content_accessors(
&self,
event_content_enums: &mut EventContentEnums<'a>,
) -> Option<TokenStream> {
let mut tokens = event_content_enums
.get_or_create(self.variation)?
.expand_content_accessors(self.variation, &self.event_struct);
if matches!(self.kind, EventEnumKind::State) && self.maybe_redacted() {
tokens.extend(event_content_enums.get_or_create(EventVariation::Stripped).map(
|event_content_enum| {
event_content_enum.expand_content_accessors(self.variation, &self.event_struct)
},
));
tokens.extend(
event_content_enums
.event_content_change_enum()
.expand_content_accessors(&self.event_struct),
);
}
Some(tokens)
}
fn expand_event_field_accessors(&self) -> impl Iterator<Item = TokenStream> {
CommonEventField::ALL
.iter()
.filter(|field| self.kind.field_is_present(**field, self.variation))
.map(|field| {
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
let docs = format!("Returns this event's `{field}` field.");
let ident = field.ident();
let (field_type, is_ref) = field.ty(self.ruma_events);
let ampersand = is_ref.then(|| quote! { & });
let call_parens = self.maybe_redacted().then(|| quote! { () });
quote! {
#[doc = #docs]
pub fn #ident(&self) -> #field_type {
match self {
#(
#( #variant_attrs )*
Self::#variants(event) => #ampersand event.#ident #call_parens,
)*
Self::_Custom(event) => #ampersand event.#ident #call_parens,
}
}
}
})
}
fn expand_state_key_accessor(&self) -> Option<TokenStream> {
if self.kind != EventEnumKind::State {
return None;
}
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
let call_parens = self.maybe_redacted().then(|| quote! { () });
Some(quote! {
pub fn state_key(&self) -> &::std::primitive::str {
match self {
#(
#( #variant_attrs )*
Self::#variants(event) => event.state_key #call_parens .as_ref(),
)*
Self::_Custom(event) => event.state_key #call_parens .as_ref(),
}
}
})
}
fn expand_relations_accessor(&self) -> Option<TokenStream> {
if self.kind != EventEnumKind::MessageLike {
return None;
}
let ruma_events = self.ruma_events;
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
Some(quote! {
pub fn relations(
&self,
) -> #ruma_events::BundledMessageLikeRelations<AnySyncMessageLikeEvent> {
match self {
#(
#( #variant_attrs )*
Self::#variants(event) => event.as_original().map_or_else(
::std::default::Default::default,
|ev| ev.unsigned.relations.clone().map_replace(|r| {
::std::convert::From::from(r.into_maybe_redacted())
}),
),
)*
Self::_Custom(event) => event.as_original().map_or_else(
::std::default::Default::default,
|ev| ev.unsigned.relations.clone().map_replace(|r| {
AnySyncMessageLikeEvent::_Custom(r.into_maybe_redacted())
}),
),
}
}
})
}
fn expand_transaction_id_accessor(&self) -> Option<TokenStream> {
if !self.maybe_redacted() {
return None;
}
let ruma_common = self.ruma_events.ruma_common();
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
Some(quote! {
pub fn transaction_id(&self) -> Option<&#ruma_common::TransactionId> {
match self {
#(
#( #variant_attrs )*
Self::#variants(event) => {
event.as_original().and_then(|ev| ev.unsigned.transaction_id.as_deref())
}
)*
Self::_Custom(event) => {
event.as_original().and_then(|ev| ev.unsigned.transaction_id.as_deref())
}
}
}
})
}
fn expand_from_sync_into_full(&self) -> Option<TokenStream> {
if !self.variation.is_sync()
|| !self.inner.data.kind.event_enum_variations().contains(&EventVariation::None)
{
return None;
}
let ruma_common = self.ruma_events.ruma_common();
let ident = &self.ident;
let full = self.kind.to_event_enum_ident(EventVariation::None);
let variants = &self.variants;
let variant_attrs = &self.variant_attrs;
Some(quote! {
#[automatically_derived]
impl ::std::convert::From<#full> for #ident {
fn from(event: #full) -> Self {
match event {
#(
#( #variant_attrs )*
#full::#variants(event) => {
Self::#variants(::std::convert::From::from(event))
}
)*
#full::_Custom(event) => {
Self::_Custom(::std::convert::From::from(event))
},
}
}
}
#[automatically_derived]
impl #ident {
pub fn into_full_event(self, room_id: #ruma_common::OwnedRoomId) -> #full {
match self {
#(
#( #variant_attrs )*
Self::#variants(event) => {
#full::#variants(event.into_full_event(room_id))
}
)*
Self::_Custom(event) => {
#full::_Custom(event.into_full_event(room_id))
},
}
}
}
})
}
}
impl<'a> Deref for EventEnumVariation<'a> {
type Target = EventEnum<'a>;
fn deref(&self) -> &Self::Target {
self.inner
}
}