Add support for events with custom types.

This commit is contained in:
Jimmy Cuadra 2019-06-20 16:53:35 -07:00
parent 44a13e6515
commit 553d9c05cd
2 changed files with 124 additions and 35 deletions

View File

@ -30,6 +30,9 @@ pub struct RumaEvent {
/// Struct fields of the event. /// Struct fields of the event.
fields: Vec<Field>, fields: Vec<Field>,
/// Whether or not the event type is `EventType::Custom`.
is_custom: bool,
/// The kind of event. /// The kind of event.
kind: EventKind, kind: EventKind,
@ -42,18 +45,25 @@ impl From<RumaEventInput> for RumaEvent {
let kind = input.kind; let kind = input.kind;
let name = input.name; let name = input.name;
let content_name = Ident::new(&format!("{}Content", &name), Span::call_site()); let content_name = Ident::new(&format!("{}Content", &name), Span::call_site());
let event_type = input.event_type;
let is_custom = is_custom_event_type(&event_type);
let mut fields = match kind { let mut fields = match kind {
EventKind::Event => { EventKind::Event => populate_event_fields(
populate_event_fields(content_name.clone(), input.fields.unwrap_or_else(Vec::new)) is_custom,
} content_name.clone(),
EventKind::RoomEvent => populate_room_event_fields( input.fields.unwrap_or_else(Vec::new),
),
EventKind::RoomEvent => populate_room_event_fields(
is_custom,
content_name.clone(),
input.fields.unwrap_or_else(Vec::new),
),
EventKind::StateEvent => populate_state_fields(
is_custom,
content_name.clone(), content_name.clone(),
input.fields.unwrap_or_else(Vec::new), input.fields.unwrap_or_else(Vec::new),
), ),
EventKind::StateEvent => {
populate_state_fields(content_name.clone(), input.fields.unwrap_or_else(Vec::new))
}
}; };
fields.sort_unstable_by_key(|field| field.ident.clone().unwrap()); fields.sort_unstable_by_key(|field| field.ident.clone().unwrap());
@ -62,8 +72,9 @@ impl From<RumaEventInput> for RumaEvent {
attrs: input.attrs, attrs: input.attrs,
content: input.content, content: input.content,
content_name, content_name,
event_type: input.event_type, event_type,
fields, fields,
is_custom,
kind, kind,
name, name,
} }
@ -78,7 +89,19 @@ impl ToTokens for RumaEvent {
let attrs = &self.attrs; let attrs = &self.attrs;
let content_name = &self.content_name; let content_name = &self.content_name;
let event_fields = &self.fields; let event_fields = &self.fields;
let event_type = &self.event_type;
let event_type = if self.is_custom {
quote! {
crate::EventType::Custom(self.event_type.clone())
}
} else {
let event_type = &self.event_type;
quote! {
#event_type
}
};
let name = &self.name; let name = &self.name;
let name_str = format!("{}", name); let name_str = format!("{}", name);
let content_docstring = format!("The payload for `{}`.", name); let content_docstring = format!("The payload for `{}`.", name);
@ -87,7 +110,7 @@ impl ToTokens for RumaEvent {
Content::Struct(fields) => { Content::Struct(fields) => {
quote! { quote! {
#[doc = #content_docstring] #[doc = #content_docstring]
#[derive(Clone, Debug, serde::Serialize)] #[derive(Clone, Debug, PartialEq, serde::Serialize)]
pub struct #content_name { pub struct #content_name {
#(#fields),* #(#fields),*
} }
@ -108,7 +131,7 @@ impl ToTokens for RumaEvent {
Content::Struct(fields) => { Content::Struct(fields) => {
quote! { quote! {
#[doc = #content_docstring] #[doc = #content_docstring]
#[derive(Clone, Debug, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, serde::Deserialize)]
pub struct #content_name { pub struct #content_name {
#(#fields),* #(#fields),*
} }
@ -117,14 +140,23 @@ impl ToTokens for RumaEvent {
Content::Typedef(_) => TokenStream::new(), Content::Typedef(_) => TokenStream::new(),
}; };
let field_count = event_fields.len() + 1; // + 1 because of manually adding `event_type` // Custom events will already have an event_type field. All other events need to account
// for this field being manually inserted in `Serialize` impls.
let event_type_field_count = if self.is_custom { 0 } else { 1 };
let field_count = event_fields.len() + event_type_field_count;
let mut from_str_field_values: Vec<TokenStream> = Vec::with_capacity(event_fields.len()); let mut from_str_field_values: Vec<TokenStream> = Vec::with_capacity(event_fields.len());
let mut serialize_field_calls: Vec<TokenStream> = Vec::with_capacity(event_fields.len()); let mut serialize_field_calls: Vec<TokenStream> = Vec::with_capacity(event_fields.len());
for field in event_fields { for field in event_fields {
let ident = field.ident.clone().unwrap(); let ident = field.ident.clone().unwrap();
let ident_str = format!("{}", ident);
let ident_str = if ident == "event_type" {
"type".to_string()
} else {
format!("{}", ident)
};
let span = field.span(); let span = field.span();
let from_str_field_value = if ident == "content" { let from_str_field_value = if ident == "content" {
@ -202,6 +234,23 @@ impl ToTokens for RumaEvent {
serialize_field_calls.push(serialize_field_call); serialize_field_calls.push(serialize_field_call);
} }
let (manually_serialize_type_field, import_event_in_serialize_impl) = if self.is_custom {
(TokenStream::new(), TokenStream::new())
} else {
let manually_serialize_type_field = quote! {
state.serialize_field("type", &self.event_type())?;
};
let import_event_in_serialize_impl = quote! {
use crate::Event as _;
};
(
manually_serialize_type_field,
import_event_in_serialize_impl,
)
};
let impl_room_event = match self.kind { let impl_room_event = match self.kind {
EventKind::RoomEvent | EventKind::StateEvent => { EventKind::RoomEvent | EventKind::StateEvent => {
quote! { quote! {
@ -260,7 +309,7 @@ impl ToTokens for RumaEvent {
let output = quote!( let output = quote!(
#(#attrs)* #(#attrs)*
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct #name { pub struct #name {
#(#event_fields),* #(#event_fields),*
} }
@ -285,21 +334,18 @@ impl ToTokens for RumaEvent {
where where
S: serde::Serializer S: serde::Serializer
{ {
use crate::Event as _; #import_event_in_serialize_impl
let mut state = serializer.serialize_struct(#name_str, #field_count)?; let mut state = serializer.serialize_struct(#name_str, #field_count)?;
#(#serialize_field_calls)* #(#serialize_field_calls)*
state.serialize_field("type", &self.event_type())?; #manually_serialize_type_field
state.end() state.end()
} }
} }
impl crate::Event for #name { impl crate::Event for #name {
/// The type of the event.
const EVENT_TYPE: crate::EventType = #event_type;
/// The type of this event's `content` field. /// The type of this event's `content` field.
type Content = #content_name; type Content = #content_name;
@ -307,6 +353,11 @@ impl ToTokens for RumaEvent {
fn content(&self) -> &Self::Content { fn content(&self) -> &Self::Content {
&self.content &self.content
} }
/// The type of the event.
fn event_type(&self) -> crate::EventType {
#event_type
}
} }
#impl_room_event #impl_room_event
@ -318,7 +369,7 @@ impl ToTokens for RumaEvent {
use super::*; use super::*;
#(#attrs)* #(#attrs)*
#[derive(Clone, Debug, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, serde::Deserialize)]
pub struct #name { pub struct #name {
#(#event_fields),* #(#event_fields),*
} }
@ -332,10 +383,24 @@ impl ToTokens for RumaEvent {
} }
/// Fills in the event's struct definition with fields common to all basic events. /// Fills in the event's struct definition with fields common to all basic events.
fn populate_event_fields(content_name: Ident, mut fields: Vec<Field>) -> Vec<Field> { fn populate_event_fields(
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! { is_custom: bool,
/// The event's content. content_name: Ident,
pub content: #content_name, mut fields: Vec<Field>,
) -> Vec<Field> {
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = if is_custom {
parse_quote! {
/// The event's content.
pub content: #content_name,
/// The custom type of the event.
pub event_type: String,
}
} else {
parse_quote! {
/// The event's content.
pub content: #content_name,
}
}; };
let mut additional_fields = Vec::with_capacity(punctuated_fields.len()); let mut additional_fields = Vec::with_capacity(punctuated_fields.len());
@ -350,8 +415,12 @@ fn populate_event_fields(content_name: Ident, mut fields: Vec<Field>) -> Vec<Fie
} }
/// Fills in the event's struct definition with fields common to all room events. /// Fills in the event's struct definition with fields common to all room events.
fn populate_room_event_fields(content_name: Ident, fields: Vec<Field>) -> Vec<Field> { fn populate_room_event_fields(
let mut fields = populate_event_fields(content_name, fields); is_custom: bool,
content_name: Ident,
fields: Vec<Field>,
) -> Vec<Field> {
let mut fields = populate_event_fields(is_custom, content_name, fields);
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! { let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! {
/// The unique identifier for the event. /// The unique identifier for the event.
@ -383,8 +452,8 @@ fn populate_room_event_fields(content_name: Ident, fields: Vec<Field>) -> Vec<Fi
} }
/// Fills in the event's struct definition with fields common to all state events. /// Fills in the event's struct definition with fields common to all state events.
fn populate_state_fields(content_name: Ident, fields: Vec<Field>) -> Vec<Field> { fn populate_state_fields(is_custom: bool, content_name: Ident, fields: Vec<Field>) -> Vec<Field> {
let mut fields = populate_room_event_fields(content_name.clone(), fields); let mut fields = populate_room_event_fields(is_custom, content_name.clone(), fields);
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! { let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! {
/// The previous content for this state key, if any. /// The previous content for this state key, if any.
@ -405,6 +474,11 @@ fn populate_state_fields(content_name: Ident, fields: Vec<Field>) -> Vec<Field>
fields fields
} }
/// Checks if the given `Path` refers to `EventType::Custom`.
fn is_custom_event_type(event_type: &Path) -> bool {
event_type.segments.last().unwrap().value().ident == "Custom"
}
/// 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`.
/// ///

View File

@ -3,7 +3,7 @@ use std::fmt::Debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// The type of an event. /// The type of an event.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum EventType { pub enum EventType {
/// m.direct /// m.direct
Direct, Direct,
@ -13,6 +13,9 @@ pub enum EventType {
/// m.room.redaction /// m.room.redaction
RoomRedaction, RoomRedaction,
/// Any event that is not part of the specification.
Custom(String),
} }
/// A basic event. /// A basic event.
@ -20,9 +23,6 @@ pub trait Event
where where
Self: Debug + Serialize, Self: Debug + Serialize,
{ {
/// The type of the event.
const EVENT_TYPE: EventType;
/// The type of this event's `content` field. /// The type of this event's `content` field.
type Content: Debug + Serialize; type Content: Debug + Serialize;
@ -30,9 +30,7 @@ where
fn content(&self) -> &Self::Content; fn content(&self) -> &Self::Content;
/// The type of the event. /// The type of the event.
fn event_type(&self) -> EventType { fn event_type(&self) -> EventType;
Self::EVENT_TYPE
}
} }
/// An event within the context of a room. /// An event within the context of a room.
@ -91,6 +89,23 @@ pub mod common_case {
} }
} }
pub mod custom_event_type {
use ruma_events_macros::ruma_event;
use serde_json::Value;
ruma_event! {
/// A custom event.
CustomEvent {
kind: Event,
event_type: Custom,
content_type_alias: {
/// The payload for `CustomEvent`.
Value
},
}
}
}
pub mod extra_fields { pub mod extra_fields {
use ruma_events_macros::ruma_event; use ruma_events_macros::ruma_event;