common: Allow variable event types in EventContent derive

This commit is contained in:
Jonas Platte 2022-03-14 18:54:02 +01:00 committed by Jonas Platte
parent cf8f1b0e7e
commit ef9c84716c
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
6 changed files with 115 additions and 16 deletions

View File

@ -4,4 +4,5 @@ fn ui() {
t.pass("tests/events/ui/01-content-sanity-check.rs"); t.pass("tests/events/ui/01-content-sanity-check.rs");
t.compile_fail("tests/events/ui/02-no-event-type.rs"); t.compile_fail("tests/events/ui/02-no-event-type.rs");
t.compile_fail("tests/events/ui/03-invalid-event-type.rs"); t.compile_fail("tests/events/ui/03-invalid-event-type.rs");
t.pass("tests/events/ui/10-content-wildcard.rs");
} }

View File

@ -1,19 +1,19 @@
error: no event type attribute found, add `#[ruma_event(type = "any.room.event", kind = Kind)]` below the event content derive error: no event type attribute found, add `#[ruma_event(type = "any.room.event", kind = Kind)]` below the event content derive
--> $DIR/03-invalid-event-type.rs:4:48 --> tests/events/ui/03-invalid-event-type.rs:4:48
| |
4 | #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] 4 | #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
| |
= note: this error originates in the derive macro `EventContent` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the derive macro `EventContent` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected one of: `type`, `kind`, `skip_redaction`, `custom_redacted` error: expected one of: `type`, `kind`, `skip_redaction`, `custom_redacted`, `type_fragment`
--> $DIR/03-invalid-event-type.rs:11:14 --> tests/events/ui/03-invalid-event-type.rs:11:14
| |
11 | #[ruma_event(event = "m.macro.test", kind = State)] 11 | #[ruma_event(event = "m.macro.test", kind = State)]
| ^^^^^ | ^^^^^
error: cannot find attribute `not_ruma_event` in this scope error: cannot find attribute `not_ruma_event` in this scope
--> $DIR/03-invalid-event-type.rs:5:3 --> tests/events/ui/03-invalid-event-type.rs:5:3
| |
5 | #[not_ruma_event(type = "m.macro.test", kind = State)] 5 | #[not_ruma_event(type = "m.macro.test", kind = State)]
| ^^^^^^^^^^^^^^ help: a derive helper attribute with a similar name exists: `ruma_event` | ^^^^^^^^^^^^^^ help: a derive helper attribute with a similar name exists: `ruma_event`

View File

@ -0,0 +1,18 @@
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[ruma_event(type = "m.macro.test.*", kind = GlobalAccountData)]
pub struct MacroTestContent {
#[ruma_event(type_fragment)]
pub frag: String,
}
fn main() {
use ruma_common::events::EventContent;
assert_eq!(
MacroTestContent { frag: "foo".to_owned() }.event_type().as_str(),
"m.macro.test.foo"
);
}

View File

@ -6,7 +6,7 @@ use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
DeriveInput, Ident, LitStr, Token, DeriveInput, Field, Ident, LitStr, Token,
}; };
use crate::util::m_prefix_name_to_type_name; use crate::util::m_prefix_name_to_type_name;
@ -20,6 +20,7 @@ mod kw {
syn::custom_keyword!(custom_redacted); syn::custom_keyword!(custom_redacted);
// The kind of event content this is. // The kind of event content this is.
syn::custom_keyword!(kind); syn::custom_keyword!(kind);
syn::custom_keyword!(type_fragment);
} }
/// Parses attributes for `*EventContent` derives. /// Parses attributes for `*EventContent` derives.
@ -38,6 +39,10 @@ enum EventMeta {
/// This attribute signals that the events redacted form is manually implemented and should not /// This attribute signals that the events redacted form is manually implemented and should not
/// be generated. /// be generated.
CustomRedacted, CustomRedacted,
/// The given field holds a part of the event type (replaces the `*` in a `m.foo.*` event
/// type).
TypeFragment,
} }
impl EventMeta { impl EventMeta {
@ -73,6 +78,9 @@ impl Parse for EventMeta {
} else if lookahead.peek(kw::custom_redacted) { } else if lookahead.peek(kw::custom_redacted) {
let _: kw::custom_redacted = input.parse()?; let _: kw::custom_redacted = input.parse()?;
Ok(EventMeta::CustomRedacted) Ok(EventMeta::CustomRedacted)
} else if lookahead.peek(kw::type_fragment) {
let _: kw::type_fragment = input.parse()?;
Ok(EventMeta::TypeFragment)
} else { } else {
Err(lookahead.error()) Err(lookahead.error())
} }
@ -149,7 +157,7 @@ pub fn expand_event_content(
let ident = &input.ident; let ident = &input.ident;
let fields = match &input.data { let fields = match &input.data {
syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, syn::Data::Struct(syn::DataStruct { fields, .. }) => fields.iter(),
_ => { _ => {
return Err(syn::Error::new( return Err(syn::Error::new(
Span::call_site(), Span::call_site(),
@ -158,15 +166,38 @@ pub fn expand_event_content(
} }
}; };
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.map_or(false, |k| k.is_account_data()) {
return Err(syn::Error::new_spanned(
event_type,
"only account data events may contain a `.*` suffix",
));
}
// We only generate redacted content structs for state and message-like events // We only generate redacted content structs for state and message-like events
let redacted_event_content = needs_redacted(&content_attr, event_kind) let redacted_event_content = needs_redacted(&content_attr, event_kind)
.then(|| { .then(|| {
generate_redacted_event_content(ident, fields, event_type, event_kind, ruma_common) generate_redacted_event_content(
ident,
fields.clone(),
event_type,
event_kind,
ruma_common,
)
}) })
.transpose()?; .transpose()?;
let event_content_impl = let event_content_impl =
generate_event_content_impl(ident, event_type, event_kind, ruma_common)?; generate_event_content_impl(ident, fields, event_type, event_kind, ruma_common)?;
let static_event_content_impl = event_kind let static_event_content_impl = event_kind
.map(|k| generate_static_event_content_impl(ident, k, false, event_type, ruma_common)); .map(|k| generate_static_event_content_impl(ident, k, false, event_type, ruma_common));
let type_aliases = event_kind let type_aliases = event_kind
@ -181,13 +212,18 @@ pub fn expand_event_content(
}) })
} }
fn generate_redacted_event_content( fn generate_redacted_event_content<'a>(
ident: &Ident, ident: &Ident,
fields: &syn::Fields, fields: impl Iterator<Item = &'a Field>,
event_type: &LitStr, event_type: &LitStr,
event_kind: Option<EventKind>, event_kind: Option<EventKind>,
ruma_common: &TokenStream, ruma_common: &TokenStream,
) -> syn::Result<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_common::exports::serde }; let serde = quote! { #ruma_common::exports::serde };
let serde_json = quote! { #ruma_common::exports::serde_json }; let serde_json = quote! { #ruma_common::exports::serde_json };
@ -195,7 +231,6 @@ fn generate_redacted_event_content(
let redacted_ident = format_ident!("Redacted{}", ident); let redacted_ident = format_ident!("Redacted{}", ident);
let kept_redacted_fields: Vec<_> = fields let kept_redacted_fields: Vec<_> = fields
.iter()
.map(|f| { .map(|f| {
let mut keep_field = false; let mut keep_field = false;
let attrs = f let attrs = f
@ -217,7 +252,7 @@ fn generate_redacted_event_content(
.collect::<syn::Result<_>>()?; .collect::<syn::Result<_>>()?;
if keep_field { if keep_field {
Ok(Some(syn::Field { attrs, ..f.clone() })) Ok(Some(Field { attrs, ..f.clone() }))
} else { } else {
Ok(None) Ok(None)
} }
@ -260,8 +295,13 @@ fn generate_redacted_event_content(
} }
}); });
let redacted_event_content = let redacted_event_content = generate_event_content_impl(
generate_event_content_impl(&redacted_ident, event_type, event_kind, ruma_common)?; &redacted_ident,
kept_redacted_fields.iter(),
event_type,
event_kind,
ruma_common,
)?;
let static_event_content_impl = event_kind.map(|k| { let static_event_content_impl = event_kind.map(|k| {
generate_static_event_content_impl(&redacted_ident, k, true, event_type, ruma_common) generate_static_event_content_impl(&redacted_ident, k, true, event_type, ruma_common)
@ -371,8 +411,9 @@ fn generate_event_type_aliases(
Ok(type_aliases) Ok(type_aliases)
} }
fn generate_event_content_impl( fn generate_event_content_impl<'a>(
ident: &Ident, ident: &Ident,
mut fields: impl Iterator<Item = &'a Field>,
event_type: &LitStr, event_type: &LitStr,
event_kind: Option<EventKind>, event_kind: Option<EventKind>,
ruma_common: &TokenStream, ruma_common: &TokenStream,
@ -387,7 +428,40 @@ fn generate_event_content_impl(
let i = kind.to_event_type_enum(); let i = kind.to_event_type_enum();
event_type_ty_decl = None; event_type_ty_decl = None;
event_type_ty = quote! { #ruma_common::events::#i }; event_type_ty = quote! { #ruma_common::events::#i };
event_type_fn_impl = quote! { ::std::convert::From::from(#event_type) }; event_type_fn_impl = match event_type.value().strip_suffix(".*") {
Some(type_prefix) => {
let type_fragment_field = fields
.find_map(|f| {
f.attrs.iter().filter(|a| a.path.is_ident("ruma_event")).find_map(|a| {
match a.parse_args() {
Ok(EventMeta::TypeFragment) => Some(Ok(f)),
Ok(_) => None,
Err(e) => Some(Err(e)),
}
})
})
.transpose()?;
let f = type_fragment_field
.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");
let format = type_prefix.to_owned() + ".{}";
quote! {
::std::convert::From::from(::std::format!(#format, self.#f))
}
}
None => quote! { ::std::convert::From::from(#event_type) },
};
} }
None => { None => {
let camel_case_type_name = m_prefix_name_to_type_name(event_type)?; let camel_case_type_name = m_prefix_name_to_type_name(event_type)?;

View File

@ -127,6 +127,10 @@ impl IdentFragment for EventKindVariation {
} }
impl EventKind { impl EventKind {
pub fn is_account_data(self) -> bool {
matches!(self, Self::GlobalAccountData | Self::RoomAccountData)
}
pub fn try_to_event_ident(self, var: EventKindVariation) -> Option<Ident> { pub fn try_to_event_ident(self, var: EventKindVariation) -> Option<Ident> {
use EventKindVariation as V; use EventKindVariation as V;

View File

@ -47,6 +47,8 @@ pub(crate) fn m_prefix_name_to_type_name(name: &LitStr) -> syn::Result<Ident> {
})?; })?;
let s: String = name let s: String = name
.strip_suffix(".*")
.unwrap_or(name)
.split(&['.', '_'] as &[char]) .split(&['.', '_'] as &[char])
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..]) .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
.collect(); .collect();