event_content_collection!: implement parsing input and generating an enum
This commit is contained in:
parent
7ec162678e
commit
3814690b29
142
ruma-events-macros/src/collection.rs
Normal file
142
ruma-events-macros/src/collection.rs
Normal file
@ -0,0 +1,142 @@
|
||||
//! Implementation of the collection type macro.
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Ident, LitStr};
|
||||
|
||||
use parse::RumaCollectionInput;
|
||||
|
||||
/// Create a collection from `RumaCollectionInput.
|
||||
pub fn expand_collection(input: RumaCollectionInput) -> syn::Result<TokenStream> {
|
||||
let attrs = &input.attrs;
|
||||
let ident = &input.name;
|
||||
|
||||
let variants = input
|
||||
.events
|
||||
.iter()
|
||||
.map(|lit| {
|
||||
let content_docstring = lit;
|
||||
let var = to_camel_case(lit);
|
||||
let content = to_event_content(lit);
|
||||
|
||||
quote! {
|
||||
#[doc = #content_docstring]
|
||||
#var(#content)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let collection = quote! {
|
||||
#( #attrs )*
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum #ident {
|
||||
#( #variants ),*
|
||||
}
|
||||
};
|
||||
|
||||
Ok(collection)
|
||||
}
|
||||
|
||||
/// Splits the given `event_type` string on `.` and `_` removing the `m.` then
|
||||
/// using only the event name append "EventContent".
|
||||
fn to_event_content(name: &LitStr) -> Ident {
|
||||
let span = name.span();
|
||||
let name = name.value();
|
||||
|
||||
assert_eq!(&name[..2], "m.");
|
||||
|
||||
let event = name[2..].split('.').last().unwrap();
|
||||
|
||||
let event = event
|
||||
.split('_')
|
||||
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
|
||||
.collect::<String>();
|
||||
|
||||
let content_str = format!("{}EventContent", event);
|
||||
Ident::new(&content_str, span)
|
||||
}
|
||||
|
||||
/// Splits the given `event_type` string on `.` and `_` removing the `m.room.` then
|
||||
/// camel casing to give the `EventContent` struct name.
|
||||
pub(crate) fn to_camel_case(name: &LitStr) -> Ident {
|
||||
let span = name.span();
|
||||
let name = name.value();
|
||||
assert_eq!(&name[..2], "m.");
|
||||
let s = name[2..]
|
||||
.split(&['.', '_'] as &[char])
|
||||
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
|
||||
.collect::<String>();
|
||||
Ident::new(&s, span)
|
||||
}
|
||||
|
||||
/// Details of parsing input for the `event_content_collection` procedural macro.
|
||||
pub mod parse {
|
||||
use syn::{
|
||||
parse::{self, Parse, ParseStream},
|
||||
Attribute, Expr, ExprLit, Ident, Lit, LitStr, Token,
|
||||
};
|
||||
|
||||
/// Custom keywords for the `event_content_collection!` macro
|
||||
mod kw {
|
||||
syn::custom_keyword!(name);
|
||||
syn::custom_keyword!(events);
|
||||
}
|
||||
|
||||
/// The entire `event_content_collection!` macro structure directly as it appears in the source code..
|
||||
pub struct RumaCollectionInput {
|
||||
/// Outer attributes on the field, such as a docstring.
|
||||
pub attrs: Vec<Attribute>,
|
||||
|
||||
/// The name of the event.
|
||||
pub name: Ident,
|
||||
|
||||
/// An array of valid matrix event types. This will generate the variants of the event content 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>,
|
||||
}
|
||||
|
||||
impl Parse for RumaCollectionInput {
|
||||
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
||||
let attrs = input.call(Attribute::parse_outer)?;
|
||||
// name field
|
||||
input.parse::<kw::name>()?;
|
||||
input.parse::<Token![:]>()?;
|
||||
// the name of our collection enum
|
||||
let name: Ident = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
// events field
|
||||
input.parse::<kw::events>()?;
|
||||
input.parse::<Token![:]>()?;
|
||||
|
||||
// an array of event names `["m.room.whatever"]`
|
||||
let ev_array = input.parse::<syn::ExprArray>()?;
|
||||
let events = ev_array
|
||||
.elems
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(lit_str),
|
||||
..
|
||||
}) = item
|
||||
{
|
||||
Ok(lit_str)
|
||||
} else {
|
||||
let msg = "values of field `events` are required to be a string literal";
|
||||
Err(syn::Error::new_spanned(item, msg))
|
||||
}
|
||||
})
|
||||
.collect::<syn::Result<_>>()?;
|
||||
|
||||
Ok(Self {
|
||||
attrs,
|
||||
name,
|
||||
events,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -178,16 +178,6 @@ fn populate_state_fields(content_name: Ident, fields: Vec<Field>) -> Vec<Field>
|
||||
fields
|
||||
}
|
||||
|
||||
/// Splits the given `event_type` string on `.` and `_` removing the `m.` then
|
||||
/// camel casing to give the `EventType` variant.
|
||||
fn to_camel_case(name: String) -> String {
|
||||
assert_eq!(&name[..2], "m.");
|
||||
name[2..]
|
||||
.split(&['.', '_'] as &[char])
|
||||
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A wrapper around `syn::Field` that makes it possible to parse `Punctuated<Field, Token![,]>`
|
||||
/// from a `TokenStream`.
|
||||
///
|
||||
|
@ -37,12 +37,14 @@ use quote::ToTokens;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
use self::{
|
||||
collection::{expand_collection, parse::RumaCollectionInput},
|
||||
event_content::{expand_message_event, expand_state_event},
|
||||
from_raw::expand_from_raw,
|
||||
gen::RumaEvent,
|
||||
parse::RumaEventInput,
|
||||
};
|
||||
|
||||
mod collection;
|
||||
mod event_content;
|
||||
mod from_raw;
|
||||
mod gen;
|
||||
@ -141,6 +143,18 @@ pub fn ruma_event(input: TokenStream) -> TokenStream {
|
||||
ruma_event.into_token_stream().into()
|
||||
}
|
||||
|
||||
/// Generates a collection type to represent the various Matrix event types.
|
||||
///
|
||||
/// This macro also implements the necessary traits for the type to serialize and deserialize itself.
|
||||
// TODO more docs/example
|
||||
#[proc_macro]
|
||||
pub fn event_content_collection(input: TokenStream) -> TokenStream {
|
||||
let ruma_collection_input = syn::parse_macro_input!(input as RumaCollectionInput);
|
||||
expand_collection(ruma_collection_input)
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Generates an implementation of `ruma_events::FromRaw`. Only usable inside of `ruma_events`.
|
||||
/// Requires there to be a `raw` module in the same scope, with a type with the same name and fields
|
||||
/// as the one that this macro is used on.
|
||||
|
59
src/state.rs
59
src/state.rs
@ -22,61 +22,12 @@ use crate::{
|
||||
room::{aliases::AliasesEventContent, avatar::AvatarEventContent},
|
||||
EventContent, RoomEventContent, StateEventContent, TryFromRaw, UnsignedData,
|
||||
};
|
||||
use ruma_events_macros::event_content_collection;
|
||||
|
||||
/// A state event.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum AnyStateEventContent {
|
||||
/// m.room.aliases
|
||||
RoomAliases(AliasesEventContent),
|
||||
|
||||
/// m.room.avatar
|
||||
RoomAvatar(AvatarEventContent),
|
||||
// /// m.room.canonical_alias
|
||||
// RoomCanonicalAlias(StateEvent<CanonicalAliasEventContent>),
|
||||
|
||||
// /// m.room.create
|
||||
// RoomCreate(StateEvent<CreateEventContent>),
|
||||
|
||||
// /// m.room.encryption
|
||||
// RoomEncryption(StateEvent<EncryptionEventContent>),
|
||||
|
||||
// /// m.room.guest_access
|
||||
// RoomGuestAccess(StateEvent<GuestAccessEventContent>),
|
||||
|
||||
// /// m.room.history_visibility
|
||||
// RoomHistoryVisibility(StateEvent<HistoryVisibilityEventContent>),
|
||||
|
||||
// /// m.room.join_rules
|
||||
// RoomJoinRules(StateEvent<JoinRulesEventContent>),
|
||||
|
||||
// /// m.room.member
|
||||
// RoomMember(StateEvent<MemberEventContent>),
|
||||
|
||||
// /// m.room.name
|
||||
// RoomName(StateEvent<NameEventContent>),
|
||||
|
||||
// /// m.room.pinned_events
|
||||
// RoomPinnedEvents(StateEvent<PinnedEventsEventContent>),
|
||||
|
||||
// /// m.room.power_levels
|
||||
// RoomPowerLevels(StateEvent<PinnedEventsEventContent>),
|
||||
|
||||
// /// m.room.server_acl
|
||||
// RoomServerAcl(StateEvent<ServerAclEventContent>),
|
||||
|
||||
// /// m.room.third_party_invite
|
||||
// RoomThirdPartyInvite(StateEvent<ThirdPartyInviteEventContent>),
|
||||
|
||||
// /// m.room.tombstone
|
||||
// RoomTombstone(StateEvent<TombstoneEventContent>),
|
||||
|
||||
// /// m.room.topic
|
||||
// RoomTopic(StateEvent<TopicEventContent>),
|
||||
|
||||
// /// Any state event that is not part of the specification.
|
||||
// CustomState(StateEvent<CustomEventContent>),
|
||||
event_content_collection! {
|
||||
/// A state event.
|
||||
name: AnyStateEventContent,
|
||||
events: ["m.room.aliases", "m.room.avatar"]
|
||||
}
|
||||
|
||||
/// State event.
|
||||
|
Loading…
x
Reference in New Issue
Block a user