use as_variant::as_variant;
use proc_macro2::Span;
use syn::{
meta::ParseNestedMeta,
parse::{Parse, ParseStream},
parse_quote,
};
use super::{EventContent, EventContentField, EventContentKind};
use crate::{
events::common::{CommonEventKind, EventType, EventTypes},
util::{ParseNestedMetaExt, RumaEvents, SerdeMetaItem, StructFieldExt},
};
impl EventContent {
pub(super) fn parse(input: syn::DeriveInput) -> syn::Result<Self> {
let ruma_events = RumaEvents::new();
let mut event_content_attrs = EventContentAttrs::default();
for attr in &input.attrs {
if !attr.path().is_ident("ruma_event") {
continue;
}
attr.parse_nested_meta(|meta| event_content_attrs.try_merge(meta, attr))?;
}
let EventContentAttrs {
event_type,
aliases,
kind,
state_key_type,
unsigned_type,
has_custom_redacted,
has_custom_possibly_redacted,
has_without_relation,
} = event_content_attrs;
let event_type = event_type.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"missing `type` attribute, \
add `#[ruma_event(type = \"m.event_type\", kind = Kind)]` \
below the event content derive",
)
})?;
let types = EventTypes::try_from_parts(event_type, aliases)?;
let fields =
as_variant!(input.data, syn::Data::Struct(syn::DataStruct { fields, ..}) => fields)
.map(|fields| {
fields.into_iter().map(|field| field.try_into()).collect::<syn::Result<_>>()
})
.transpose()?;
let kind = EventContentKind::try_from_parts(
kind,
state_key_type,
unsigned_type,
has_custom_redacted,
has_custom_possibly_redacted,
&ruma_events,
)?;
let event_content = Self {
types,
ident: input.ident,
vis: input.vis,
fields,
kind,
has_without_relation,
ruma_events,
};
event_content.validate()?;
Ok(event_content)
}
}
impl EventContent {
fn validate(&self) -> syn::Result<()> {
if !self.ident.to_string().ends_with("Content") {
return Err(syn::Error::new_spanned(
&self.ident,
"event content struct name must end with `Content`",
));
}
let has_type_suffix = self.types.is_prefix();
if has_type_suffix && !self.kind.is_account_data() {
return Err(syn::Error::new_spanned(
&self.types.ev_type,
"only account data events may contain a `.*` suffix",
));
}
if let Some(fields) = &self.fields {
let type_fragment_fields_count =
fields.iter().filter(|field| field.is_type_fragment).count();
if has_type_suffix && type_fragment_fields_count == 0 {
return Err(syn::Error::new_spanned(
&self.types.ev_type,
"event type with a `.*` suffix requires there to be a \
`#[ruma_event(type_fragment)]` field",
));
}
if !has_type_suffix && type_fragment_fields_count > 0 {
return Err(syn::Error::new(
Span::call_site(),
"`#[ruma_event(type_fragment)]` field is only valid for an event type with a `.*` suffix",
));
}
if type_fragment_fields_count > 1 {
return Err(syn::Error::new(
Span::call_site(),
"There can only be one `#[ruma_event(type_fragment)]` field",
));
}
} else if has_type_suffix {
return Err(syn::Error::new_spanned(
&self.types.ev_type,
"event type with a `.*` suffix is required to be a struct",
));
}
if self.kind.should_generate_redacted() && self.fields.is_none() {
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 self.kind.should_generate_possibly_redacted() {
if self
.fields
.as_ref()
.is_none_or(|fields| fields.iter().any(|field| field.inner.ident.is_none()))
{
return Err(syn::Error::new(
Span::call_site(),
"To generate a possibly redacted event content, \
the event content type needs to be a struct with named fields. \
Disable this with the `custom_possibly_redacted` attribute",
));
}
if let Some(fields) = &self.fields
&& let Some(field_with_unsupported_serde_attribute) = fields.iter().find(|field| {
if field.keep_in_possibly_redacted() {
return false;
}
field.inner.serde_meta_items().any(|serde_meta| {
serde_meta != SerdeMetaItem::Rename && serde_meta != SerdeMetaItem::Alias
})
})
{
return Err(syn::Error::new_spanned(
field_with_unsupported_serde_attribute,
"To generate a possibly redacted event content, \
the fields that are redacted must either use the `default` \
serde attribute with any other attribute, or only the \
following serde attributes: `rename` or `alias`. \
Disable this with the `custom_possibly_redacted` attribute",
));
}
}
if self.has_without_relation
&& self.fields.as_ref().is_none_or(|fields| {
!fields.iter().any(|field| {
field.inner.ident.as_ref().is_some_and(|ident| *ident == "relates_to")
})
})
{
return Err(syn::Error::new(
Span::call_site(),
"To generate an event content without relation, \
the event content type needs to be a struct with a field named `relates_to`. \
Disable this by removing the `without_relation` attribute",
));
}
Ok(())
}
}
#[derive(Default)]
pub struct EventContentAttrs {
event_type: Option<EventType>,
aliases: Vec<EventType>,
kind: Option<EventContentKindAttr>,
state_key_type: Option<syn::Type>,
unsigned_type: Option<syn::Type>,
has_custom_redacted: bool,
has_custom_possibly_redacted: bool,
has_without_relation: bool,
}
impl EventContentAttrs {
fn set_event_type(&mut self, event_type: EventType, attr: &syn::Attribute) -> syn::Result<()> {
if self.event_type.is_some() {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple values for `type` attribute",
));
}
self.event_type = Some(event_type);
Ok(())
}
fn push_alias(&mut self, event_type: EventType) {
self.aliases.push(event_type);
}
fn set_kind(&mut self, kind: EventContentKindAttr, attr: &syn::Attribute) -> syn::Result<()> {
if self.kind.is_some() {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple values for `kind` attribute",
));
}
self.kind = Some(kind);
Ok(())
}
fn set_state_key_type(
&mut self,
state_key_type: syn::Type,
attr: &syn::Attribute,
) -> syn::Result<()> {
if self.state_key_type.is_some() {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple values for `state_key_type` attribute",
));
}
self.state_key_type = Some(state_key_type);
Ok(())
}
fn set_unsigned_type(
&mut self,
unsigned_type: syn::Type,
attr: &syn::Attribute,
) -> syn::Result<()> {
if self.unsigned_type.is_some() {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple values for `unsigned_type` attribute",
));
}
self.unsigned_type = Some(unsigned_type);
Ok(())
}
fn set_custom_redacted(&mut self, attr: &syn::Attribute) -> syn::Result<()> {
if self.has_custom_redacted {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple occurrences of `custom_redacted` attribute",
));
}
self.has_custom_redacted = true;
Ok(())
}
fn set_custom_possibly_redacted(&mut self, attr: &syn::Attribute) -> syn::Result<()> {
if self.has_custom_possibly_redacted {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple occurrences of `custom_possibly_redacted` attribute",
));
}
self.has_custom_possibly_redacted = true;
Ok(())
}
fn set_without_relation(&mut self, attr: &syn::Attribute) -> syn::Result<()> {
if self.has_without_relation {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple occurrences of `without_relation` attribute",
));
}
self.has_without_relation = true;
Ok(())
}
fn try_merge(&mut self, meta: ParseNestedMeta<'_>, attr: &syn::Attribute) -> syn::Result<()> {
if meta.path.is_ident("type") {
return self.set_event_type(meta.value()?.parse()?, attr);
}
if meta.path.is_ident("alias") {
self.push_alias(meta.value()?.parse()?);
return Ok(());
}
if meta.path.is_ident("kind") {
return self.set_kind(meta.value()?.parse()?, attr);
}
if meta.path.is_ident("state_key_type") {
return self.set_state_key_type(meta.value()?.parse()?, attr);
}
if meta.path.is_ident("unsigned_type") {
return self.set_unsigned_type(meta.value()?.parse()?, attr);
}
if meta.path.is_ident("custom_redacted") {
if meta.has_value() {
return Err(meta.error("`custom_redacted` attribute doesn't expect a value"));
}
return self.set_custom_redacted(attr);
}
if meta.path.is_ident("custom_possibly_redacted") {
if meta.has_value() {
return Err(
meta.error("`custom_possibly_redacted` attribute doesn't expect a value")
);
}
return self.set_custom_possibly_redacted(attr);
}
if meta.path.is_ident("without_relation") {
if meta.has_value() {
return Err(meta.error("`without_relation` attribute doesn't expect a value"));
}
return self.set_without_relation(attr);
}
Err(meta.error("unsupported `ruma_event` attribute"))
}
}
impl TryFrom<syn::Field> for EventContentField {
type Error = syn::Error;
fn try_from(mut inner: syn::Field) -> Result<Self, Self::Error> {
let mut field_attrs = EventContentFieldAttrs::default();
for attr in inner.attrs.extract_if(.., |attr| attr.path().is_ident("ruma_event")) {
attr.parse_nested_meta(|meta| field_attrs.try_merge(meta, &attr))?;
}
let EventContentFieldAttrs { skip_redaction, is_type_fragment } = field_attrs;
if skip_redaction && is_type_fragment {
return Err(syn::Error::new_spanned(
inner,
"the `skip_redaction` attribute is not valid on a field with the `type_fragment` attribute",
));
}
Ok(Self { inner, skip_redaction, is_type_fragment })
}
}
#[derive(Default)]
struct EventContentFieldAttrs {
skip_redaction: bool,
is_type_fragment: bool,
}
impl EventContentFieldAttrs {
fn set_skip_redaction(&mut self, attr: &syn::Attribute) -> syn::Result<()> {
if self.skip_redaction {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple occurrences of `skip_redaction` attribute",
));
}
self.skip_redaction = true;
Ok(())
}
fn set_type_fragment(&mut self, attr: &syn::Attribute) -> syn::Result<()> {
if self.is_type_fragment {
return Err(syn::Error::new_spanned(
attr,
"cannot have multiple occurrences of `type_fragment` attribute",
));
}
self.is_type_fragment = true;
Ok(())
}
fn try_merge(&mut self, meta: ParseNestedMeta<'_>, attr: &syn::Attribute) -> syn::Result<()> {
if meta.path.is_ident("skip_redaction") {
if meta.has_value() {
return Err(meta.error("`skip_redaction` attribute doesn't expect a value"));
}
return self.set_skip_redaction(attr);
}
if meta.path.is_ident("type_fragment") {
if meta.has_value() {
return Err(meta.error("`type_fragment` attribute doesn't expect a value"));
}
return self.set_type_fragment(attr);
}
Err(meta.error("unsupported `ruma_event` attribute"))
}
}
impl EventContentKind {
fn try_from_parts(
kind: Option<EventContentKindAttr>,
state_key_type: Option<syn::Type>,
unsigned_type: Option<syn::Type>,
has_custom_redacted: bool,
has_custom_possibly_redacted: bool,
ruma_events: &RumaEvents,
) -> syn::Result<Self> {
let kind = kind.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"missing `kind` attribute, \
add `#[ruma_event(type = \"m.event_type\", kind = Kind)]` \
below the event content derive",
)
})?;
let is_state = matches!(kind, EventContentKindAttr::Single(CommonEventKind::State));
let is_message_like =
matches!(kind, EventContentKindAttr::Single(CommonEventKind::MessageLike));
if let Some(state_key_type) = &state_key_type
&& !is_state
{
return Err(syn::Error::new_spanned(
state_key_type,
"the `state_key_type` attribute is only valid for the State kind",
));
}
if let Some(unsigned_type) = &unsigned_type
&& !is_state
{
return Err(syn::Error::new_spanned(
unsigned_type,
"the `unsigned_type` attribute is only valid for the State kind",
));
}
if has_custom_redacted && !is_state && !is_message_like {
return Err(syn::Error::new(
Span::call_site(),
"the `custom_redacted` attribute is only valid for the State and MessageLike kinds",
));
}
if has_custom_possibly_redacted && !is_state {
return Err(syn::Error::new(
Span::call_site(),
"the `custom_possibly_redacted` attribute is only valid for the State kind",
));
}
Ok(match kind {
EventContentKindAttr::Single(kind) => match kind {
CommonEventKind::GlobalAccountData => EventContentKind::GlobalAccountData,
CommonEventKind::RoomAccountData => EventContentKind::RoomAccountData,
CommonEventKind::EphemeralRoom => EventContentKind::EphemeralRoom,
CommonEventKind::MessageLike => {
EventContentKind::MessageLike { has_custom_redacted }
}
CommonEventKind::State => {
let state_key_type = state_key_type.ok_or_else(|| {
syn::Error::new(Span::call_site(), "missing `state_key_type` attribute")
})?;
let unsigned_type = unsigned_type.unwrap_or_else(|| {
parse_quote! {
#ruma_events::StateUnsigned<Self::PossiblyRedacted>
}
});
EventContentKind::State {
state_key_type,
unsigned_type,
has_custom_redacted,
has_custom_possibly_redacted,
}
}
CommonEventKind::ToDevice => EventContentKind::ToDevice,
},
EventContentKindAttr::BothAccountData => EventContentKind::BothAccountData,
})
}
}
#[derive(Clone, Copy)]
enum EventContentKindAttr {
Single(CommonEventKind),
BothAccountData,
}
impl Parse for EventContentKindAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let first_event_kind: CommonEventKind = input.parse()?;
let second_event_kind = input
.peek(syn::Token![+])
.then(|| {
let _: syn::Token![+] = input.parse()?;
input.parse::<CommonEventKind>()
})
.transpose()?;
match (first_event_kind, second_event_kind) {
(event_kind, None) => Ok(Self::Single(event_kind)),
(CommonEventKind::GlobalAccountData, Some(CommonEventKind::RoomAccountData))
| (CommonEventKind::RoomAccountData, Some(CommonEventKind::GlobalAccountData)) => {
Ok(Self::BothAccountData)
}
_ => Err(syn::Error::new(Span::call_site(), "only account data can have two kinds")),
}
}
}