diff --git a/src/gen.rs b/src/gen.rs index 5ed8defc..6971fccf 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -6,7 +6,7 @@ use syn::{ parse::{self, Parse, ParseStream}, parse_quote, punctuated::Punctuated, - Attribute, Field, Ident, Token, + Attribute, Field, Ident, Path, Token, }; use crate::parse::{Content, EventKind, RumaEventInput}; @@ -22,6 +22,10 @@ pub struct RumaEvent { /// The name of the type of the event's `content` field. content_name: Ident, + /// The variant of `ruma_events::EventType` for this event, determined by the `event_type` + /// field. + event_type: Path, + /// Struct fields of the event. fields: Vec, @@ -58,6 +62,7 @@ impl From for RumaEvent { attrs: input.attrs, content: input.content, content_name, + event_type: input.event_type, fields, kind, name, @@ -69,18 +74,17 @@ impl ToTokens for RumaEvent { fn to_tokens(&self, tokens: &mut TokenStream) { let attrs = &self.attrs; let content_name = &self.content_name; - let event_fields = &self.fields; - + let event_type = &self.event_type; let name = &self.name; - + let name_str = format!("{}", name); let content_docstring = format!("The payload for `{}`.", name); let content = match &self.content { Content::Struct(fields) => { quote! { #[doc = #content_docstring] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, serde::Serialize)] pub struct #content_name { #(#fields),* } @@ -110,6 +114,21 @@ impl ToTokens for RumaEvent { Content::Typedef(_) => TokenStream::new(), }; + let field_count = event_fields.len() + 1; // + 1 because of manually adding `event_type` + + let mut serialize_field_calls: Vec = Vec::with_capacity(event_fields.len()); + + for field in event_fields { + let ident = field.ident.clone().unwrap(); + let ident_str = format!("{}", ident); + + let token_stream = quote! { + state.serialize_field(#ident_str, &self.#ident)?; + }; + + serialize_field_calls.push(token_stream); + } + let output = quote!( #(#attrs)* #[derive(Clone, Debug)] @@ -119,6 +138,35 @@ impl ToTokens for RumaEvent { #content + use serde::ser::SerializeStruct as _; + + impl serde::Serialize for #name { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer + { + let mut state = serializer.serialize_struct(#name_str, #field_count)?; + + #(#serialize_field_calls)* + state.serialize_field("type", &self.event_type())?; + + state.end() + } + } + + 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. + type Content = #content_name; + + /// The event's content. + fn content(&self) -> &Self::Content { + &self.content + } + } + /// "Raw" versions of the event and its content which implement `serde::Deserialize`. mod raw { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 91ae9450..c0faab46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ clippy::wrong_pub_self_convention, clippy::wrong_self_convention )] +#![recursion_limit = "128"] extern crate proc_macro; @@ -52,7 +53,7 @@ mod parse; /// The most common form of event is a struct with all the standard fields for an event of its /// kind and a struct for its `content` field: /// -/// ```rust,no_run +/// ```ignore /// # pub mod example { /// # use ruma_events_macros::ruma_event; /// ruma_event! { @@ -72,7 +73,7 @@ mod parse; /// Occasionally an event will have non-standard fields at its top level (outside the `content` /// field). These extra fields are declared in block labeled with `fields`: /// -/// ```rust,no_run +/// ```ignore /// # pub mod example { /// # use ruma_events_macros::ruma_event; /// ruma_event! { @@ -96,7 +97,7 @@ mod parse; /// Sometimes the type of the `content` should be a type alias rather than a struct or enum. This /// is designated with `content_type_alias`: /// -/// ```rust,no_run +/// ```ignore /// # pub mod example { /// # use ruma_events_macros::ruma_event; /// ruma_event! { diff --git a/tests/ruma_events_macros.rs b/tests/ruma_events_macros.rs index 8716e0e7..fa077be8 100644 --- a/tests/ruma_events_macros.rs +++ b/tests/ruma_events_macros.rs @@ -1,5 +1,44 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +/// The type of an event. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub enum EventType { + /// m.direct + Direct, + + /// m.room.aliases + RoomAliases, + + /// m.room.redaction + RoomRedaction, +} + +/// A basic event. +pub trait Event +where + Self: Debug + Serialize, +{ + /// The type of the event. + const EVENT_TYPE: EventType; + + /// The type of this event's `content` field. + type Content: Debug + Serialize; + + /// The event's content. + fn content(&self) -> &Self::Content; + + /// The type of the event. + fn event_type(&self) -> EventType { + Self::EVENT_TYPE + } +} + // See note about wrapping macro expansion in a module from `src/lib.rs` pub mod common_case { + use super::Event; + use ruma_events_macros::ruma_event; ruma_event! { @@ -16,6 +55,8 @@ pub mod common_case { } pub mod extra_fields { + use super::Event; + use ruma_events_macros::ruma_event; ruma_event! { @@ -36,6 +77,8 @@ pub mod extra_fields { } pub mod type_alias { + use super::Event; + use ruma_events_macros::ruma_event; ruma_event! {