common: Allow variable event types in EventContent derive
This commit is contained in:
parent
cf8f1b0e7e
commit
ef9c84716c
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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`
|
||||||
|
18
crates/ruma-common/tests/events/ui/10-content-wildcard.rs
Normal file
18
crates/ruma-common/tests/events/ui/10-content-wildcard.rs
Normal 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"
|
||||||
|
);
|
||||||
|
}
|
@ -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)?;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user