#![allow(clippy::too_many_arguments)] use std::{borrow::Cow, fmt};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
DeriveInput, Field, Ident, LitStr, Meta, Token, Type,
};
use super::event_parse::{EventKind, EventKindVariation};
use crate::util::{m_prefix_name_to_type_name, PrivateField};
mod kw {
syn::custom_keyword!(skip_redaction);
syn::custom_keyword!(custom_redacted);
syn::custom_keyword!(custom_possibly_redacted);
syn::custom_keyword!(kind);
syn::custom_keyword!(type_fragment);
syn::custom_keyword!(state_key_type);
syn::custom_keyword!(unsigned_type);
syn::custom_keyword!(alias);
syn::custom_keyword!(without_relation);
}
enum EventFieldMeta {
SkipRedaction,
TypeFragment,
}
impl Parse for EventFieldMeta {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::skip_redaction) {
let _: kw::skip_redaction = input.parse()?;
Ok(EventFieldMeta::SkipRedaction)
} else if lookahead.peek(kw::type_fragment) {
let _: kw::type_fragment = input.parse()?;
Ok(EventFieldMeta::TypeFragment)
} else {
Err(lookahead.error())
}
}
}
#[derive(Default)]
struct ContentMeta {
event_type: Option<LitStr>,
event_kind: Option<EventKind>,
custom_redacted: Option<kw::custom_redacted>,
custom_possibly_redacted: Option<kw::custom_possibly_redacted>,
state_key_type: Option<Box<Type>>,
unsigned_type: Option<Box<Type>>,
aliases: Vec<LitStr>,
without_relation: Option<kw::without_relation>,
}
impl ContentMeta {
fn merge(self, other: ContentMeta) -> syn::Result<Self> {
fn either_spanned<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
match (a, b) {
(None, None) => Ok(None),
(Some(val), None) | (None, Some(val)) => Ok(Some(val)),
(Some(a), Some(b)) => {
let mut error = syn::Error::new_spanned(a, "redundant attribute argument");
error.combine(syn::Error::new_spanned(b, "note: first one here"));
Err(error)
}
}
}
fn either_named<T>(name: &str, a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> {
match (a, b) {
(None, None) => Ok(None),
(Some(val), None) | (None, Some(val)) => Ok(Some(val)),
(Some(_), Some(_)) => Err(syn::Error::new(
Span::call_site(),
format!("multiple {name} attributes found, there can only be one"),
)),
}
}
Ok(Self {
event_type: either_spanned(self.event_type, other.event_type)?,
event_kind: either_named("event_kind", self.event_kind, other.event_kind)?,
custom_redacted: either_spanned(self.custom_redacted, other.custom_redacted)?,
custom_possibly_redacted: either_spanned(
self.custom_possibly_redacted,
other.custom_possibly_redacted,
)?,
state_key_type: either_spanned(self.state_key_type, other.state_key_type)?,
unsigned_type: either_spanned(self.unsigned_type, other.unsigned_type)?,
aliases: [self.aliases, other.aliases].concat(),
without_relation: either_spanned(self.without_relation, other.without_relation)?,
})
}
}
impl Parse for ContentMeta {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Token![type]) {
let _: Token![type] = input.parse()?;
let _: Token![=] = input.parse()?;
let event_type = input.parse()?;
Ok(Self { event_type: Some(event_type), ..Default::default() })
} else if lookahead.peek(kw::kind) {
let _: kw::kind = input.parse()?;
let _: Token![=] = input.parse()?;
let event_kind = input.parse()?;
Ok(Self { event_kind: Some(event_kind), ..Default::default() })
} else if lookahead.peek(kw::custom_redacted) {
let custom_redacted: kw::custom_redacted = input.parse()?;
Ok(Self { custom_redacted: Some(custom_redacted), ..Default::default() })
} else if lookahead.peek(kw::custom_possibly_redacted) {
let custom_possibly_redacted: kw::custom_possibly_redacted = input.parse()?;
Ok(Self {
custom_possibly_redacted: Some(custom_possibly_redacted),
..Default::default()
})
} else if lookahead.peek(kw::state_key_type) {
let _: kw::state_key_type = input.parse()?;
let _: Token![=] = input.parse()?;
let state_key_type = input.parse()?;
Ok(Self { state_key_type: Some(state_key_type), ..Default::default() })
} else if lookahead.peek(kw::unsigned_type) {
let _: kw::unsigned_type = input.parse()?;
let _: Token![=] = input.parse()?;
let unsigned_type = input.parse()?;
Ok(Self { unsigned_type: Some(unsigned_type), ..Default::default() })
} else if lookahead.peek(kw::alias) {
let _: kw::alias = input.parse()?;
let _: Token![=] = input.parse()?;
let alias = input.parse()?;
Ok(Self { aliases: vec![alias], ..Default::default() })
} else if lookahead.peek(kw::without_relation) {
let without_relation: kw::without_relation = input.parse()?;
Ok(Self { without_relation: Some(without_relation), ..Default::default() })
} else {
Err(lookahead.error())
}
}
}
struct ContentAttrs {
event_type: LitStr,
event_kind: Option<EventKind>,
state_key_type: Option<TokenStream>,
unsigned_type: Option<TokenStream>,
aliases: Vec<LitStr>,
is_custom_redacted: bool,
is_custom_possibly_redacted: bool,
has_without_relation: bool,
}
impl TryFrom<ContentMeta> for ContentAttrs {
type Error = syn::Error;
fn try_from(value: ContentMeta) -> Result<Self, Self::Error> {
let ContentMeta {
event_type,
event_kind,
custom_redacted,
custom_possibly_redacted,
state_key_type,
unsigned_type,
aliases,
without_relation,
} = value;
let event_type = event_type.ok_or_else(|| {
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",
)
})?;
let state_key_type = match (event_kind, state_key_type) {
(Some(EventKind::State), None) => {
return Err(syn::Error::new(
Span::call_site(),
"no state_key_type attribute found, please specify one",
));
}
(Some(EventKind::State), Some(ty)) => Some(quote! { #ty }),
(_, None) => None,
(_, Some(ty)) => {
return Err(syn::Error::new_spanned(
ty,
"state_key_type attribute is not valid for non-state event kinds",
));
}
};
let is_custom_redacted = custom_redacted.is_some();
let is_custom_possibly_redacted = custom_possibly_redacted.is_some();
let unsigned_type = unsigned_type.map(|ty| quote! { #ty });
let event_type_s = event_type.value();
let prefix = event_type_s.strip_suffix(".*");
if prefix.unwrap_or(&event_type_s).contains('*') {
return Err(syn::Error::new_spanned(
event_type,
"event type may only contain `*` as part of a `.*` suffix",
));
}
if prefix.is_some() && !event_kind.is_some_and(|k| k.is_account_data()) {
return Err(syn::Error::new_spanned(
event_type,
"only account data events may contain a `.*` suffix",
));
}
for alias in &aliases {
if alias.value().ends_with(".*") != prefix.is_some() {
return Err(syn::Error::new_spanned(
alias,
"aliases should have the same `.*` suffix, or lack thereof, as the main event type",
));
}
}
let has_without_relation = without_relation.is_some();
Ok(Self {
event_type,
event_kind,
state_key_type,
unsigned_type,
aliases,
is_custom_redacted,
is_custom_possibly_redacted,
has_without_relation,
})
}
}
pub fn expand_event_content(
input: &DeriveInput,
ruma_events: &TokenStream,
) -> syn::Result<TokenStream> {
let content_meta = input
.attrs
.iter()
.filter(|attr| attr.path().is_ident("ruma_event"))
.try_fold(ContentMeta::default(), |meta, attr| {
let list: Punctuated<ContentMeta, Token![,]> =
attr.parse_args_with(Punctuated::parse_terminated)?;
list.into_iter().try_fold(meta, ContentMeta::merge)
})?;
let ContentAttrs {
event_type,
event_kind,
state_key_type,
unsigned_type,
aliases,
is_custom_redacted,
is_custom_possibly_redacted,
has_without_relation,
} = content_meta.try_into()?;
let ident = &input.ident;
let fields = match &input.data {
syn::Data::Struct(syn::DataStruct { fields, .. }) => Some(fields.iter()),
_ => {
if event_kind.is_some_and(|kind| needs_redacted(is_custom_redacted, kind)) {
return Err(syn::Error::new(
Span::call_site(),
"To generate a redacted event content, the event content type needs to be a struct. Disable this with the custom_redacted attribute",
));
}
if event_kind.is_some_and(|kind| needs_possibly_redacted(is_custom_redacted, kind)) {
return Err(syn::Error::new(
Span::call_site(),
"To generate a possibly redacted event content, the event content type needs to be a struct. Disable this with the custom_possibly_redacted attribute",
));
}
if has_without_relation {
return Err(syn::Error::new(
Span::call_site(),
"To generate an event content without relation, the event content type needs to be a struct. Disable this by removing the without_relation attribute",
));
}
None
}
};
let redacted_event_content =
event_kind.filter(|kind| needs_redacted(is_custom_redacted, *kind)).map(|kind| {
generate_redacted_event_content(
ident,
&input.vis,
fields.clone().unwrap(),
&event_type,
kind,
state_key_type.as_ref(),
unsigned_type.clone(),
&aliases,
ruma_events,
)
.unwrap_or_else(syn::Error::into_compile_error)
});
let possibly_redacted_event_content = event_kind
.filter(|kind| needs_possibly_redacted(is_custom_possibly_redacted, *kind))
.map(|_| {
generate_possibly_redacted_event_content(
ident,
&input.vis,
fields.clone().unwrap(),
&event_type,
state_key_type.as_ref(),
unsigned_type.clone(),
&aliases,
ruma_events,
)
.unwrap_or_else(syn::Error::into_compile_error)
});
let event_content_without_relation = has_without_relation.then(|| {
generate_event_content_without_relation(
ident,
&input.vis,
fields.clone().unwrap(),
ruma_events,
)
.unwrap_or_else(syn::Error::into_compile_error)
});
let event_content_impl = generate_event_content_impl(
ident,
&input.vis,
fields,
&event_type,
event_kind,
EventKindContentVariation::Original,
state_key_type.as_ref(),
unsigned_type,
&aliases,
ruma_events,
)
.unwrap_or_else(syn::Error::into_compile_error);
let static_event_content_impl =
generate_static_event_content_impl(ident, &event_type, ruma_events);
let type_aliases = event_kind.map(|k| {
generate_event_type_aliases(k, ident, &input.vis, &event_type.value(), ruma_events)
.unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
#redacted_event_content
#possibly_redacted_event_content
#event_content_without_relation
#event_content_impl
#static_event_content_impl
#type_aliases
})
}
fn generate_redacted_event_content<'a>(
ident: &Ident,
vis: &syn::Visibility,
fields: impl Iterator<Item = &'a Field>,
event_type: &LitStr,
event_kind: EventKind,
state_key_type: Option<&TokenStream>,
unsigned_type: Option<TokenStream>,
aliases: &[LitStr],
ruma_events: &TokenStream,
) -> syn::Result<TokenStream> {
assert!(
!event_type.value().contains('*'),
"Event type shouldn't contain a `*`, this should have been checked previously"
);
let ruma_common = quote! { #ruma_events::exports::ruma_common };
let serde = quote! { #ruma_events::exports::serde };
let doc = format!("Redacted form of [`{ident}`]");
let redacted_ident = format_ident!("Redacted{ident}");
let kept_redacted_fields: Vec<_> = fields
.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 EventFieldMeta::SkipRedaction = a.parse_args()? {
keep_field = true;
}
Ok(None)
} else {
Ok(Some(a.clone()))
}
})
.filter_map(Result::transpose)
.collect::<syn::Result<_>>()?;
if keep_field {
Ok(Some(Field { attrs, ..f.clone() }))
} else {
Ok(None)
}
})
.filter_map(Result::transpose)
.collect::<syn::Result<_>>()?;
let redaction_struct_fields = kept_redacted_fields.iter().flat_map(|f| &f.ident);
let constructor = kept_redacted_fields.is_empty().then(|| {
let doc = format!("Creates an empty {redacted_ident}.");
quote! {
impl #redacted_ident {
#[doc = #doc]
#vis fn new() -> Self {
Self {}
}
}
}
});
let redacted_event_content = generate_event_content_impl(
&redacted_ident,
vis,
Some(kept_redacted_fields.iter()),
event_type,
Some(event_kind),
EventKindContentVariation::Redacted,
state_key_type,
unsigned_type,
aliases,
ruma_events,
)
.unwrap_or_else(syn::Error::into_compile_error);
let static_event_content_impl =
generate_static_event_content_impl(&redacted_ident, event_type, ruma_events);
Ok(quote! {
#[automatically_derived]
impl #ruma_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(ruma_unstable_exhaustive_types), non_exhaustive)]
#vis struct #redacted_ident {
#( #kept_redacted_fields, )*
}
#constructor
#redacted_event_content
#static_event_content_impl
})
}
fn generate_possibly_redacted_event_content<'a>(
ident: &Ident,
vis: &syn::Visibility,
fields: impl Iterator<Item = &'a Field>,
event_type: &LitStr,
state_key_type: Option<&TokenStream>,
unsigned_type: Option<TokenStream>,
aliases: &[LitStr],
ruma_events: &TokenStream,
) -> syn::Result<TokenStream> {
assert!(
!event_type.value().contains('*'),
"Event type shouldn't contain a `*`, this should have been checked previously"
);
let serde = quote! { #ruma_events::exports::serde };
let doc = format!(
"The possibly redacted form of [`{ident}`].\n\n\
This type is used when it's not obvious whether the content is redacted or not."
);
let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
let mut field_changed = false;
let possibly_redacted_fields: Vec<_> = fields
.map(|f| {
let mut keep_field = false;
let mut unsupported_serde_attribute = None;
if let Type::Path(type_path) = &f.ty {
if type_path.path.segments.first().filter(|s| s.ident == "Option").is_some() {
keep_field = true;
}
}
let mut attrs = f
.attrs
.iter()
.map(|a| -> syn::Result<_> {
if a.path().is_ident("ruma_event") {
if let EventFieldMeta::SkipRedaction = a.parse_args()? {
keep_field = true;
}
Ok(None)
} else {
if a.path().is_ident("serde") {
if let Meta::List(list) = &a.meta {
let nested: Punctuated<Meta, Token![,]> =
list.parse_args_with(Punctuated::parse_terminated)?;
for meta in &nested {
if meta.path().is_ident("default") {
keep_field = true;
} else if !meta.path().is_ident("rename")
&& !meta.path().is_ident("alias")
&& unsupported_serde_attribute.is_none()
{
unsupported_serde_attribute =
Some(syn::Error::new_spanned(
meta,
"Can't generate PossiblyRedacted struct with \
unsupported serde attribute\n\
Expected one of `default`, `rename` or `alias`\n\
Use the `custom_possibly_redacted` attribute \
and create the struct manually",
));
}
}
}
}
Ok(Some(a.clone()))
}
})
.filter_map(Result::transpose)
.collect::<syn::Result<_>>()?;
if keep_field {
Ok(Field { attrs, ..f.clone() })
} else if let Some(err) = unsupported_serde_attribute {
Err(err)
} else if f.ident.is_none() {
Err(syn::Error::new(
Span::call_site(),
"Can't generate PossiblyRedacted struct for tuple structs\n\
Use the `custom_possibly_redacted` attribute and create the struct manually",
))
} else {
field_changed = true;
let old_type = &f.ty;
let ty = parse_quote! { Option<#old_type> };
attrs.push(parse_quote! { #[serde(skip_serializing_if = "Option::is_none")] });
Ok(Field { attrs, ty, ..f.clone() })
}
})
.collect::<syn::Result<_>>()?;
if field_changed {
let possibly_redacted_event_content = generate_event_content_impl(
&possibly_redacted_ident,
vis,
Some(possibly_redacted_fields.iter()),
event_type,
Some(EventKind::State),
EventKindContentVariation::PossiblyRedacted,
state_key_type,
unsigned_type,
aliases,
ruma_events,
)
.unwrap_or_else(syn::Error::into_compile_error);
let static_event_content_impl =
generate_static_event_content_impl(&possibly_redacted_ident, event_type, ruma_events);
Ok(quote! {
#[doc = #doc]
#[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#vis struct #possibly_redacted_ident {
#( #possibly_redacted_fields, )*
}
#possibly_redacted_event_content
#static_event_content_impl
})
} else {
Ok(quote! {
#[doc = #doc]
#vis type #possibly_redacted_ident = #ident;
#[automatically_derived]
impl #ruma_events::PossiblyRedactedStateEventContent for #ident {
type StateKey = #state_key_type;
}
})
}
}
fn generate_event_content_without_relation<'a>(
ident: &Ident,
vis: &syn::Visibility,
fields: impl Iterator<Item = &'a Field>,
ruma_events: &TokenStream,
) -> syn::Result<TokenStream> {
let serde = quote! { #ruma_events::exports::serde };
let type_doc = format!(
"Form of [`{ident}`] without relation.\n\n\
To construct this type, construct a [`{ident}`] and then use one of its `::from()` / `.into()` methods."
);
let without_relation_ident = format_ident!("{ident}WithoutRelation");
let with_relation_fn_doc =
format!("Transform `self` into a [`{ident}`] with the given relation.");
let (relates_to, other_fields) = fields.partition::<Vec<_>, _>(|f| {
f.ident.as_ref().filter(|ident| *ident == "relates_to").is_some()
});
let relates_to_type = relates_to.into_iter().next().map(|f| &f.ty).ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"`without_relation` can only be used on events with a `relates_to` field",
)
})?;
let without_relation_fields = other_fields.iter().flat_map(|f| &f.ident).collect::<Vec<_>>();
let without_relation_struct = if other_fields.is_empty() {
quote! { ; }
} else {
quote! {
{ #( #other_fields, )* }
}
};
Ok(quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl ::std::convert::From<#ident> for #without_relation_ident {
fn from(c: #ident) -> Self {
Self {
#( #without_relation_fields: c.#without_relation_fields, )*
}
}
}
#[doc = #type_doc]
#[derive(Clone, Debug, #serde::Deserialize, #serde::Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#vis struct #without_relation_ident #without_relation_struct
impl #without_relation_ident {
#[doc = #with_relation_fn_doc]
#vis fn with_relation(self, relates_to: #relates_to_type) -> #ident {
#ident {
#( #without_relation_fields: self.#without_relation_fields, )*
relates_to,
}
}
}
})
}
fn generate_event_type_aliases(
event_kind: EventKind,
ident: &Ident,
vis: &syn::Visibility,
event_type: &str,
ruma_events: &TokenStream,
) -> syn::Result<TokenStream> {
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::None,
EventKindVariation::Sync,
EventKindVariation::Original,
EventKindVariation::OriginalSync,
EventKindVariation::Stripped,
EventKindVariation::Initial,
EventKindVariation::Redacted,
EventKindVariation::RedactedSync,
]
.iter()
.filter_map(|&var| Some((var, event_kind.to_event_ident(var).ok()?)))
.map(|(var, ev_struct)| {
let ev_type = format_ident!("{var}{ev_type_s}");
let doc_text = match var {
EventKindVariation::None | EventKindVariation::Original => "",
EventKindVariation::Sync | EventKindVariation::OriginalSync => {
" 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_type}` event{doc_text}.");
let content_struct = if var.is_redacted() {
Cow::Owned(format_ident!("Redacted{ident}"))
} else if let EventKindVariation::Stripped = var {
Cow::Owned(format_ident!("PossiblyRedacted{ident}"))
} else {
Cow::Borrowed(ident)
};
quote! {
#[doc = #ev_type_doc]
#vis type #ev_type = #ruma_events::#ev_struct<#content_struct>;
}
})
.collect();
Ok(type_aliases)
}
#[derive(PartialEq)]
enum EventKindContentVariation {
Original,
Redacted,
PossiblyRedacted,
}
impl fmt::Display for EventKindContentVariation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EventKindContentVariation::Original => Ok(()),
EventKindContentVariation::Redacted => write!(f, "Redacted"),
EventKindContentVariation::PossiblyRedacted => write!(f, "PossiblyRedacted"),
}
}
}
fn generate_event_content_impl<'a>(
ident: &Ident,
vis: &syn::Visibility,
mut fields: Option<impl Iterator<Item = &'a Field>>,
event_type: &LitStr,
event_kind: Option<EventKind>,
variation: EventKindContentVariation,
state_key_type: Option<&TokenStream>,
unsigned_type: Option<TokenStream>,
aliases: &[LitStr],
ruma_events: &TokenStream,
) -> syn::Result<TokenStream> {
let serde = quote! { #ruma_events::exports::serde };
let serde_json = quote! { #ruma_events::exports::serde_json };
let (event_type_ty_decl, event_type_ty, event_type_fn_impl);
let type_suffix_data = event_type
.value()
.strip_suffix('*')
.map(|type_prefix| {
let Some(fields) = &mut fields else {
return Err(syn::Error::new_spanned(
event_type,
"event type with a `.*` suffix is required to be a struct",
));
};
let type_fragment_field = fields
.find_map(|f| {
f.attrs.iter().filter(|a| a.path().is_ident("ruma_event")).find_map(|attr| {
match attr.parse_args() {
Ok(EventFieldMeta::TypeFragment) => Some(Ok(f)),
Ok(_) => None,
Err(e) => Some(Err(e)),
}
})
})
.transpose()?
.ok_or_else(|| {
syn::Error::new_spanned(
event_type,
"event type with a `.*` suffix requires there to be a \
`#[ruma_event(type_fragment)]` field",
)
})?
.ident
.as_ref()
.expect("type fragment field needs to have a name");
<syn::Result<_>>::Ok((type_prefix.to_owned(), type_fragment_field))
})
.transpose()?;
match event_kind {
Some(kind) => {
let i = kind.to_event_type_enum();
event_type_ty_decl = None;
event_type_ty = quote! { #ruma_events::#i };
event_type_fn_impl = match &type_suffix_data {
Some((type_prefix, type_fragment_field)) => {
let format = type_prefix.to_owned() + "{}";
quote! {
::std::convert::From::from(::std::format!(#format, self.#type_fragment_field))
}
}
None => 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! {
#[doc(hidden)]
#vis struct #i {
ty: ::std::option::Option<crate::PrivOwnedStr>,
}
impl #serde::Serialize for #i {
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: #serde::Serializer,
{
let s = self.ty.as_ref().map(|t| &t.0[..]).unwrap_or(#event_type);
serializer.serialize_str(s)
}
}
});
event_type_ty = quote! { #i };
event_type_fn_impl = quote! { #event_type_ty { ty: ::std::option::Option::None } };
}
}
let sub_trait_impl = event_kind.map(|kind| {
let trait_name = format_ident!("{variation}{kind}Content");
let state_key = (kind == EventKind::State).then(|| {
assert!(state_key_type.is_some());
quote! {
type StateKey = #state_key_type;
}
});
quote! {
#[automatically_derived]
impl #ruma_events::#trait_name for #ident {
#state_key
}
}
});
let static_state_event_content_impl = (event_kind == Some(EventKind::State)
&& variation == EventKindContentVariation::Original)
.then(|| {
let possibly_redacted_ident = format_ident!("PossiblyRedacted{ident}");
let unsigned_type = unsigned_type
.unwrap_or_else(|| quote! { #ruma_events::StateUnsigned<Self::PossiblyRedacted> });
quote! {
#[automatically_derived]
impl #ruma_events::StaticStateEventContent for #ident {
type PossiblyRedacted = #possibly_redacted_ident;
type Unsigned = #unsigned_type;
}
}
});
let event_types = aliases.iter().chain([event_type]);
let event_content_from_type_impl = type_suffix_data.map(|(_, type_fragment_field)| {
let type_prefixes = event_types.map(|ev_type| {
ev_type
.value()
.strip_suffix('*')
.expect("aliases have already been checked to have the same suffix")
.to_owned()
});
let type_prefixes = quote! {
[#(#type_prefixes,)*]
};
let fields_without_type_fragment = fields
.unwrap()
.filter(|f| {
!f.attrs.iter().any(|a| {
a.path().is_ident("ruma_event")
&& matches!(a.parse_args(), Ok(EventFieldMeta::TypeFragment))
})
})
.map(PrivateField)
.collect::<Vec<_>>();
let fields_ident_without_type_fragment =
fields_without_type_fragment.iter().filter_map(|f| f.0.ident.as_ref());
quote! {
impl #ruma_events::EventContentFromType for #ident {
fn from_parts(
ev_type: &::std::primitive::str,
content: &#serde_json::value::RawValue,
) -> #serde_json::Result<Self> {
#[derive(#serde::Deserialize)]
struct WithoutTypeFragment {
#( #fields_without_type_fragment, )*
}
if let ::std::option::Option::Some(type_fragment) =
#type_prefixes.iter().find_map(|prefix| ev_type.strip_prefix(prefix))
{
let c: WithoutTypeFragment = #serde_json::from_str(content.get())?;
::std::result::Result::Ok(Self {
#(
#fields_ident_without_type_fragment:
c.#fields_ident_without_type_fragment,
)*
#type_fragment_field: type_fragment.to_owned(),
})
} else {
::std::result::Result::Err(#serde::de::Error::custom(
::std::format!(
"expected event type starting with one of `{:?}`, found `{}`",
#type_prefixes, ev_type,
)
))
}
}
}
}
});
Ok(quote! {
#event_type_ty_decl
#[automatically_derived]
impl #ruma_events::EventContent for #ident {
type EventType = #event_type_ty;
fn event_type(&self) -> Self::EventType {
#event_type_fn_impl
}
}
#event_content_from_type_impl
#sub_trait_impl
#static_state_event_content_impl
})
}
fn generate_static_event_content_impl(
ident: &Ident,
event_type: &LitStr,
ruma_events: &TokenStream,
) -> TokenStream {
quote! {
impl #ruma_events::StaticEventContent for #ident {
const TYPE: &'static ::std::primitive::str = #event_type;
}
}
}
fn needs_redacted(is_custom_redacted: bool, event_kind: EventKind) -> bool {
!is_custom_redacted && matches!(event_kind, EventKind::MessageLike | EventKind::State)
}
fn needs_possibly_redacted(is_custom_possibly_redacted: bool, event_kind: EventKind) -> bool {
!is_custom_possibly_redacted && event_kind == EventKind::State
}