//! Implementations of the MessageLikeEventContent and StateEventContent derive macro. use std::borrow::Cow; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, DeriveInput, Ident, LitStr, Token, }; use crate::util::m_prefix_name_to_type_name; use super::event_parse::{EventKind, EventKindVariation}; mod kw { // This `content` field is kept when the event is redacted. syn::custom_keyword!(skip_redaction); // Do not emit any redacted event code. syn::custom_keyword!(custom_redacted); // The kind of event content this is. syn::custom_keyword!(kind); } /// Parses attributes for `*EventContent` derives. /// /// `#[ruma_event(type = "m.room.alias")]` enum EventMeta { /// Variant holds the "m.whatever" event type. Type(LitStr), Kind(EventKind), /// Fields marked with `#[ruma_event(skip_redaction)]` are kept when the event is /// redacted. SkipRedaction, /// This attribute signals that the events redacted form is manually implemented and should not /// be generated. CustomRedacted, } impl EventMeta { fn get_event_type(&self) -> Option<&LitStr> { match self { Self::Type(t) => Some(t), _ => None, } } fn get_event_kind(&self) -> Option { match self { Self::Kind(k) => Some(*k), _ => None, } } } impl Parse for EventMeta { fn parse(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(Token![type]) { let _: Token![type] = input.parse()?; let _: Token![=] = input.parse()?; input.parse().map(EventMeta::Type) } else if lookahead.peek(kw::kind) { let _: kw::kind = input.parse()?; let _: Token![=] = input.parse()?; EventKind::parse(input).map(EventMeta::Kind) } else if lookahead.peek(kw::skip_redaction) { let _: kw::skip_redaction = input.parse()?; Ok(EventMeta::SkipRedaction) } else if lookahead.peek(kw::custom_redacted) { let _: kw::custom_redacted = input.parse()?; Ok(EventMeta::CustomRedacted) } else { Err(lookahead.error()) } } } struct MetaAttrs(Vec); impl MetaAttrs { fn is_custom(&self) -> bool { self.0.iter().any(|a| matches!(a, &EventMeta::CustomRedacted)) } fn get_event_type(&self) -> Option<&LitStr> { self.0.iter().find_map(|a| a.get_event_type()) } fn get_event_kind(&self) -> Option { self.0.iter().find_map(|a| a.get_event_kind()) } } impl Parse for MetaAttrs { fn parse(input: ParseStream<'_>) -> syn::Result { let attrs = syn::punctuated::Punctuated::::parse_terminated(input)?; Ok(Self(attrs.into_iter().collect())) } } /// Create an `EventContent` implementation for a struct. pub fn expand_event_content( input: &DeriveInput, ruma_common: &TokenStream, ) -> syn::Result { let content_attr = input .attrs .iter() .filter(|attr| attr.path.is_ident("ruma_event")) .map(|attr| attr.parse_args::()) .collect::>>()?; let mut event_types: Vec<_> = content_attr.iter().filter_map(|attrs| attrs.get_event_type()).collect(); let event_type = match event_types.as_slice() { [] => { return Err(syn::Error::new( Span::call_site(), "no event type attribute found, \ add `#[ruma_event(type = \"any.room.event\", kind = Kind)]` \ below the event content derive", )); } [_] => event_types.pop().unwrap(), _ => { return Err(syn::Error::new( Span::call_site(), "multiple event type attribute found, there can only be one", )); } }; let mut event_kinds: Vec<_> = content_attr.iter().filter_map(|attrs| attrs.get_event_kind()).collect(); let event_kind = match event_kinds.as_slice() { [] => None, [_] => Some(event_kinds.pop().unwrap()), _ => { return Err(syn::Error::new( Span::call_site(), "multiple event kind attribute found, there can only be one", )); } }; let ident = &input.ident; let fields = match &input.data { syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, _ => { return Err(syn::Error::new( Span::call_site(), "event content types need to be structs", )); } }; // We only generate redacted content structs for state and message-like events let redacted_event_content = needs_redacted(&content_attr, event_kind) .then(|| { generate_redacted_event_content(ident, fields, event_type, event_kind, ruma_common) }) .transpose()?; let event_content_impl = generate_event_content_impl(ident, event_type, event_kind, ruma_common)?; let static_event_content_impl = event_kind .map(|k| generate_static_event_content_impl(ident, k, false, event_type, ruma_common)); let type_aliases = event_kind .map(|k| generate_event_type_aliases(k, ident, &event_type.value(), ruma_common)) .transpose()?; Ok(quote! { #redacted_event_content #event_content_impl #static_event_content_impl #type_aliases }) } fn generate_redacted_event_content( ident: &Ident, fields: &syn::Fields, event_type: &LitStr, event_kind: Option, ruma_common: &TokenStream, ) -> syn::Result { let serde = quote! { #ruma_common::exports::serde }; let serde_json = quote! { #ruma_common::exports::serde_json }; let doc = format!("Redacted form of [`{}`]", ident); let redacted_ident = format_ident!("Redacted{}", ident); let kept_redacted_fields: Vec<_> = fields .iter() .map(|f| { let mut keep_field = false; let attrs = f .attrs .iter() .map(|a| -> syn::Result<_> { if a.path.is_ident("ruma_event") { if let EventMeta::SkipRedaction = a.parse_args()? { keep_field = true; } // don't re-emit our `ruma_event` attributes Ok(None) } else { Ok(Some(a.clone())) } }) .filter_map(Result::transpose) .collect::>()?; if keep_field { Ok(Some(syn::Field { attrs, ..f.clone() })) } else { Ok(None) } }) .filter_map(Result::transpose) .collect::>()?; let redaction_struct_fields = kept_redacted_fields.iter().flat_map(|f| &f.ident); let (redacted_fields, redacted_return) = if kept_redacted_fields.is_empty() { (quote! { ; }, quote! { Ok(#redacted_ident {}) }) } else { ( quote! { { #( #kept_redacted_fields, )* } }, quote! { Err(#serde::de::Error::custom( format!("this redacted event has fields that cannot be constructed") )) }, ) }; let (has_deserialize_fields, has_serialize_fields) = if kept_redacted_fields.is_empty() { (quote! { #ruma_common::events::HasDeserializeFields::False }, quote! { false }) } else { (quote! { #ruma_common::events::HasDeserializeFields::True }, quote! { true }) }; let constructor = kept_redacted_fields.is_empty().then(|| { let doc = format!("Creates an empty {}.", redacted_ident); quote! { impl #redacted_ident { #[doc = #doc] pub fn new() -> Self { Self } } } }); let redacted_event_content = generate_event_content_impl(&redacted_ident, event_type, event_kind, ruma_common)?; let static_event_content_impl = event_kind.map(|k| { generate_static_event_content_impl(&redacted_ident, k, true, event_type, ruma_common) }); Ok(quote! { // this is the non redacted event content's impl #[automatically_derived] impl #ruma_common::events::RedactContent for #ident { type Redacted = #redacted_ident; fn redact(self, version: &#ruma_common::RoomVersionId) -> #redacted_ident { #redacted_ident { #( #redaction_struct_fields: self.#redaction_struct_fields, )* } } } #[doc = #doc] #[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct #redacted_ident #redacted_fields #constructor #redacted_event_content #[automatically_derived] impl #ruma_common::events::RedactedEventContent for #redacted_ident { fn empty(ev_type: &str) -> #serde_json::Result { if ev_type != #event_type { return Err(#serde::de::Error::custom( format!("expected event type `{}`, found `{}`", #event_type, ev_type) )); } #redacted_return } fn has_serialize_fields(&self) -> bool { #has_serialize_fields } fn has_deserialize_fields() -> #ruma_common::events::HasDeserializeFields { #has_deserialize_fields } } #static_event_content_impl }) } fn generate_event_type_aliases( event_kind: EventKind, ident: &Ident, event_type: &str, ruma_common: &TokenStream, ) -> syn::Result { // The redaction module has its own event types. if ident == "RoomRedactionEventContent" { return Ok(quote! {}); } let ident_s = ident.to_string(); let ev_type_s = ident_s.strip_suffix("Content").ok_or_else(|| { syn::Error::new_spanned(ident, "Expected content struct name ending in `Content`") })?; let type_aliases = [ EventKindVariation::Full, EventKindVariation::Sync, EventKindVariation::Stripped, EventKindVariation::Initial, EventKindVariation::Redacted, EventKindVariation::RedactedSync, ] .iter() .filter_map(|&var| Some((var, event_kind.try_to_event_ident(var)?))) .map(|(var, ev_struct)| { let ev_type = format_ident!("{}{}", var, ev_type_s); let doc_text = match var { EventKindVariation::Full => "", EventKindVariation::Sync => " from a `sync_events` response", EventKindVariation::Stripped => " from an invited room preview", EventKindVariation::Redacted => " that has been redacted", EventKindVariation::RedactedSync => { " from a `sync_events` response that has been redacted" } EventKindVariation::Initial => " for creating a room", }; let ev_type_doc = format!("An `{}` event{}.", event_type, doc_text); let content_struct = if var.is_redacted() { Cow::Owned(format_ident!("Redacted{}", ident)) } else { Cow::Borrowed(ident) }; quote! { #[doc = #ev_type_doc] pub type #ev_type = #ruma_common::events::#ev_struct<#content_struct>; } }) .collect(); Ok(type_aliases) } fn generate_event_content_impl( ident: &Ident, event_type: &LitStr, event_kind: Option, ruma_common: &TokenStream, ) -> syn::Result { let serde = quote! { #ruma_common::exports::serde }; let serde_json = quote! { #ruma_common::exports::serde_json }; let (event_type_ty_decl, event_type_ty, event_type_fn_impl); match event_kind { Some(kind) => { let i = kind.to_event_type_enum(); event_type_ty_decl = None; event_type_ty = quote! { #ruma_common::events::#i }; event_type_fn_impl = quote! { ::std::convert::From::from(#event_type) }; } None => { let camel_case_type_name = m_prefix_name_to_type_name(event_type)?; let i = format_ident!("{}EventType", camel_case_type_name); event_type_ty_decl = Some(quote! { /// Implementation detail, you don't need to care about this. #[doc(hidden)] pub struct #i { // Set to None for intended type, Some for a different one ty: ::std::option::Option, } impl ::std::convert::AsRef<::std::primitive::str> for #i { fn as_ref(&self) -> &::std::primitive::str { self.ty.as_ref().map(|t| &t.0[..]).unwrap_or(#event_type) } } }); event_type_ty = quote! { #i }; event_type_fn_impl = quote! { #event_type_ty { ty: ::std::option::Option::None } }; } } Ok(quote! { #event_type_ty_decl #[automatically_derived] impl #ruma_common::events::EventContent for #ident { type EventType = #event_type_ty; fn event_type(&self) -> Self::EventType { #event_type_fn_impl } fn from_parts( ev_type: &::std::primitive::str, content: &#serde_json::value::RawValue, ) -> #serde_json::Result { if ev_type != #event_type { return ::std::result::Result::Err(#serde::de::Error::custom( ::std::format!("expected event type `{}`, found `{}`", #event_type, ev_type) )); } #serde_json::from_str(content.get()) } } }) } fn generate_static_event_content_impl( ident: &Ident, event_kind: EventKind, redacted: bool, event_type: &LitStr, ruma_common: &TokenStream, ) -> TokenStream { let event_kind = match event_kind { EventKind::GlobalAccountData => quote! { GlobalAccountData }, EventKind::RoomAccountData => quote! { RoomAccountData }, EventKind::Ephemeral => quote! { EphemeralRoomData }, EventKind::MessageLike => quote! { MessageLike { redacted: #redacted } }, EventKind::State => quote! { State { redacted: #redacted } }, EventKind::ToDevice => quote! { ToDevice }, EventKind::RoomRedaction | EventKind::Presence | EventKind::Decrypted | EventKind::HierarchySpaceChild => { unreachable!("not a valid event content kind") } }; quote! { impl #ruma_common::events::StaticEventContent for #ident { const KIND: #ruma_common::events::EventKind = #ruma_common::events::EventKind::#event_kind; const TYPE: &'static ::std::primitive::str = #event_type; } } } fn needs_redacted(input: &[MetaAttrs], event_kind: Option) -> bool { // `is_custom` means that the content struct does not need a generated // redacted struct also. If no `custom_redacted` attrs are found the content // needs a redacted struct generated. !input.iter().any(|a| a.is_custom()) && matches!(event_kind, Some(EventKind::MessageLike) | Some(EventKind::State)) }