From 2f54ee3e3253a61c258ab9960397246acc2c51ec Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 18 Jun 2019 17:54:50 -0700 Subject: [PATCH] Implement generation of structs. --- Cargo.toml | 6 ++ src/gen.rs | 117 +++++++++++++++++++++++++++++++++--- src/lib.rs | 6 +- src/parse.rs | 2 +- tests/ruma_events_macros.rs | 47 +++++++++++++++ 5 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 tests/ruma_events_macros.rs diff --git a/Cargo.toml b/Cargo.toml index 547cf9a8..809f555b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,9 @@ proc-macro2 = "0.4.30" [lib] proc-macro = true + +[dev-dependencies] +ruma-identifiers = "0.13.1" +serde_json = "1.0.39" +js_int = "0.1.0" +serde = "1.0.92" diff --git a/src/gen.rs b/src/gen.rs index 30fdf61e..c84cb45f 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,25 +1,126 @@ //! Details of generating code for the `ruma_event` procedural macro. -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; +use syn::{Attribute, Field, Ident}; -use crate::parse::RumaEventInput; +use crate::parse::{EventKind, RumaEventInput}; /// The result of processing the `ruma_event` macro, ready for output back to source code. -pub struct RumaEvent; +pub struct RumaEvent { + /// Outer attributes on the field, such as a docstring. + attrs: Vec, + + /// The name of the type of the event's `content` field. + content_name: Ident, + + /// Additional named struct fields in the top level event struct. + fields: Option>, + + /// The kind of event. + kind: EventKind, + + /// The name of the event. + name: Ident, +} + +impl RumaEvent { + /// Fills in the event's struct definition with fields common to all basic events. + fn common_basic_event_fields(&self) -> TokenStream { + let content_name = &self.content_name; + + quote! { + /// The event's content. + pub content: #content_name, + } + } + + /// Fills in the event's struct definition with fields common to all room events. + fn common_room_event_fields(&self) -> TokenStream { + let common_basic_event_fields = self.common_basic_event_fields(); + + quote! { + #common_basic_event_fields + + /// The unique identifier for the event. + pub event_id: ruma_identifiers::EventId, + + /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this + /// event was sent. + pub origin_server_ts: js_int::UInt, + + /// The unique identifier for the room associated with this event. + pub room_id: Option, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, + + /// The unique identifier for the user who sent this event. + pub sender: ruma_identifiers::UserId, + } + } + + /// Fills in the event's struct definition with fields common to all state events. + fn common_state_event_fields(&self) -> TokenStream { + let content_name = &self.content_name; + let common_room_event_fields = self.common_room_event_fields(); + + quote! { + #common_room_event_fields + + /// The previous content for this state key, if any. + pub prev_content: Option<#content_name>, + + /// A key that determines which piece of room state the event represents. + pub state_key: String, + } + } +} impl From for RumaEvent { - // TODO: Provide an actual impl for this. - fn from(_input: RumaEventInput) -> Self { - Self + fn from(input: RumaEventInput) -> Self { + Self { + attrs: input.attrs, + content_name: Ident::new(&format!("{}Content", input.name), Span::call_site()), + fields: input.fields, + kind: input.kind, + name: input.name, + } } } impl ToTokens for RumaEvent { - // TODO: Provide an actual impl for this. fn to_tokens(&self, tokens: &mut TokenStream) { + let attrs = &self.attrs; + let content_name = &self.content_name; + + let common_event_fields = match self.kind { + EventKind::Event => self.common_basic_event_fields(), + EventKind::RoomEvent => self.common_room_event_fields(), + EventKind::StateEvent => self.common_state_event_fields(), + }; + + let event_fields = match &self.fields { + Some(fields) => fields.clone(), + None => vec![], + }; + + let name = &self.name; + + let content_docstring = format!("The payload for `{}`.", name); + let output = quote!( - pub struct Foo {} + #(#attrs),* + #[derive(Clone, Debug)] + pub struct #name { + #common_event_fields + #(#event_fields),* + } + + #[doc = #content_docstring] + #[derive(Clone, Debug)] + pub struct #content_name { + } ); output.to_tokens(tokens); diff --git a/src/lib.rs b/src/lib.rs index 537e7198..ca782e54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ mod parse; /// event_type: RoomAliases, /// content: { /// /// A list of room aliases. -/// pub aliases: Vec, +/// pub aliases: Vec, /// } /// } /// } @@ -82,7 +82,7 @@ mod parse; /// event_type: RoomRedaction, /// fields: { /// /// The ID of the event that was redacted. -/// pub redacts: EventId +/// pub redacts: ruma_identifiers::EventId /// }, /// content: { /// /// The reason for the redaction, if any. @@ -109,7 +109,7 @@ mod parse; /// /// /// /// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered /// /// *direct* for that particular user. -/// HashMap> +/// HashMap> /// } /// } /// } diff --git a/src/parse.rs b/src/parse.rs index d4812147..5077191c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -16,7 +16,7 @@ pub struct RumaEventInput { /// Outer attributes on the field, such as a docstring. pub attrs: Vec, - /// The name of the field. + /// The name of the event. pub name: Ident, /// The kind of event, determiend by the `kind` field. diff --git a/tests/ruma_events_macros.rs b/tests/ruma_events_macros.rs new file mode 100644 index 00000000..bb2e1dce --- /dev/null +++ b/tests/ruma_events_macros.rs @@ -0,0 +1,47 @@ +// See note about wrapping macro expansion in a module from `src/lib.rs` +pub mod tests { + use ruma_events_macros::ruma_event; + + ruma_event! { + /// Informs the room about what room aliases it has been given. + AliasesEvent { + kind: StateEvent, + event_type: RoomAliases, + content: { + /// A list of room aliases. + pub aliases: Vec, + } + } + } + + ruma_event! { + /// A redaction of an event. + RedactionEvent { + kind: RoomEvent, + event_type: RoomRedaction, + fields: { + /// The ID of the event that was redacted. + pub redacts: ruma_identifiers::EventId + }, + content: { + /// The reason for the redaction, if any. + pub reason: Option, + }, + } + } + + ruma_event! { + /// Informs the client about the rooms that are considered direct by a user. + DirectEvent { + kind: Event, + event_type: Direct, + content_type_alias: { + /// The payload of a `DirectEvent`. + /// + /// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered + /// *direct* for that particular user. + std::collections::HashMap> + } + } + } +}