Add field accessor code-gen for the event_enum! macro

This commit is contained in:
Ragotzy.devin 2020-06-30 08:59:24 -04:00 committed by GitHub
parent 5376a3fc6e
commit 41b8bd77f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 10 deletions

View File

@ -1,28 +1,67 @@
//! Implementation of event enum and event content enum macros. //! Implementation of event enum and event content enum macros.
use proc_macro2::TokenStream; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::{ use syn::{
parse::{self, Parse, ParseStream}, parse::{self, Parse, ParseStream},
Attribute, Expr, ExprLit, Ident, Lit, LitStr, Token, 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,
};
// 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];
const ROOM_ID_KIND: &[&str] = &[ANY_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_EPHEMERAL_EVENT];
const EVENT_ID_KIND: &[&str] =
&[ANY_MESSAGE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_SYNC_STATE_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,
];
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];
/// 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),
];
/// Create a content enum from `EventEnumInput`. /// Create a content enum from `EventEnumInput`.
pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> { pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
let ident = &input.name; let ident = &input.name;
let event_enum = expand_any_enum_with_deserialize(&input, ident)?; let event_enum = expand_any_enum_with_deserialize(&input, ident)?;
let needs_event_content = ident == "AnyStateEvent" let needs_event_content = ident == ANY_STATE_EVENT
|| ident == "AnyMessageEvent" || ident == ANY_MESSAGE_EVENT
|| ident == "AnyToDeviceEvent" || ident == ANY_TO_DEVICE_EVENT
|| ident == "AnyEphemeralRoomEvent" || ident == ANY_EPHEMERAL_EVENT
|| ident == "AnyBasicEvent"; || ident == ANY_BASIC_EVENT;
let needs_event_stub = let needs_event_stub =
ident == "AnyStateEvent" || ident == "AnyMessageEvent" || ident == "AnyEphemeralRoomEvent"; ident == ANY_STATE_EVENT || ident == ANY_MESSAGE_EVENT || ident == ANY_EPHEMERAL_EVENT;
let needs_stripped_event = ident == "AnyStateEvent"; let needs_stripped_event = ident == ANY_STATE_EVENT;
let event_stub_enum = let event_stub_enum =
if needs_event_stub { expand_stub_enum(&input)? } else { TokenStream::new() }; if needs_event_stub { expand_stub_enum(&input)? } else { TokenStream::new() };
@ -175,9 +214,13 @@ fn expand_any_enum_with_deserialize(
} }
}; };
let field_accessor_impl = accessor_methods(ident, &variants);
Ok(quote! { Ok(quote! {
#any_enum #any_enum
#field_accessor_impl
#event_deserialize_impl #event_deserialize_impl
}) })
} }
@ -202,6 +245,57 @@ fn marker_traits(ident: &Ident) -> TokenStream {
} }
} }
fn accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
let fields = EVENT_FIELDS
.iter()
.map(|(name, has_field)| generate_accessor(name, ident, *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 = quote! {
/// Returns the any content enum for this event.
pub fn content(&self) -> #content_enum {
match self {
#(
Self::#variants(event) => #content_enum::#variants(event.content.clone()),
)*
Self::Custom(event) => #content_enum::Custom(event.content.clone()),
}
}
};
let prev_content = if PREV_CONTENT_KIND.contains(&ident.to_string().as_str()) {
quote! {
/// Returns the any content enum for this events prev_content.
pub fn prev_content(&self) -> Option<#content_enum> {
match self {
#(
Self::#variants(event) => {
event.prev_content.as_ref().map(|c| #content_enum::#variants(c.clone()))
},
)*
Self::Custom(event) => {
event.prev_content.as_ref().map(|c| #content_enum::Custom(c.clone()))
},
}
}
}
} else {
TokenStream::new()
};
quote! {
impl #ident {
#content
#prev_content
#( #fields )*
}
}
}
fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream { fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream {
let span = name.span(); let span = name.span();
let name = name.value(); let name = name.value();
@ -280,6 +374,45 @@ pub(crate) fn to_camel_case(name: &LitStr) -> syn::Result<Ident> {
Ok(Ident::new(&s, span)) Ok(Ident::new(&s, span))
} }
fn generate_accessor(
name: &str,
ident: &Ident,
event_kind_list: &[&str],
variants: &[Ident],
) -> TokenStream {
if event_kind_list.contains(&ident.to_string().as_str()) {
let field_type = field_return_type(name);
let name = Ident::new(name, Span::call_site());
let docs = format!("Returns this events {} field.", name);
quote! {
#[doc = #docs]
pub fn #name(&self) -> &#field_type {
match self {
#(
Self::#variants(event) => &event.#name,
)*
Self::Custom(event) => &event.#name,
}
}
}
} else {
TokenStream::new()
}
}
fn field_return_type(name: &str) -> TokenStream {
match name {
"origin_server_ts" => quote! { ::std::time::SystemTime },
"room_id" => quote! { ::ruma_identifiers::RoomId },
"event_id" => quote! { ::ruma_identifiers::EventId },
"sender" => quote! { ::ruma_identifiers::UserId },
"state_key" => quote! { str },
"unsigned" => quote! { ::ruma_events::UnsignedData },
_ => panic!("the `ruma_events_macros::event_enum::EVENT_FIELD` const was changed"),
}
}
/// Custom keywords for the `event_enum!` macro /// Custom keywords for the `event_enum!` macro
mod kw { mod kw {
syn::custom_keyword!(name); syn::custom_keyword!(name);

View File

@ -0,0 +1,28 @@
//! 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.
// 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";
// Message events
pub const ANY_MESSAGE_EVENT: &str = "AnyMessageEvent";
pub const ANY_SYNC_MESSAGE_EVENT: &str = "AnyMessageEventStub";
// Ephemeral events
pub const ANY_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEvent";
#[allow(dead_code)]
// This is currently not used but, left for completeness sake.
pub const ANY_SYNC_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEventStub";
// Basic event
pub const ANY_BASIC_EVENT: &str = "AnyBasicEvent";
// To device event
pub const ANY_TO_DEVICE_EVENT: &str = "AnyToDeviceEvent";

View File

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

View File

@ -1,7 +1,7 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use matches::assert_matches; use matches::assert_matches;
use ruma_identifiers::RoomAliasId; use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId};
use serde_json::{from_value as from_json_value, json, Value as JsonValue}; use serde_json::{from_value as from_json_value, json, Value as JsonValue};
use ruma_events::{ use ruma_events::{
@ -11,7 +11,8 @@ use ruma_events::{
power_levels::PowerLevelsEventContent, power_levels::PowerLevelsEventContent,
}, },
AnyEvent, AnyMessageEvent, AnyMessageEventStub, AnyRoomEvent, AnyRoomEventStub, AnyStateEvent, AnyEvent, AnyMessageEvent, AnyMessageEventStub, AnyRoomEvent, AnyRoomEventStub, AnyStateEvent,
AnyStateEventStub, MessageEvent, MessageEventStub, StateEvent, StateEventStub, AnyStateEventContent, AnyStateEventStub, MessageEvent, MessageEventStub, StateEvent,
StateEventStub,
}; };
fn message_event() -> JsonValue { fn message_event() -> JsonValue {
@ -243,3 +244,24 @@ fn alias_event_deserialization() {
if aliases == vec![ RoomAliasId::try_from("#somewhere:localhost").unwrap() ] if aliases == vec![ RoomAliasId::try_from("#somewhere:localhost").unwrap() ]
); );
} }
#[test]
fn alias_event_field_access() {
let json_data = aliases_event();
assert_matches!(
from_json_value::<AnyEvent>(json_data.clone()),
Ok(AnyEvent::State(state_event))
if state_event.state_key() == ""
&& state_event.room_id() == &RoomId::try_from("!room:room.com").unwrap()
&& state_event.event_id() == &EventId::try_from("$152037280074GZeOm:localhost").unwrap()
&& state_event.sender() == &UserId::try_from("@example:localhost").unwrap()
);
let deser = from_json_value::<AnyStateEvent>(json_data).unwrap();
if let AnyStateEventContent::RoomAliases(AliasesEventContent { aliases }) = deser.content() {
assert_eq!(aliases, vec![RoomAliasId::try_from("#somewhere:localhost").unwrap()])
} else {
panic!("the `Any*Event` enum's accessor methods may have been altered")
}
}