Combine and sort common and custom fields using a Vec<Field>.

This commit is contained in:
Jimmy Cuadra 2019-06-19 15:40:29 -07:00
parent 2f54ee3e32
commit a5834ea192

View File

@ -2,7 +2,12 @@
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{Attribute, Field, Ident}; use syn::{
parse::{self, Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
Attribute, Field, Ident, Token,
};
use crate::parse::{EventKind, RumaEventInput}; use crate::parse::{EventKind, RumaEventInput};
@ -14,77 +19,44 @@ pub struct RumaEvent {
/// The name of the type of the event's `content` field. /// The name of the type of the event's `content` field.
content_name: Ident, content_name: Ident,
/// Additional named struct fields in the top level event struct. /// Struct fields of the event.
fields: Option<Vec<Field>>, fields: Vec<Field>,
/// The kind of event. /// The kind of event.
#[allow(dead_code)]
kind: EventKind, kind: EventKind,
/// The name of the event. /// The name of the event.
name: Ident, 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<ruma_identifiers::RoomId>,
/// Additional key-value pairs not signed by the homeserver.
pub unsigned: Option<serde_json::Value>,
/// 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<RumaEventInput> for RumaEvent { impl From<RumaEventInput> for RumaEvent {
fn from(input: RumaEventInput) -> Self { fn from(input: RumaEventInput) -> Self {
let kind = input.kind;
let name = input.name;
let content_name = Ident::new(&format!("{}Content", &name), Span::call_site());
let mut fields = match kind {
EventKind::Event => {
populate_event_fields(content_name.clone(), input.fields.unwrap_or_else(Vec::new))
}
EventKind::RoomEvent => populate_room_event_fields(
content_name.clone(),
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());
Self { Self {
attrs: input.attrs, attrs: input.attrs,
content_name: Ident::new(&format!("{}Content", input.name), Span::call_site()), content_name,
fields: input.fields, fields,
kind: input.kind, kind,
name: input.name, name,
} }
} }
} }
@ -94,16 +66,7 @@ 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 common_event_fields = match self.kind { let event_fields = &self.fields;
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 name = &self.name;
@ -113,7 +76,6 @@ impl ToTokens for RumaEvent {
#(#attrs),* #(#attrs),*
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct #name { pub struct #name {
#common_event_fields
#(#event_fields),* #(#event_fields),*
} }
@ -126,3 +88,94 @@ impl ToTokens for RumaEvent {
output.to_tokens(tokens); output.to_tokens(tokens);
} }
} }
/// 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> {
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! {
/// The event's content.
pub content: #content_name,
};
let mut additional_fields = Vec::with_capacity(punctuated_fields.len());
for punctuated_field in punctuated_fields {
additional_fields.push(punctuated_field.field);
}
fields.extend(additional_fields);
fields
}
/// 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> {
let mut fields = populate_event_fields(content_name, fields);
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! {
/// 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<ruma_identifiers::RoomId>,
/// Additional key-value pairs not signed by the homeserver.
pub unsigned: Option<serde_json::Value>,
/// The unique identifier for the user who sent this event.
pub sender: ruma_identifiers::UserId,
};
let mut additional_fields = Vec::with_capacity(punctuated_fields.len());
for punctuated_field in punctuated_fields {
additional_fields.push(punctuated_field.field);
}
fields.extend(additional_fields);
fields
}
/// 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> {
let mut fields = populate_room_event_fields(content_name.clone(), fields);
let punctuated_fields: Punctuated<ParsableNamedField, Token![,]> = parse_quote! {
/// 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,
};
let mut additional_fields = Vec::with_capacity(punctuated_fields.len());
for punctuated_field in punctuated_fields {
additional_fields.push(punctuated_field.field);
}
fields.extend(additional_fields);
fields
}
/// A wrapper around `syn::Field` that makes it possible to parse `Punctuated<Field, Token![,]>`
/// from a `TokenStream`.
///
/// See https://github.com/dtolnay/syn/issues/651 for more context.
struct ParsableNamedField {
/// The wrapped `Field`.
pub field: Field,
}
impl Parse for ParsableNamedField {
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
let field = Field::parse_named(input)?;
Ok(Self { field })
}
}