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