Repository: ruma/ruma (Rust)

Issue:
Generate event *type* enums
It would be good if our `event_enum!` macro would generate `*EventType` enums in addition to event enums and event content enums, for use in routes such as `send_message_event` and `create_message_event`.

cc @DevinR528 who wrote almost all of the events macro code


Discussion:
Once these enums exist, we should also update the `.event_type()` getters on the event (content) enums to return them.

Ideally I'd want them to be returned as references, but that means the `_Custom` variant has to wrap `str`, which would be annoying for other usage. I guess this part required more design work still.
So instead of using `EventType` for these getters they would return `StateEventType` for `State` enums and so on. Are we removing the `EventType` enum or could there be conversions generated also?
Whether we should still have `EventType` depends on whether we keep `AnyEvent`, currently it is used in `ruma-appservice-api` but I'm not entirely sure whether it's correct.

If we keep it, it should probably be generated as well instead of maintained separately. But that runs into our event enum invocations currently being separate, which is also an issue if we want a flat `RoomEventType` enum (instead of a nested one akin to `AnyRoomEvent` / `AnySyncRoomEvent`). Hmmmm... 🤔



Patch:
diff --git a/crates/ruma-events-macros/src/event_enum.rs b/crates/ruma-events-macros/src/event_enum.rs
index 907d802858..3471d96f55 100644
--- a/crates/ruma-events-macros/src/event_enum.rs
+++ b/crates/ruma-events-macros/src/event_enum.rs
@@ -4,7 +4,7 @@ use proc_macro2::{Span, TokenStream};
 use quote::{format_ident, quote, ToTokens};
 use syn::{Attribute, Ident, LitStr};
 
-use crate::event_parse::{EventEnumEntry, EventEnumInput, EventKind, EventKindVariation};
+use crate::event_parse::{EventEnumDecl, EventEnumEntry, EventKind, EventKindVariation};
 
 fn is_non_stripped_room_event(kind: &EventKind, var: &EventKindVariation) -> bool {
     matches!(kind, EventKind::Message | EventKind::State)
@@ -43,7 +43,7 @@ const EVENT_FIELDS: &[(&str, EventKindFn)] = &[
 ];
 
 /// Create a content enum from `EventEnumInput`.
-pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
+pub fn expand_event_enum(input: &EventEnumDecl) -> syn::Result<TokenStream> {
     let ruma_events = crate::import_ruma_events();
 
     let name = &input.name;
@@ -969,13 +969,13 @@ fn field_return_type(
     }
 }
 
-struct EventEnumVariant {
+pub(crate) struct EventEnumVariant {
     pub attrs: Vec<Attribute>,
     pub ident: Ident,
 }
 
 impl EventEnumVariant {
-    fn to_tokens<T>(&self, prefix: Option<T>, with_attrs: bool) -> TokenStream
+    pub(crate) fn to_tokens<T>(&self, prefix: Option<T>, with_attrs: bool) -> TokenStream
     where
         T: ToTokens,
     {
@@ -993,21 +993,21 @@ impl EventEnumVariant {
         tokens
     }
 
-    fn decl(&self) -> TokenStream {
+    pub(crate) fn decl(&self) -> TokenStream {
         self.to_tokens::<TokenStream>(None, true)
     }
 
-    fn match_arm(&self, prefix: impl ToTokens) -> TokenStream {
+    pub(crate) fn match_arm(&self, prefix: impl ToTokens) -> TokenStream {
         self.to_tokens(Some(prefix), true)
     }
 
-    fn ctor(&self, prefix: impl ToTokens) -> TokenStream {
+    pub(crate) fn ctor(&self, prefix: impl ToTokens) -> TokenStream {
         self.to_tokens(Some(prefix), false)
     }
 }
 
 impl EventEnumEntry {
-    fn to_variant(&self) -> syn::Result<EventEnumVariant> {
+    pub(crate) fn to_variant(&self) -> syn::Result<EventEnumVariant> {
         let attrs = self.attrs.clone();
         let ident = to_camel_case(&self.ev_type)?;
         Ok(EventEnumVariant { attrs, ident })
diff --git a/crates/ruma-events-macros/src/event_parse.rs b/crates/ruma-events-macros/src/event_parse.rs
index 8097e09658..41372cae5e 100644
--- a/crates/ruma-events-macros/src/event_parse.rs
+++ b/crates/ruma-events-macros/src/event_parse.rs
@@ -5,7 +5,7 @@ use std::fmt;
 use proc_macro2::Span;
 use quote::format_ident;
 use syn::{
-    bracketed,
+    braced,
     parse::{self, Parse, ParseStream},
     Attribute, Ident, LitStr, Token,
 };
@@ -140,8 +140,8 @@ impl Parse for EventKind {
             "State" => EventKind::State,
             "ToDevice" => EventKind::ToDevice,
             id => {
-                return Err(syn::Error::new(
-                    input.span(),
+                return Err(syn::Error::new_spanned(
+                    ident,
                     format!(
                         "valid event kinds are GlobalAccountData, RoomAccountData, EphemeralRoom, \
                         Message, State, ToDevice found `{}`",
@@ -184,8 +184,19 @@ pub fn to_kind_variation(ident: &Ident) -> Option<(EventKind, EventKindVariation
     }
 }
 
+pub struct EventEnumEntry {
+    pub attrs: Vec<Attribute>,
+    pub ev_type: LitStr,
+}
+
+impl Parse for EventEnumEntry {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        Ok(Self { attrs: input.call(Attribute::parse_outer)?, ev_type: input.parse()? })
+    }
+}
+
 /// The entire `event_enum!` macro structure directly as it appears in the source code.
-pub struct EventEnumInput {
+pub struct EventEnumDecl {
     /// Outer attributes on the field, such as a docstring.
     pub attrs: Vec<Attribute>,
 
@@ -194,45 +205,33 @@ pub struct EventEnumInput {
 
     /// An array of valid matrix event types.
     ///
-    /// This will generate the variants of the event type "name". There needs to be a corresponding
+    /// This will generate the variants of the event type "kind". 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<EventEnumEntry>,
 }
 
-impl Parse for EventEnumInput {
-    fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
-        let attrs = input.call(Attribute::parse_outer)?;
-        // "name" field
-        let _: kw::kind = input.parse()?;
-        let _: Token![:] = input.parse()?;
-
-        // the name of our event enum
-        let name: EventKind = input.parse()?;
-        let _: Token![,] = input.parse()?;
-
-        // "events" field
-        let _: kw::events = input.parse()?;
-        let _: Token![:] = input.parse()?;
-
-        // an array of event names `["m.room.whatever", ...]`
-        let content;
-        bracketed!(content in input);
-        let events = content.parse_terminated::<_, Token![,]>(EventEnumEntry::parse)?;
-        let events = events.into_iter().collect();
-
-        Ok(Self { attrs, name, events })
-    }
-}
-
-pub struct EventEnumEntry {
-    pub attrs: Vec<Attribute>,
-    pub ev_type: LitStr,
+/// The entire `event_enum!` macro structure directly as it appears in the source code.
+pub struct EventEnumInput {
+    pub(crate) enums: Vec<EventEnumDecl>,
 }
 
-impl Parse for EventEnumEntry {
-    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
-        Ok(Self { attrs: input.call(Attribute::parse_outer)?, ev_type: input.parse()? })
+impl Parse for EventEnumInput {
+    fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
+        let mut enums = vec![];
+        while !input.is_empty() {
+            let attrs = input.call(Attribute::parse_outer)?;
+
+            let _: Token![enum] = input.parse()?;
+            let name: EventKind = input.parse()?;
+
+            let content;
+            braced!(content in input);
+            let events = content.parse_terminated::<_, Token![,]>(EventEnumEntry::parse)?;
+            let events = events.into_iter().collect();
+            enums.push(EventEnumDecl { attrs, name, events });
+        }
+        Ok(EventEnumInput { enums })
     }
 }
diff --git a/crates/ruma-events-macros/src/event_type.rs b/crates/ruma-events-macros/src/event_type.rs
new file mode 100644
index 0000000000..028de31c94
--- /dev/null
+++ b/crates/ruma-events-macros/src/event_type.rs
@@ -0,0 +1,176 @@
+use proc_macro2::{Span, TokenStream};
+use quote::{format_ident, quote};
+use syn::{Ident, LitStr};
+
+use crate::event_parse::{EventEnumEntry, EventEnumInput, EventKind};
+
+pub fn expand_event_type_enum(
+    input: EventEnumInput,
+    ruma_events: TokenStream,
+) -> syn::Result<TokenStream> {
+    let ruma_serde = quote! { #ruma_events::exports::ruma_serde };
+
+    let mut room: Vec<&Vec<EventEnumEntry>> = vec![];
+    let mut state: Vec<&Vec<EventEnumEntry>> = vec![];
+    let mut message: Vec<&Vec<EventEnumEntry>> = vec![];
+    let mut ephemeral: Vec<&Vec<EventEnumEntry>> = vec![];
+    let mut room_account: Vec<&Vec<EventEnumEntry>> = vec![];
+    let mut global_account: Vec<&Vec<EventEnumEntry>> = vec![];
+    let mut to_device: Vec<&Vec<EventEnumEntry>> = vec![];
+    for event in &input.enums {
+        match event.name {
+            EventKind::GlobalAccountData => global_account.push(&event.events),
+            EventKind::RoomAccountData => room_account.push(&event.events),
+            EventKind::Ephemeral => ephemeral.push(&event.events),
+            EventKind::Message => {
+                message.push(&event.events);
+                room.push(&event.events);
+            }
+            EventKind::State => {
+                state.push(&event.events);
+                room.push(&event.events);
+            }
+            EventKind::ToDevice => to_device.push(&event.events),
+            EventKind::Redaction => {}
+            EventKind::Presence => {}
+        }
+    }
+    let presence = vec![EventEnumEntry {
+        attrs: vec![],
+        ev_type: LitStr::new("m.presence", Span::call_site()),
+    }];
+    let mut all = input.enums.iter().map(|e| &e.events).collect::<Vec<_>>();
+    all.push(&presence);
+    let (all_event_types, all_str_ev_types) = generate_variants(&all)?;
+    let all =
+        generate_enum(format_ident!("EventType"), all_str_ev_types, all_event_types, &ruma_serde);
+
+    let (room_event_types, room_str_ev_types) = generate_variants(&room)?;
+    let room = generate_enum(
+        format_ident!("RoomEventType"),
+        room_str_ev_types,
+        room_event_types,
+        &ruma_serde,
+    );
+
+    let (state_event_types, state_str_ev_types) = generate_variants(&state)?;
+    let state = generate_enum(
+        format_ident!("StateEventType"),
+        state_str_ev_types,
+        state_event_types,
+        &ruma_serde,
+    );
+
+    let (message_event_types, message_str_ev_types) = generate_variants(&message)?;
+    let message = generate_enum(
+        format_ident!("MessageEventType"),
+        message_str_ev_types,
+        message_event_types,
+        &ruma_serde,
+    );
+
+    let (ephemeral_event_types, ephemeral_str_ev_types) = generate_variants(&ephemeral)?;
+    let ephemeral = generate_enum(
+        format_ident!("EphemeralRoomEventType"),
+        ephemeral_str_ev_types,
+        ephemeral_event_types,
+        &ruma_serde,
+    );
+
+    let (room_account_event_types, room_account_str_ev_types) = generate_variants(&room_account)?;
+    let room_account = generate_enum(
+        format_ident!("RoomAccountDataEventType"),
+        room_account_str_ev_types,
+        room_account_event_types,
+        &ruma_serde,
+    );
+
+    let (global_account_event_types, global_account_str_ev_types) =
+        generate_variants(&global_account)?;
+    let global_account = generate_enum(
+        format_ident!("GlobalAccountDataEventType"),
+        global_account_str_ev_types,
+        global_account_event_types,
+        &ruma_serde,
+    );
+
+    let (to_device_event_types, to_device_str_ev_types) = generate_variants(&to_device)?;
+    let to_device = generate_enum(
+        format_ident!("ToDeviceEventType"),
+        to_device_str_ev_types,
+        to_device_event_types,
+        &ruma_serde,
+    );
+
+    Ok(quote! {
+        #all
+        #room
+        #state
+        #message
+        #ephemeral
+        #room_account
+        #global_account
+        #to_device
+    })
+}
+
+fn generate_enum(
+    ident: Ident,
+    ev_type_strings: Vec<&LitStr>,
+    variants: Vec<TokenStream>,
+    ruma_serde: &TokenStream,
+) -> TokenStream {
+    let str_doc = format!("Creates a string slice from this `{}`.", ident);
+    let byte_doc = format!("Creates a byte slice from this `{}`.", ident);
+    let enum_doc = format!("The type of `{}` this is.", ident);
+    quote! {
+        #[doc = #enum_doc]
+        ///
+        /// This type can hold an arbitrary string. To check for events that are not available as a
+        /// documented variant here, use its string representation, obtained through `.as_str()`.
+        #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, #ruma_serde::StringEnum)]
+        pub enum #ident {
+            #(
+                #[doc = #ev_type_strings]
+                #[ruma_enum(rename = #ev_type_strings)]
+                #variants,
+            )*
+            #[doc(hidden)]
+            _Custom(String),
+        }
+
+        impl #ident {
+            #[doc = #str_doc]
+            pub fn as_str(&self) -> &str {
+                self.as_ref()
+            }
+
+            #[doc = #byte_doc]
+            pub fn as_bytes(&self) -> &[u8] {
+                self.as_str().as_bytes()
+            }
+        }
+    }
+}
+
+fn generate_variants<'a>(
+    input: &'a [&Vec<EventEnumEntry>],
+) -> syn::Result<(Vec<TokenStream>, Vec<&'a LitStr>)> {
+    let mut deduped: Vec<&EventEnumEntry> = vec![];
+    for item in input.iter().copied().flatten() {
+        if let Some(idx) = deduped.iter().position(|e| e.ev_type == item.ev_type) {
+            // If there is a variant without config attributes use that
+            if deduped[idx].attrs != item.attrs && item.attrs.is_empty() {
+                deduped[idx] = item;
+            }
+        } else {
+            deduped.push(item);
+        }
+    }
+    let event_types = deduped.iter().map(|e| &e.ev_type).collect();
+
+    let event_types_variants =
+        deduped.iter().map(|e| Ok(e.to_variant()?.decl())).collect::<syn::Result<Vec<_>>>()?;
+
+    Ok((event_types_variants, event_types))
+}
diff --git a/crates/ruma-events-macros/src/lib.rs b/crates/ruma-events-macros/src/lib.rs
index fc4fdf5134..9fb87562bb 100644
--- a/crates/ruma-events-macros/src/lib.rs
+++ b/crates/ruma-events-macros/src/lib.rs
@@ -8,6 +8,7 @@
 
 #![warn(missing_docs)]
 
+use event_parse::EventEnumInput;
 use proc_macro::TokenStream;
 use proc_macro2 as pm2;
 use proc_macro_crate::{crate_name, FoundCrate};
@@ -16,13 +17,15 @@ use syn::{parse_macro_input, DeriveInput, Ident};
 
 use self::{
     event::expand_event, event_content::expand_event_content, event_enum::expand_event_enum,
-    event_parse::EventEnumInput,
+    event_type::expand_event_type_enum,
 };
 
 mod event;
 mod event_content;
 mod event_enum;
 mod event_parse;
+mod event_type;
+
 /// Generates an enum to represent the various Matrix event types.
 ///
 /// This macro also implements the necessary traits for the type to serialize and deserialize
@@ -35,20 +38,40 @@ mod event_parse;
 /// use ruma_events_macros::event_enum;
 ///
 /// event_enum! {
-///     kind: ToDevice,
-///     events: [
+///     enum ToDevice {
 ///         "m.any.event",
 ///         "m.other.event",
-///     ]
+///     }
+///
+///     enum State {
+///         "m.more.events",
+///         "m.different.event",
+///     }
 /// }
 /// ```
-/// (The argument to `kind` has to be a valid identifier for `<EventKind as Parse>::parse`)
+/// (The enum name has to be a valid identifier for `<EventKind as Parse>::parse`)
 //// TODO: Change above (`<EventKind as Parse>::parse`) to [] after fully qualified syntax is
 //// supported:  https://github.com/rust-lang/rust/issues/74563
 #[proc_macro]
 pub fn event_enum(input: TokenStream) -> TokenStream {
+    let ruma_events = import_ruma_events();
+
     let event_enum_input = syn::parse_macro_input!(input as EventEnumInput);
-    expand_event_enum(event_enum_input).unwrap_or_else(syn::Error::into_compile_error).into()
+    let enums = event_enum_input
+        .enums
+        .iter()
+        .map(expand_event_enum)
+        .collect::<syn::Result<pm2::TokenStream>>();
+    let event_types = expand_event_type_enum(event_enum_input, ruma_events);
+    event_types
+        .and_then(|types| {
+            enums.map(|mut enums| {
+                enums.extend(types);
+                enums
+            })
+        })
+        .unwrap_or_else(syn::Error::into_compile_error)
+        .into()
 }
 
 /// Generates an implementation of `ruma_events::EventContent`.
diff --git a/crates/ruma-events/src/enums.rs b/crates/ruma-events/src/enums.rs
index 8de1816347..0db0f90cec 100644
--- a/crates/ruma-events/src/enums.rs
+++ b/crates/ruma-events/src/enums.rs
@@ -6,36 +6,26 @@ use crate::{from_raw_json_value, EventDeHelper};
 
 event_enum! {
     /// Any global account data event.
-    kind: GlobalAccountData,
-    events: [
+    enum GlobalAccountData {
         "m.direct",
         "m.ignored_user_list",
         "m.push_rules",
-    ]
-}
+    }
 
-event_enum! {
     /// Any room account data event.
-    kind: RoomAccountData,
-    events: [
+    enum RoomAccountData {
         "m.fully_read",
         "m.tag",
-    ]
-}
+    }
 
-event_enum! {
     /// Any ephemeral room event.
-    kind: EphemeralRoom,
-    events: [
+    enum EphemeralRoom {
         "m.receipt",
         "m.typing",
-    ]
-}
+    }
 
-event_enum! {
     /// Any message event.
-    kind: Message,
-    events: [
+    enum Message {
         "m.call.answer",
         "m.call.invite",
         "m.call.hangup",
@@ -69,13 +59,11 @@ event_enum! {
         "m.room.message.feedback",
         "m.room.redaction",
         "m.sticker",
-    ]
-}
+    }
+
 
-event_enum! {
     /// Any state event.
-    kind: State,
-    events: [
+    enum State {
         "m.policy.rule.room",
         "m.policy.rule.server",
         "m.policy.rule.user",
@@ -95,13 +83,10 @@ event_enum! {
         "m.room.third_party_invite",
         "m.room.tombstone",
         "m.room.topic",
-    ]
-}
+    }
 
-event_enum! {
     /// Any to-device event.
-    kind: ToDevice,
-    events: [
+    enum ToDevice {
         "m.dummy",
         "m.room_key",
         "m.room_key_request",
@@ -119,7 +104,7 @@ event_enum! {
         #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
         "m.key.verification.done",
         "m.room.encrypted",
-    ]
+    }
 }
 
 /// Any room event.
diff --git a/crates/ruma-events/src/event_type.rs b/crates/ruma-events/src/event_type.rs
deleted file mode 100644
index b2d754841f..0000000000
--- a/crates/ruma-events/src/event_type.rs
+++ /dev/null
@@ -1,278 +0,0 @@
-use ruma_serde::StringEnum;
-
-/// The type of an event.
-///
-/// This type can hold an arbitrary string. To check for events that are not available as a
-/// documented variant here, use its string representation, obtained through `.as_str()`.
-// FIXME: Add `m.foo.bar` or `m.foo_bar` as a naming scheme in StringEnum and remove most rename
-//        attributes.
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, StringEnum)]
-pub enum EventType {
-    /// m.call.answer
-    #[ruma_enum(rename = "m.call.answer")]
-    CallAnswer,
-
-    /// m.call.candidates
-    #[ruma_enum(rename = "m.call.candidates")]
-    CallCandidates,
-
-    /// m.call.hangup
-    #[ruma_enum(rename = "m.call.hangup")]
-    CallHangup,
-
-    /// m.call.invite
-    #[ruma_enum(rename = "m.call.invite")]
-    CallInvite,
-
-    /// m.direct
-    #[ruma_enum(rename = "m.direct")]
-    Direct,
-
-    /// m.dummy
-    #[ruma_enum(rename = "m.dummy")]
-    Dummy,
-
-    /// m.forwarded_room_key
-    #[ruma_enum(rename = "m.forwarded_room_key")]
-    ForwardedRoomKey,
-
-    /// m.fully_read
-    #[ruma_enum(rename = "m.fully_read")]
-    FullyRead,
-
-    /// m.key.verification.accept
-    #[ruma_enum(rename = "m.key.verification.accept")]
-    KeyVerificationAccept,
-
-    /// m.key.verification.cancel
-    #[ruma_enum(rename = "m.key.verification.cancel")]
-    KeyVerificationCancel,
-
-    /// m.key.verification.done
-    #[ruma_enum(rename = "m.key.verification.done")]
-    KeyVerificationDone,
-
-    /// m.key.verification.key
-    #[ruma_enum(rename = "m.key.verification.key")]
-    KeyVerificationKey,
-
-    /// m.key.verification.mac
-    #[ruma_enum(rename = "m.key.verification.mac")]
-    KeyVerificationMac,
-
-    /// m.key.verification.ready
-    #[ruma_enum(rename = "m.key.verification.ready")]
-    KeyVerificationReady,
-
-    /// m.key.verification.request
-    #[ruma_enum(rename = "m.key.verification.request")]
-    KeyVerificationRequest,
-
-    /// m.key.verification.start
-    #[ruma_enum(rename = "m.key.verification.start")]
-    KeyVerificationStart,
-
-    /// m.ignored_user_list
-    #[ruma_enum(rename = "m.ignored_user_list")]
-    IgnoredUserList,
-
-    /// m.policy.rule.room
-    #[ruma_enum(rename = "m.policy.rule.room")]
-    PolicyRuleRoom,
-
-    /// m.policy.rule.server
-    #[ruma_enum(rename = "m.policy.rule.server")]
-    PolicyRuleServer,
-
-    /// m.policy.rule.user
-    #[ruma_enum(rename = "m.policy.rule.user")]
-    PolicyRuleUser,
-
-    /// m.presence
-    #[ruma_enum(rename = "m.presence")]
-    Presence,
-
-    /// m.push_rules
-    #[ruma_enum(rename = "m.push_rules")]
-    PushRules,
-
-    /// m.receipt
-    #[ruma_enum(rename = "m.receipt")]
-    Receipt,
-
-    /// m.room.aliases
-    #[ruma_enum(rename = "m.room.aliases")]
-    RoomAliases,
-
-    /// m.room.avatar
-    #[ruma_enum(rename = "m.room.avatar")]
-    RoomAvatar,
-
-    /// m.room.canonical_alias
-    #[ruma_enum(rename = "m.room.canonical_alias")]
-    RoomCanonicalAlias,
-
-    /// m.room.create
-    #[ruma_enum(rename = "m.room.create")]
-    RoomCreate,
-
-    /// m.room.encrypted
-    #[ruma_enum(rename = "m.room.encrypted")]
-    RoomEncrypted,
-
-    /// m.room.encryption
-    #[ruma_enum(rename = "m.room.encryption")]
-    RoomEncryption,
-
-    /// m.room.guest_access
-    #[ruma_enum(rename = "m.room.guest_access")]
-    RoomGuestAccess,
-
-    /// m.room.history_visibility
-    #[ruma_enum(rename = "m.room.history_visibility")]
-    RoomHistoryVisibility,
-
-    /// m.room.join_rules
-    #[ruma_enum(rename = "m.room.join_rules")]
-    RoomJoinRules,
-
-    /// m.room.member
-    #[ruma_enum(rename = "m.room.member")]
-    RoomMember,
-
-    /// m.room.message
-    #[ruma_enum(rename = "m.room.message")]
-    RoomMessage,
-
-    /// m.room.message.feedback
-    #[ruma_enum(rename = "m.room.message.feedback")]
-    RoomMessageFeedback,
-
-    /// m.room.name
-    #[ruma_enum(rename = "m.room.name")]
-    RoomName,
-
-    /// m.room.pinned_events
-    #[ruma_enum(rename = "m.room.pinned_events")]
-    RoomPinnedEvents,
-
-    /// m.room.power_levels
-    #[ruma_enum(rename = "m.room.power_levels")]
-    RoomPowerLevels,
-
-    /// m.room.redaction
-    #[ruma_enum(rename = "m.room.redaction")]
-    RoomRedaction,
-
-    /// m.room.server_acl
-    #[ruma_enum(rename = "m.room.server_acl")]
-    RoomServerAcl,
-
-    /// m.room.third_party_invite
-    #[ruma_enum(rename = "m.room.third_party_invite")]
-    RoomThirdPartyInvite,
-
-    /// m.room.tombstone
-    #[ruma_enum(rename = "m.room.tombstone")]
-    RoomTombstone,
-
-    /// m.room.topic
-    #[ruma_enum(rename = "m.room.topic")]
-    RoomTopic,
-
-    /// m.room_key
-    #[ruma_enum(rename = "m.room_key")]
-    RoomKey,
-
-    /// m.room_key_request
-    #[ruma_enum(rename = "m.room_key_request")]
-    RoomKeyRequest,
-
-    /// m.sticker
-    #[ruma_enum(rename = "m.sticker")]
-    Sticker,
-
-    /// m.tag
-    #[ruma_enum(rename = "m.tag")]
-    Tag,
-
-    /// m.typing
-    #[ruma_enum(rename = "m.typing")]
-    Typing,
-
-    #[doc(hidden)]
-    _Custom(String),
-}
-
-impl EventType {
-    /// Creates a string slice from this `EventType`.
-    pub fn as_str(&self) -> &str {
-        self.as_ref()
-    }
-
-    /// Creates a byte slice from this `EventType`.
-    pub fn as_bytes(&self) -> &[u8] {
-        self.as_str().as_bytes()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use ruma_serde::test::serde_json_eq;
-    use serde_json::json;
-
-    use super::EventType;
-
-    #[test]
-    fn serialize_and_deserialize_from_display_form() {
-        serde_json_eq(EventType::CallAnswer, json!("m.call.answer"));
-        serde_json_eq(EventType::CallCandidates, json!("m.call.candidates"));
-        serde_json_eq(EventType::CallHangup, json!("m.call.hangup"));
-        serde_json_eq(EventType::CallInvite, json!("m.call.invite"));
-        serde_json_eq(EventType::Direct, json!("m.direct"));
-        serde_json_eq(EventType::Dummy, json!("m.dummy"));
-        serde_json_eq(EventType::ForwardedRoomKey, json!("m.forwarded_room_key"));
-        serde_json_eq(EventType::FullyRead, json!("m.fully_read"));
-        serde_json_eq(EventType::KeyVerificationAccept, json!("m.key.verification.accept"));
-        serde_json_eq(EventType::KeyVerificationCancel, json!("m.key.verification.cancel"));
-        serde_json_eq(EventType::KeyVerificationDone, json!("m.key.verification.done"));
-        serde_json_eq(EventType::KeyVerificationKey, json!("m.key.verification.key"));
-        serde_json_eq(EventType::KeyVerificationMac, json!("m.key.verification.mac"));
-        serde_json_eq(EventType::KeyVerificationReady, json!("m.key.verification.ready"));
-        serde_json_eq(EventType::KeyVerificationRequest, json!("m.key.verification.request"));
-        serde_json_eq(EventType::KeyVerificationStart, json!("m.key.verification.start"));
-        serde_json_eq(EventType::IgnoredUserList, json!("m.ignored_user_list"));
-        serde_json_eq(EventType::PolicyRuleRoom, json!("m.policy.rule.room"));
-        serde_json_eq(EventType::PolicyRuleServer, json!("m.policy.rule.server"));
-        serde_json_eq(EventType::PolicyRuleUser, json!("m.policy.rule.user"));
-        serde_json_eq(EventType::Presence, json!("m.presence"));
-        serde_json_eq(EventType::PushRules, json!("m.push_rules"));
-        serde_json_eq(EventType::Receipt, json!("m.receipt"));
-        serde_json_eq(EventType::RoomAliases, json!("m.room.aliases"));
-        serde_json_eq(EventType::RoomAvatar, json!("m.room.avatar"));
-        serde_json_eq(EventType::RoomCanonicalAlias, json!("m.room.canonical_alias"));
-        serde_json_eq(EventType::RoomCreate, json!("m.room.create"));
-        serde_json_eq(EventType::RoomEncrypted, json!("m.room.encrypted"));
-        serde_json_eq(EventType::RoomEncryption, json!("m.room.encryption"));
-        serde_json_eq(EventType::RoomGuestAccess, json!("m.room.guest_access"));
-        serde_json_eq(EventType::RoomHistoryVisibility, json!("m.room.history_visibility"));
-        serde_json_eq(EventType::RoomJoinRules, json!("m.room.join_rules"));
-        serde_json_eq(EventType::RoomMember, json!("m.room.member"));
-        serde_json_eq(EventType::RoomMessage, json!("m.room.message"));
-        serde_json_eq(EventType::RoomMessageFeedback, json!("m.room.message.feedback"));
-        serde_json_eq(EventType::RoomName, json!("m.room.name"));
-        serde_json_eq(EventType::RoomPinnedEvents, json!("m.room.pinned_events"));
-        serde_json_eq(EventType::RoomPowerLevels, json!("m.room.power_levels"));
-        serde_json_eq(EventType::RoomRedaction, json!("m.room.redaction"));
-        serde_json_eq(EventType::RoomServerAcl, json!("m.room.server_acl"));
-        serde_json_eq(EventType::RoomThirdPartyInvite, json!("m.room.third_party_invite"));
-        serde_json_eq(EventType::RoomTombstone, json!("m.room.tombstone"));
-        serde_json_eq(EventType::RoomTopic, json!("m.room.topic"));
-        serde_json_eq(EventType::RoomKey, json!("m.room_key"));
-        serde_json_eq(EventType::RoomKeyRequest, json!("m.room_key_request"));
-        serde_json_eq(EventType::Sticker, json!("m.sticker"));
-        serde_json_eq(EventType::Tag, json!("m.tag"));
-        serde_json_eq(EventType::Typing, json!("m.typing"));
-        serde_json_eq(EventType::_Custom("io.ruma.test".into()), json!("io.ruma.test"));
-    }
-}
diff --git a/crates/ruma-events/src/lib.rs b/crates/ruma-events/src/lib.rs
index acbf8b8e44..932e3a061e 100644
--- a/crates/ruma-events/src/lib.rs
+++ b/crates/ruma-events/src/lib.rs
@@ -131,7 +131,6 @@ use self::room::redaction::{RedactionEvent, SyncRedactionEvent};
 mod enums;
 mod error;
 mod event_kinds;
-mod event_type;
 
 // Hack to allow both ruma-events itself and external crates (or tests) to use procedural macros
 // that expect `ruma_events` to exist in the prelude.
@@ -145,6 +144,7 @@ extern crate self as ruma_events;
 pub mod exports {
     pub use ruma_common;
     pub use ruma_identifiers;
+    pub use ruma_serde;
     pub use serde;
     pub use serde_json;
 }
@@ -194,7 +194,9 @@ pub use self::{
         AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent, AnyRoomEvent, AnyStateEvent,
         AnyStateEventContent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent,
         AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, AnyToDeviceEvent,
-        AnyToDeviceEventContent,
+        AnyToDeviceEventContent, EphemeralRoomEventType, EventType, GlobalAccountDataEventType,
+        MessageEventType, RoomAccountDataEventType, RoomEventType, StateEventType,
+        ToDeviceEventType,
     },
     error::{FromStrError, InvalidInput},
     event_kinds::{
@@ -204,7 +206,6 @@ pub use self::{
         StrippedStateEvent, SyncEphemeralRoomEvent, SyncMessageEvent, SyncStateEvent,
         ToDeviceEvent,
     },
-    event_type::EventType,
 };
 
 /// Extra information about an event that is not incorporated into the event's


Test patch:
diff --git a/crates/ruma-events/tests/enums.rs b/crates/ruma-events/tests/enums.rs
index 494ccb0373..94cc9eff38 100644
--- a/crates/ruma-events/tests/enums.rs
+++ b/crates/ruma-events/tests/enums.rs
@@ -2,6 +2,7 @@ use js_int::uint;
 use matches::assert_matches;
 use ruma_common::MilliSecondsSinceUnixEpoch;
 use ruma_identifiers::{event_id, room_alias_id, room_id, user_id};
+use ruma_serde::test::serde_json_eq;
 use serde_json::{from_value as from_json_value, json, Value as JsonValue};
 
 use ruma_events::{
@@ -11,8 +12,9 @@ use ruma_events::{
         power_levels::PowerLevelsEventContent,
     },
     AnyEphemeralRoomEvent, AnyMessageEvent, AnyRoomEvent, AnyStateEvent, AnyStateEventContent,
-    AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, MessageEvent, StateEvent,
-    SyncMessageEvent, SyncStateEvent, Unsigned,
+    AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EphemeralRoomEventType, EventType,
+    GlobalAccountDataEventType, MessageEvent, MessageEventType, RoomAccountDataEventType,
+    StateEvent, StateEventType, SyncMessageEvent, SyncStateEvent, ToDeviceEventType, Unsigned,
 };
 
 fn message_event() -> JsonValue {
@@ -316,3 +318,64 @@ fn ephemeral_event_deserialization() {
         if ephem.room_id() == &room_id!("!jEsUZKDJdhlrceRyVU:example.org")
     );
 }
+
+#[test]
+fn serialize_and_deserialize_from_display_form() {
+    serde_json_eq(EventType::CallAnswer, json!("m.call.answer"));
+    serde_json_eq(MessageEventType::CallAnswer, json!("m.call.answer"));
+    serde_json_eq(EventType::CallCandidates, json!("m.call.candidates"));
+    serde_json_eq(EventType::CallHangup, json!("m.call.hangup"));
+    serde_json_eq(EventType::CallInvite, json!("m.call.invite"));
+    serde_json_eq(EventType::Direct, json!("m.direct"));
+    serde_json_eq(GlobalAccountDataEventType::Direct, json!("m.direct"));
+    serde_json_eq(EventType::Dummy, json!("m.dummy"));
+    serde_json_eq(EventType::ForwardedRoomKey, json!("m.forwarded_room_key"));
+    serde_json_eq(EventType::FullyRead, json!("m.fully_read"));
+    serde_json_eq(RoomAccountDataEventType::FullyRead, json!("m.fully_read"));
+    serde_json_eq(EventType::KeyVerificationAccept, json!("m.key.verification.accept"));
+    serde_json_eq(EventType::KeyVerificationCancel, json!("m.key.verification.cancel"));
+    // m.key.verification.ready is unstable-pre-spec
+    // serde_json_eq(EventType::KeyVerificationDone, json!("m.key.verification.done"));
+    serde_json_eq(EventType::KeyVerificationKey, json!("m.key.verification.key"));
+    serde_json_eq(ToDeviceEventType::KeyVerificationKey, json!("m.key.verification.key"));
+    serde_json_eq(EventType::KeyVerificationMac, json!("m.key.verification.mac"));
+    // m.key.verification.ready is unstable-pre-spec
+    // serde_json_eq(EventType::KeyVerificationReady, json!("m.key.verification.ready"));
+    serde_json_eq(EventType::KeyVerificationRequest, json!("m.key.verification.request"));
+    serde_json_eq(EventType::KeyVerificationStart, json!("m.key.verification.start"));
+    serde_json_eq(EventType::IgnoredUserList, json!("m.ignored_user_list"));
+    serde_json_eq(EventType::PolicyRuleRoom, json!("m.policy.rule.room"));
+    serde_json_eq(EventType::PolicyRuleServer, json!("m.policy.rule.server"));
+    serde_json_eq(EventType::PolicyRuleUser, json!("m.policy.rule.user"));
+    serde_json_eq(EventType::Presence, json!("m.presence"));
+    serde_json_eq(EventType::PushRules, json!("m.push_rules"));
+    serde_json_eq(EventType::Receipt, json!("m.receipt"));
+    serde_json_eq(EventType::RoomAliases, json!("m.room.aliases"));
+    serde_json_eq(EventType::RoomAvatar, json!("m.room.avatar"));
+    serde_json_eq(EventType::RoomCanonicalAlias, json!("m.room.canonical_alias"));
+    serde_json_eq(EventType::RoomCreate, json!("m.room.create"));
+    serde_json_eq(StateEventType::RoomCreate, json!("m.room.create"));
+    serde_json_eq(EventType::RoomEncrypted, json!("m.room.encrypted"));
+    serde_json_eq(EventType::RoomEncryption, json!("m.room.encryption"));
+    serde_json_eq(EventType::RoomGuestAccess, json!("m.room.guest_access"));
+    serde_json_eq(EventType::RoomHistoryVisibility, json!("m.room.history_visibility"));
+    serde_json_eq(EventType::RoomJoinRules, json!("m.room.join_rules"));
+    serde_json_eq(EventType::RoomMember, json!("m.room.member"));
+    serde_json_eq(EventType::RoomMessage, json!("m.room.message"));
+    serde_json_eq(EventType::RoomMessageFeedback, json!("m.room.message.feedback"));
+    serde_json_eq(EventType::RoomName, json!("m.room.name"));
+    serde_json_eq(EventType::RoomPinnedEvents, json!("m.room.pinned_events"));
+    serde_json_eq(EventType::RoomPowerLevels, json!("m.room.power_levels"));
+    serde_json_eq(EventType::RoomRedaction, json!("m.room.redaction"));
+    serde_json_eq(EventType::RoomServerAcl, json!("m.room.server_acl"));
+    serde_json_eq(EventType::RoomThirdPartyInvite, json!("m.room.third_party_invite"));
+    serde_json_eq(EventType::RoomTombstone, json!("m.room.tombstone"));
+    serde_json_eq(EventType::RoomTopic, json!("m.room.topic"));
+    serde_json_eq(EventType::RoomKey, json!("m.room_key"));
+    serde_json_eq(EventType::RoomKeyRequest, json!("m.room_key_request"));
+    serde_json_eq(EventType::Sticker, json!("m.sticker"));
+    serde_json_eq(EventType::Tag, json!("m.tag"));
+    serde_json_eq(EventType::Typing, json!("m.typing"));
+    serde_json_eq(EphemeralRoomEventType::Typing, json!("m.typing"));
+    serde_json_eq(EventType::_Custom("io.ruma.test".into()), json!("io.ruma.test"));
+}
diff --git a/crates/ruma-events/tests/ui/07-enum-sanity-check.rs b/crates/ruma-events/tests/ui/07-enum-sanity-check.rs
index 83ac5a532a..f32172fe6e 100644
--- a/crates/ruma-events/tests/ui/07-enum-sanity-check.rs
+++ b/crates/ruma-events/tests/ui/07-enum-sanity-check.rs
@@ -2,15 +2,14 @@ use ruma_events_macros::event_enum;
 
 event_enum! {
     /// Any global account data event.
-    kind: GlobalAccountData,
-    events: [
+    enum GlobalAccountData {
         "m.direct",
         #[cfg(test)]
         "m.ignored_user_list",
         "m.push_rules",
         #[cfg(any())]
         "m.ruma_test",
-    ]
+    }
 }
 
 fn main() {}
diff --git a/crates/ruma-events/tests/ui/08-enum-invalid-path.rs b/crates/ruma-events/tests/ui/08-enum-invalid-path.rs
index 2f6e13676d..ff5d48296d 100644
--- a/crates/ruma-events/tests/ui/08-enum-invalid-path.rs
+++ b/crates/ruma-events/tests/ui/08-enum-invalid-path.rs
@@ -1,17 +1,15 @@
 use ruma_events_macros::event_enum;
 
 event_enum! {
-    kind: State,
-    events: [
+    enum State {
         "m.not.a.path",
-    ]
+    }
 }
 
 event_enum! {
-    kind: State,
-    events: [
+    enum State {
         "not.a.path",
-    ]
+    }
 }
 
 fn main() {}
diff --git a/crates/ruma-events/tests/ui/08-enum-invalid-path.stderr b/crates/ruma-events/tests/ui/08-enum-invalid-path.stderr
index 4806290765..6656a3d95a 100644
--- a/crates/ruma-events/tests/ui/08-enum-invalid-path.stderr
+++ b/crates/ruma-events/tests/ui/08-enum-invalid-path.stderr
@@ -1,11 +1,11 @@
 error: well-known matrix events have to start with `m.` found `not.a.path`
-  --> $DIR/08-enum-invalid-path.rs:13:9
+  --> $DIR/08-enum-invalid-path.rs:11:9
    |
-13 |         "not.a.path",
+11 |         "not.a.path",
    |         ^^^^^^^^^^^^
 
 error[E0433]: failed to resolve: could not find `not` in `ruma_events`
- --> $DIR/08-enum-invalid-path.rs:6:9
+ --> $DIR/08-enum-invalid-path.rs:5:9
   |
-6 |         "m.not.a.path",
+5 |         "m.not.a.path",
   |         ^^^^^^^^^^^^^^ could not find `not` in `ruma_events`
diff --git a/crates/ruma-events/tests/ui/09-enum-invalid-kind.rs b/crates/ruma-events/tests/ui/09-enum-invalid-kind.rs
index f3597fe036..b21b75feff 100644
--- a/crates/ruma-events/tests/ui/09-enum-invalid-kind.rs
+++ b/crates/ruma-events/tests/ui/09-enum-invalid-kind.rs
@@ -1,14 +1,13 @@
 use ruma_events_macros::event_enum;
 
 event_enum! {
-    kind: NotReal,
-    events: [
+    enum NotReal {
         "m.direct",
         "m.dummy",
         "m.ignored_user_list",
         "m.push_rules",
         "m.room_key",
-    ]
+    }
 }
 
 fn main() {}
diff --git a/crates/ruma-events/tests/ui/09-enum-invalid-kind.stderr b/crates/ruma-events/tests/ui/09-enum-invalid-kind.stderr
index 4a1cc65506..afeae5fad5 100644
--- a/crates/ruma-events/tests/ui/09-enum-invalid-kind.stderr
+++ b/crates/ruma-events/tests/ui/09-enum-invalid-kind.stderr
@@ -1,5 +1,5 @@
 error: valid event kinds are GlobalAccountData, RoomAccountData, EphemeralRoom, Message, State, ToDevice found `NotReal`
- --> $DIR/09-enum-invalid-kind.rs:4:18
+ --> $DIR/09-enum-invalid-kind.rs:4:10
   |
-4 |     kind: NotReal,
-  |                  ^
+4 |     enum NotReal {
+  |          ^^^^^^^