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
|
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![,]>`
|
/// A wrapper around `syn::Field` that makes it possible to parse `Punctuated<Field, Token![,]>`
|
||||||
/// from a `TokenStream`.
|
/// from a `TokenStream`.
|
||||||
///
|
///
|
||||||
|
@ -37,12 +37,14 @@ use quote::ToTokens;
|
|||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
|
collection::{expand_collection, parse::RumaCollectionInput},
|
||||||
event_content::{expand_message_event, expand_state_event},
|
event_content::{expand_message_event, expand_state_event},
|
||||||
from_raw::expand_from_raw,
|
from_raw::expand_from_raw,
|
||||||
gen::RumaEvent,
|
gen::RumaEvent,
|
||||||
parse::RumaEventInput,
|
parse::RumaEventInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod collection;
|
||||||
mod event_content;
|
mod event_content;
|
||||||
mod from_raw;
|
mod from_raw;
|
||||||
mod gen;
|
mod gen;
|
||||||
@ -141,6 +143,18 @@ pub fn ruma_event(input: TokenStream) -> TokenStream {
|
|||||||
ruma_event.into_token_stream().into()
|
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`.
|
/// 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
|
/// 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.
|
/// 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},
|
room::{aliases::AliasesEventContent, avatar::AvatarEventContent},
|
||||||
EventContent, RoomEventContent, StateEventContent, TryFromRaw, UnsignedData,
|
EventContent, RoomEventContent, StateEventContent, TryFromRaw, UnsignedData,
|
||||||
};
|
};
|
||||||
|
use ruma_events_macros::event_content_collection;
|
||||||
|
|
||||||
/// A state event.
|
event_content_collection! {
|
||||||
#[derive(Clone, Debug, Serialize)]
|
/// A state event.
|
||||||
#[serde(untagged)]
|
name: AnyStateEventContent,
|
||||||
#[allow(clippy::large_enum_variant)]
|
events: ["m.room.aliases", "m.room.avatar"]
|
||||||
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>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State event.
|
/// State event.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user