Refactor the input parsing of event_enums!

This commit is contained in:
Ragotzy.devin 2020-07-13 09:31:15 -04:00 committed by GitHub
parent 5e428ac95a
commit 1db4898cde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 323 additions and 312 deletions

View File

@ -16,6 +16,7 @@ version = "0.21.3"
syn = { version = "1.0.31", features = ["full"] }
quote = "1.0.7"
proc-macro2 = "1.0.18"
matches = "0.1.8"
[lib]
proc-macro = true

View File

@ -1,7 +1,7 @@
//! Implementations of the MessageEventContent and StateEventContent derive macro.
use proc_macro2::{Span, TokenStream};
use quote::quote;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream},
DeriveInput, Ident, LitStr, Token,
@ -77,7 +77,7 @@ pub fn expand_event_content(input: &DeriveInput, emit_redacted: bool) -> syn::Re
let redacted = if emit_redacted && needs_redacted(input) {
let doc = format!("The payload for a redacted `{}`", ident);
let redacted_ident = quote::format_ident!("Redacted{}", ident);
let redacted_ident = format_ident!("Redacted{}", ident);
let kept_redacted_fields = if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(syn::FieldsNamed { named, .. }),
..
@ -233,7 +233,7 @@ pub fn expand_message_event_content(input: &DeriveInput) -> syn::Result<TokenStr
let room_ev_content = expand_room_event_content(input)?;
let redacted_marker_trait = if needs_redacted(input) {
let ident = quote::format_ident!("Redacted{}", &ident);
let ident = format_ident!("Redacted{}", &ident);
quote! {
impl ::ruma_events::RedactedMessageEventContent for #ident { }
}
@ -256,7 +256,7 @@ pub fn expand_state_event_content(input: &DeriveInput) -> syn::Result<TokenStrea
let room_ev_content = expand_room_event_content(input)?;
let redacted_marker_trait = if needs_redacted(input) {
let ident = quote::format_ident!("Redacted{}", input.ident);
let ident = format_ident!("Redacted{}", input.ident);
quote! {
impl ::ruma_events::RedactedStateEventContent for #ident { }
}

View File

@ -1,126 +1,62 @@
//! Implementation of event enum and event content enum macros.
use matches::matches;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use quote::{format_ident, quote};
use syn::{
parse::{self, Parse, ParseStream},
Attribute, Expr, ExprLit, Ident, Lit, LitStr, Token,
};
use crate::event_names::{
ANY_BASIC_EVENT, ANY_EPHEMERAL_EVENT, ANY_MESSAGE_EVENT, ANY_STATE_EVENT,
ANY_STRIPPED_STATE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_SYNC_STATE_EVENT, ANY_TO_DEVICE_EVENT,
REDACTED_MESSAGE_EVENT, REDACTED_STATE_EVENT, REDACTED_STRIPPED_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT, REDACTED_SYNC_STATE_EVENT,
};
fn is_non_stripped_room_event(kind: &EventKind, var: &EventKindVariation) -> bool {
matches!(kind, EventKind::Message(_) | EventKind::State(_))
&& !matches!(var, EventKindVariation::Stripped | EventKindVariation::RedactedStripped)
}
// Arrays of event enum names grouped by a field they share in common.
const ROOM_EVENT_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
REDACTED_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT,
REDACTED_SYNC_STATE_EVENT,
];
fn has_prev_content_field(kind: &EventKind, var: &EventKindVariation) -> bool {
matches!(kind, EventKind::State(_))
&& matches!(var, EventKindVariation::Full | EventKindVariation::Stub)
}
const ROOM_ID_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_EPHEMERAL_EVENT,
REDACTED_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
];
const EVENT_ID_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
REDACTED_SYNC_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT,
REDACTED_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
];
const SENDER_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
ANY_TO_DEVICE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
ANY_STRIPPED_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
REDACTED_STATE_EVENT,
REDACTED_STRIPPED_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT,
REDACTED_SYNC_STATE_EVENT,
];
const PREV_CONTENT_KIND: &[&str] = &[ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT];
const STATE_KEY_KIND: &[&str] = &[
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
ANY_STRIPPED_STATE_EVENT,
REDACTED_SYNC_STATE_EVENT,
REDACTED_STRIPPED_STATE_EVENT,
REDACTED_STATE_EVENT,
];
const REDACTED_EVENT_KIND: &[&str] = &[
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
ANY_STRIPPED_STATE_EVENT,
ANY_MESSAGE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
];
type EventKindFn = fn(&EventKind, &EventKindVariation) -> bool;
/// This const is used to generate the accessor methods for the `Any*Event` enums.
///
/// DO NOT alter the field names unless the structs in `ruma_events::event_kinds` have changed.
const EVENT_FIELDS: &[(&str, &[&str])] = &[
("origin_server_ts", ROOM_EVENT_KIND),
("room_id", ROOM_ID_KIND),
("event_id", EVENT_ID_KIND),
("sender", SENDER_KIND),
("state_key", STATE_KEY_KIND),
("unsigned", ROOM_EVENT_KIND),
const EVENT_FIELDS: &[(&str, EventKindFn)] = &[
("origin_server_ts", is_non_stripped_room_event),
("room_id", |kind, var| {
matches!(kind, EventKind::Message(_) | EventKind::State(_))
&& matches!(var, EventKindVariation::Full | EventKindVariation::Redacted)
}),
("event_id", is_non_stripped_room_event),
(
"sender",
|kind, _| matches!(kind, EventKind::Message(_) | EventKind::State(_) | EventKind::ToDevice(_)),
),
("state_key", |kind, _| matches!(kind, EventKind::State(_))),
("unsigned", is_non_stripped_room_event),
];
/// Create a content enum from `EventEnumInput`.
pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
let ident = &input.name;
let name = &input.name;
let events = &input.events;
let attrs = &input.attrs;
let variants = events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
let event_enum = expand_any_enum_with_deserialize(&input, ident)?;
let needs_event_content = ident == ANY_STATE_EVENT
|| ident == ANY_MESSAGE_EVENT
|| ident == ANY_TO_DEVICE_EVENT
|| ident == ANY_EPHEMERAL_EVENT
|| ident == ANY_BASIC_EVENT;
let needs_event_stub =
ident == ANY_STATE_EVENT || ident == ANY_MESSAGE_EVENT || ident == ANY_EPHEMERAL_EVENT;
let needs_stripped_event = ident == ANY_STATE_EVENT;
let event_enum =
expand_any_with_deser(name, events, attrs, &variants, &EventKindVariation::Full);
let event_stub_enum =
if needs_event_stub { expand_stub_enum(&input)? } else { TokenStream::new() };
expand_any_with_deser(name, events, attrs, &variants, &EventKindVariation::Stub);
let event_stripped_enum =
if needs_stripped_event { expand_stripped_enum(&input)? } else { TokenStream::new() };
expand_any_with_deser(name, events, attrs, &variants, &EventKindVariation::Stripped);
let redacted_event_enums = if needs_redacted(ident) {
expand_any_redacted_enum_with_deserialize(&input, ident)?
} else {
TokenStream::new()
};
let redacted_event_enums = expand_any_redacted(name, events, attrs, &variants);
let event_content_enum =
if needs_event_content { expand_content_enum(&input, ident)? } else { TokenStream::new() };
let event_content_enum = expand_content_enum(name, events, attrs, &variants);
Ok(quote! {
#event_enum
@ -135,18 +71,73 @@ pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
})
}
/// Create a "stub" enum from `EventEnumInput`.
pub fn expand_stub_enum(input: &EventEnumInput) -> syn::Result<TokenStream> {
let ident = Ident::new(&format!("{}Stub", input.name.to_string()), input.name.span());
expand_any_enum_with_deserialize(input, &ident)
fn generate_event_idents(kind: &EventKind, var: &EventKindVariation) -> Option<(Ident, Ident)> {
Some((kind.to_event_ident(var)?, kind.to_event_enum_ident(var)?))
}
/// Create a "stripped" enum from `EventEnumInput`.
pub fn expand_stripped_enum(input: &EventEnumInput) -> syn::Result<TokenStream> {
let ident = Ident::new("AnyStrippedStateEventStub", input.name.span());
fn expand_any_with_deser(
kind: &EventKind,
events: &[LitStr],
attrs: &[Attribute],
variants: &[Ident],
var: &EventKindVariation,
) -> Option<TokenStream> {
// If the event cannot be generated this bails out returning None which is rendered the same
// as an empty `TokenStream`. This is effectively the check if the given input generates
// a valid event enum.
let (event_struct, ident) = generate_event_idents(kind, var)?;
expand_any_enum_with_deserialize(input, &ident)
let content =
events.iter().map(|event| to_event_path(event, &event_struct)).collect::<Vec<_>>();
let (custom_variant, custom_deserialize) = expand_custom_variant(&event_struct, var);
let any_enum = quote! {
#( #attrs )*
#[derive(Clone, Debug, ::serde::Serialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum #ident {
#(
#[doc = #events]
#variants(#content),
)*
#custom_variant
}
};
let event_deserialize_impl = quote! {
impl<'de> ::serde::de::Deserialize<'de> for #ident {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
use ::serde::de::Error as _;
let json = Box::<::serde_json::value::RawValue>::deserialize(deserializer)?;
let ::ruma_events::EventDeHelper { ev_type, .. } = ::ruma_events::from_raw_json_value(&json)?;
match ev_type.as_str() {
#(
#events => {
let event = ::serde_json::from_str::<#content>(json.get()).map_err(D::Error::custom)?;
Ok(Self::#variants(event))
},
)*
#custom_deserialize
}
}
}
};
let field_accessor_impl = accessor_methods(kind, var, &variants);
Some(quote! {
#any_enum
#field_accessor_impl
#event_deserialize_impl
})
}
/// Generates the 3 redacted state enums, 2 redacted message enums,
@ -154,58 +145,52 @@ pub fn expand_stripped_enum(input: &EventEnumInput) -> syn::Result<TokenStream>
///
/// No content enums are generated since no part of the API deals with
/// redacted event's content. There are only five state variants that contain content.
fn expand_any_redacted_enum_with_deserialize(
input: &EventEnumInput,
ident: &Ident,
) -> syn::Result<TokenStream> {
let name = ident.to_string().trim_start_matches("Any").to_string();
fn expand_any_redacted(
kind: &EventKind,
events: &[LitStr],
attrs: &[Attribute],
variants: &[Ident],
) -> TokenStream {
use EventKindVariation::*;
let redacted_enums_deserialize = if ident.to_string().contains("State") {
let ident = Ident::new(&format!("AnyRedacted{}", name), ident.span());
let full = expand_any_enum_with_deserialize(input, &ident)?;
let ident = Ident::new(&format!("AnyRedacted{}Stub", name), ident.span());
let stub = expand_any_enum_with_deserialize(input, &ident)?;
let ident = Ident::new(&format!("AnyRedactedStripped{}Stub", name), ident.span());
let stripped = expand_any_enum_with_deserialize(input, &ident)?;
if kind.is_state() {
let state_full = expand_any_with_deser(kind, events, attrs, variants, &Redacted);
let state_stub = expand_any_with_deser(kind, events, attrs, variants, &RedactedStub);
let state_stripped =
expand_any_with_deser(kind, events, attrs, variants, &RedactedStripped);
quote! {
#full
#state_full
#stub
#state_stub
#stripped
#state_stripped
}
} else if kind.is_message() {
let message_full = expand_any_with_deser(kind, events, attrs, variants, &Redacted);
let message_stub = expand_any_with_deser(kind, events, attrs, variants, &RedactedStub);
quote! {
#message_full
#message_stub
}
} else {
let ident = Ident::new(&format!("AnyRedacted{}", name), ident.span());
let full = expand_any_enum_with_deserialize(input, &ident)?;
let ident = Ident::new(&format!("AnyRedacted{}Stub", name), ident.span());
let stub = expand_any_enum_with_deserialize(input, &ident)?;
quote! {
#full
#stub
TokenStream::new()
}
};
Ok(quote! {
#redacted_enums_deserialize
})
}
/// Create a content enum from `EventEnumInput`.
pub fn expand_content_enum(input: &EventEnumInput, ident: &Ident) -> syn::Result<TokenStream> {
let attrs = &input.attrs;
let ident = Ident::new(&format!("{}Content", ident), ident.span());
let event_type_str = &input.events;
fn expand_content_enum(
kind: &EventKind,
events: &[LitStr],
attrs: &[Attribute],
variants: &[Ident],
) -> TokenStream {
let ident = kind.to_content_enum();
let event_type_str = events;
let variants = input.events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
let content = input.events.iter().map(to_event_content_path).collect::<Vec<_>>();
let content = events.iter().map(to_event_content_path).collect::<Vec<_>>();
let content_enum = quote! {
#( #attrs )*
@ -248,81 +233,24 @@ pub fn expand_content_enum(input: &EventEnumInput, ident: &Ident) -> syn::Result
}
};
let marker_trait_impls = marker_traits(&ident);
let marker_trait_impls = marker_traits(&kind);
Ok(quote! {
quote! {
#content_enum
#event_content_impl
#marker_trait_impls
})
}
fn expand_any_enum_with_deserialize(
input: &EventEnumInput,
ident: &Ident,
) -> syn::Result<TokenStream> {
let attrs = &input.attrs;
let event_type_str = &input.events;
let event_struct = Ident::new(&ident.to_string().replace("Any", ""), ident.span());
let variants = input.events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
let content =
input.events.iter().map(|event| to_event_path(event, &event_struct)).collect::<Vec<_>>();
let (custom_variant, custom_deserialize) = expand_custom_variant(ident, &event_struct);
let any_enum = quote! {
#( #attrs )*
#[derive(Clone, Debug, ::serde::Serialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum #ident {
#(
#[doc = #event_type_str]
#variants(#content),
)*
#custom_variant
}
};
let event_deserialize_impl = quote! {
impl<'de> ::serde::de::Deserialize<'de> for #ident {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
use ::serde::de::Error as _;
let json = Box::<::serde_json::value::RawValue>::deserialize(deserializer)?;
let ::ruma_events::EventDeHelper { ev_type, .. } = ::ruma_events::from_raw_json_value(&json)?;
match ev_type.as_str() {
#(
#event_type_str => {
let event = ::serde_json::from_str::<#content>(json.get()).map_err(D::Error::custom)?;
Ok(Self::#variants(event))
},
)*
#custom_deserialize
}
}
}
};
let field_accessor_impl = accessor_methods(ident, &variants);
fn expand_custom_variant(
event_struct: &Ident,
var: &EventKindVariation,
) -> (TokenStream, TokenStream) {
use EventKindVariation::*;
Ok(quote! {
#any_enum
#field_accessor_impl
#event_deserialize_impl
})
}
fn expand_custom_variant(ident: &Ident, event_struct: &Ident) -> (TokenStream, TokenStream) {
if ident.to_string().contains("Redacted") {
if matches!(var, Redacted | RedactedStub | RedactedStripped) {
(
quote! {
/// A redacted event not defined by the Matrix specification
@ -358,37 +286,46 @@ fn expand_custom_variant(ident: &Ident, event_struct: &Ident) -> (TokenStream, T
}
}
fn marker_traits(ident: &Ident) -> TokenStream {
match ident.to_string().as_str() {
"AnyStateEventContent" => quote! {
fn marker_traits(kind: &EventKind) -> TokenStream {
let ident = kind.to_content_enum();
match kind {
EventKind::State(_) => quote! {
impl ::ruma_events::RoomEventContent for #ident {}
impl ::ruma_events::StateEventContent for #ident {}
},
"AnyMessageEventContent" => quote! {
EventKind::Message(_) => quote! {
impl ::ruma_events::RoomEventContent for #ident {}
impl ::ruma_events::MessageEventContent for #ident {}
},
"AnyEphemeralRoomEventContent" => quote! {
EventKind::Ephemeral(_) => quote! {
impl ::ruma_events::EphemeralRoomEventContent for #ident {}
},
"AnyBasicEventContent" => quote! {
EventKind::Basic(_) => quote! {
impl ::ruma_events::BasicEventContent for #ident {}
},
_ => TokenStream::new(),
}
}
fn accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
if ident.to_string().contains("Redacted") {
return redacted_accessor_methods(ident, variants);
fn accessor_methods(
kind: &EventKind,
var: &EventKindVariation,
variants: &[Ident],
) -> Option<TokenStream> {
use EventKindVariation::*;
let ident = kind.to_event_enum_ident(var)?;
// matching `EventKindVariation`s
if let Redacted | RedactedStub | RedactedStripped = var {
return redacted_accessor_methods(kind, var, variants);
}
let fields = EVENT_FIELDS
let methods = EVENT_FIELDS
.iter()
.map(|(name, has_field)| generate_accessor(name, ident, *has_field, variants));
.map(|(name, has_field)| generate_accessor(name, kind, var, *has_field, variants));
let any_content = ident.to_string().replace("Stub", "").replace("Stripped", "");
let content_enum = Ident::new(&format!("{}Content", any_content), ident.span());
let content_enum = kind.to_content_enum();
let content = quote! {
/// Returns the any content enum for this event.
@ -402,7 +339,7 @@ fn accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
}
};
let prev_content = if PREV_CONTENT_KIND.contains(&ident.to_string().as_str()) {
let prev_content = if has_prev_content_field(kind, var) {
quote! {
/// Returns the any content enum for this events prev_content.
pub fn prev_content(&self) -> Option<#content_enum> {
@ -422,35 +359,42 @@ fn accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
TokenStream::new()
};
quote! {
Some(quote! {
impl #ident {
#content
#prev_content
#( #fields )*
}
#( #methods )*
}
})
}
/// Redacted events do NOT generate `content` or `prev_content` methods like
/// un-redacted events; otherwise, they are the same.
fn redacted_accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
let fields = EVENT_FIELDS
fn redacted_accessor_methods(
kind: &EventKind,
var: &EventKindVariation,
variants: &[Ident],
) -> Option<TokenStream> {
// this will never fail as it is called in `expand_any_with_deser`.
let ident = kind.to_event_enum_ident(var).unwrap();
let methods = EVENT_FIELDS
.iter()
.map(|(name, has_field)| generate_accessor(name, ident, *has_field, variants));
.map(|(name, has_field)| generate_accessor(name, kind, var, *has_field, variants));
quote! {
Some(quote! {
impl #ident {
#( #fields )*
}
#( #methods )*
}
})
}
fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream {
let span = name.span();
let name = name.value();
// There is no need to give a good compiler error as `to_camel_case` is called first.
assert_eq!(&name[..2], "m.");
let path = name[2..].split('.').collect::<Vec<_>>();
@ -473,15 +417,15 @@ fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream {
quote! { ::ruma_events::room::redaction::#redaction }
}
"ToDeviceEvent" | "StateEventStub" | "StrippedStateEventStub" | "MessageEventStub" => {
let content = Ident::new(&format!("{}EventContent", event), span);
let content = format_ident!("{}EventContent", event);
quote! { ::ruma_events::#struct_name<::ruma_events::#( #path )::*::#content> }
}
struct_str if struct_str.contains("Redacted") => {
let content = Ident::new(&format!("Redacted{}EventContent", event), span);
let content = format_ident!("Redacted{}EventContent", event);
quote! { ::ruma_events::#struct_name<::ruma_events::#( #path )::*::#content> }
}
_ => {
let event_name = Ident::new(&format!("{}Event", event), span);
let event_name = format_ident!("{}Event", event);
quote! { ::ruma_events::#( #path )::*::#event_name }
}
}
@ -491,6 +435,7 @@ fn to_event_content_path(name: &LitStr) -> TokenStream {
let span = name.span();
let name = name.value();
// There is no need to give a good compiler error as `to_camel_case` is called first.
assert_eq!(&name[..2], "m.");
let path = name[2..].split('.').collect::<Vec<_>>();
@ -501,7 +446,7 @@ fn to_event_content_path(name: &LitStr) -> TokenStream {
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
.collect::<String>();
let content_str = Ident::new(&format!("{}EventContent", event), span);
let content_str = format_ident!("{}EventContent", event);
let path = path.iter().map(|s| Ident::new(s, span));
quote! {
::ruma_events::#( #path )::*::#content_str
@ -510,7 +455,7 @@ fn to_event_content_path(name: &LitStr) -> TokenStream {
/// Splits the given `event_type` string on `.` and `_` removing the `m.room.` then
/// camel casing to give the `Event` struct name.
pub(crate) fn to_camel_case(name: &LitStr) -> syn::Result<Ident> {
fn to_camel_case(name: &LitStr) -> syn::Result<Ident> {
let span = name.span();
let name = name.value();
@ -531,11 +476,12 @@ pub(crate) fn to_camel_case(name: &LitStr) -> syn::Result<Ident> {
fn generate_accessor(
name: &str,
ident: &Ident,
event_kind_list: &[&str],
kind: &EventKind,
var: &EventKindVariation,
is_event_kind: EventKindFn,
variants: &[Ident],
) -> TokenStream {
if event_kind_list.contains(&ident.to_string().as_str()) {
if is_event_kind(kind, var) {
let field_type = field_return_type(name);
let name = Ident::new(name, Span::call_site());
@ -556,11 +502,6 @@ fn generate_accessor(
}
}
/// Returns true if the `ident` is a state or message event.
fn needs_redacted(ident: &Ident) -> bool {
REDACTED_EVENT_KIND.contains(&ident.to_string().as_str())
}
fn field_return_type(name: &str) -> TokenStream {
match name {
"origin_server_ts" => quote! { ::std::time::SystemTime },
@ -575,34 +516,127 @@ fn field_return_type(name: &str) -> TokenStream {
/// Custom keywords for the `event_enum!` macro
mod kw {
syn::custom_keyword!(name);
syn::custom_keyword!(kind);
syn::custom_keyword!(events);
}
// If the variants of this enum change `to_event_path` needs to be updated as well.
enum EventKindVariation {
Full,
Stub,
Stripped,
Redacted,
RedactedStub,
RedactedStripped,
}
// If the variants of this enum change `to_event_path` needs to be updated as well.
enum EventKind {
Basic(Ident),
Ephemeral(Ident),
Message(Ident),
State(Ident),
ToDevice(Ident),
}
impl EventKind {
fn is_state(&self) -> bool {
matches!(self, Self::State(_))
}
fn is_message(&self) -> bool {
matches!(self, Self::Message(_))
}
fn to_event_ident(&self, var: &EventKindVariation) -> Option<Ident> {
use EventKindVariation::*;
match (self, var) {
// all `EventKind`s are valid event structs and event enums.
(_, Full) => Some(format_ident!("{}Event", self.get_ident())),
(Self::Ephemeral(i), Stub) | (Self::Message(i), Stub) | (Self::State(i), Stub) => {
Some(format_ident!("{}EventStub", i))
}
(Self::State(i), Stripped) => Some(format_ident!("Stripped{}EventStub", i)),
(Self::Message(i), Redacted) | (Self::State(i), Redacted) => {
Some(format_ident!("Redacted{}Event", i))
}
(Self::Message(i), RedactedStub) | (Self::State(i), RedactedStub) => {
Some(format_ident!("Redacted{}EventStub", i))
}
(Self::State(i), RedactedStripped) => {
Some(format_ident!("RedactedStripped{}EventStub", i))
}
_ => None,
}
}
fn to_event_enum_ident(&self, var: &EventKindVariation) -> Option<Ident> {
Some(format_ident!("Any{}", self.to_event_ident(var)?))
}
/// `Any[kind]EventContent`
fn to_content_enum(&self) -> Ident {
format_ident!("Any{}EventContent", self.get_ident())
}
fn get_ident(&self) -> &Ident {
match self {
EventKind::Basic(i)
| EventKind::Ephemeral(i)
| EventKind::Message(i)
| EventKind::State(i)
| EventKind::ToDevice(i) => i,
}
}
}
impl Parse for EventKind {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<Ident>()?;
Ok(match ident.to_string().as_str() {
"Basic" => EventKind::Basic(ident),
"EphemeralRoom" => EventKind::Ephemeral(ident),
"Message" => EventKind::Message(ident),
"State" => EventKind::State(ident),
"ToDevice" => EventKind::ToDevice(ident),
id => {
return Err(syn::Error::new(
input.span(),
format!(
"valid event kinds are Basic, EphemeralRoom, Message, State, ToDevice found `{}`",
id
),
));
}
})
}
}
/// The entire `event_enum!` macro structure directly as it appears in the source code.
pub struct EventEnumInput {
/// Outer attributes on the field, such as a docstring.
pub attrs: Vec<Attribute>,
attrs: Vec<Attribute>,
/// The name of the event.
pub name: Ident,
name: EventKind,
/// An array of valid matrix event types. This will generate the variants of the event type "name".
/// There needs to be a corresponding variant in `ruma_events::EventType` for
/// this event (converted to a valid Rust-style type name by stripping `m.`, replacing the
/// remaining dots by underscores and then converting from snake_case to CamelCase).
pub events: Vec<LitStr>,
events: Vec<LitStr>,
}
impl Parse for EventEnumInput {
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
// "name" field
input.parse::<kw::name>()?;
input.parse::<kw::kind>()?;
input.parse::<Token![:]>()?;
// the name of our event enum
let name: Ident = input.parse()?;
let name = input.parse::<EventKind>()?;
input.parse::<Token![,]>()?;
// "events" field

View File

@ -1,43 +0,0 @@
//! The names of the `Any*Event` enums. The event_enum! macro uses these names to generate
//! certain code for certain enums. If the names change this is the one source of truth,
//! most comparisons and branching uses these constants.
#![allow(dead_code)]
// Those marked with (UNUSED) are not used but, left for completeness sake.
// If you change this please remove the (UNUSED) comment.
// State events
pub const ANY_STATE_EVENT: &str = "AnyStateEvent";
pub const ANY_SYNC_STATE_EVENT: &str = "AnyStateEventStub";
pub const ANY_STRIPPED_STATE_EVENT: &str = "AnyStrippedStateEventStub";
// Redacted state events
pub const REDACTED_STATE_EVENT: &str = "AnyRedactedStateEvent"; // (UNUSED)
pub const REDACTED_SYNC_STATE_EVENT: &str = "AnyRedactedStateEventStub"; // (UNUSED)
pub const REDACTED_STRIPPED_STATE_EVENT: &str = "AnyRedactedStrippedStateEventStub"; // (UNUSED)
// Message events
pub const ANY_MESSAGE_EVENT: &str = "AnyMessageEvent";
pub const ANY_SYNC_MESSAGE_EVENT: &str = "AnyMessageEventStub";
// Redacted message events
pub const REDACTED_MESSAGE_EVENT: &str = "AnyRedactedMessageEvent"; // (UNUSED)
pub const REDACTED_SYNC_MESSAGE_EVENT: &str = "AnyRedactedMessageEventStub"; // (UNUSED)
// Ephemeral events
pub const ANY_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEvent";
pub const ANY_SYNC_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEventStub"; // (UNUSED)
// Basic event
pub const ANY_BASIC_EVENT: &str = "AnyBasicEvent";
// To device event
pub const ANY_TO_DEVICE_EVENT: &str = "AnyToDeviceEvent";

View File

@ -22,7 +22,6 @@ use self::{
mod event;
mod event_content;
mod event_enum;
mod event_names;
/// Generates an enum to represent the various Matrix event types.
///

View File

@ -6,7 +6,7 @@ use crate::{from_raw_json_value, EventDeHelper};
event_enum! {
/// Any basic event.
name: AnyBasicEvent,
kind: Basic,
events: [
"m.direct",
"m.dummy",
@ -20,7 +20,7 @@ event_enum! {
event_enum! {
/// Any ephemeral room event.
name: AnyEphemeralRoomEvent,
kind: EphemeralRoom,
events: [
"m.fully_read",
"m.receipt",
@ -30,7 +30,7 @@ event_enum! {
event_enum! {
/// Any message event.
name: AnyMessageEvent,
kind: Message,
events: [
"m.call.answer",
"m.call.invite",
@ -46,7 +46,7 @@ event_enum! {
event_enum! {
/// Any state event.
name: AnyStateEvent,
kind: State,
events: [
"m.room.aliases",
"m.room.avatar",
@ -69,7 +69,7 @@ event_enum! {
event_enum! {
/// Any to-device event.
name: AnyToDeviceEvent,
kind: ToDevice,
events: [
"m.dummy",
"m.room_key",

View File

@ -20,6 +20,7 @@ fn ui() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/07-enum-sanity-check.rs");
t.compile_fail("tests/ui/08-enum-invalid-path.rs");
t.compile_fail("tests/ui/09-enum-invalid-kind.rs");
}
#[test]

View File

@ -2,7 +2,7 @@ use ruma_events_macros::event_enum;
event_enum! {
/// Any basic event.
name: AnyBasicEvent,
kind: Basic,
events: [
"m.direct",
"m.dummy",

View File

@ -1,14 +1,14 @@
use ruma_events_macros::event_enum;
event_enum! {
name: AnyStateEvent,
kind: State,
events: [
"m.not.a.path",
]
}
event_enum! {
name: AnyStateEvent,
kind: State,
events: [
"not.a.path",
]

View File

@ -0,0 +1,14 @@
use ruma_events_macros::event_enum;
event_enum! {
kind: NotReal,
events: [
"m.direct",
"m.dummy",
"m.ignored_user_list",
"m.push_rules",
"m.room_key",
]
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: valid event kinds are Basic, EphemeralRoom, Message, State, ToDevice found `NotReal`
--> $DIR/09-enum-invalid-kind.rs:4:18
|
4 | kind: NotReal,
| ^