… by moving them from ruma-events-macros to ruma-events and updating the macros to work in different contexts (without items like FromRaw being available at the calling crate's root)
290 lines
10 KiB
Rust
290 lines
10 KiB
Rust
//! Details of parsing input for the `ruma_event` procedural macro.
|
|
|
|
use proc_macro2::Span;
|
|
|
|
use syn::{
|
|
braced,
|
|
parse::{self, Parse, ParseStream},
|
|
punctuated::Punctuated,
|
|
token::Colon,
|
|
Attribute, Expr, Field, FieldValue, Ident, Member, Path, PathArguments, PathSegment, Token,
|
|
TypePath,
|
|
};
|
|
|
|
/// The entire `ruma_event!` macro structure directly as it appears in the source code..
|
|
pub struct RumaEventInput {
|
|
/// Outer attributes on the field, such as a docstring.
|
|
pub attrs: Vec<Attribute>,
|
|
|
|
/// The name of the event.
|
|
pub name: Ident,
|
|
|
|
/// The kind of event, determiend by the `kind` field.
|
|
pub kind: EventKind,
|
|
|
|
/// The variant of `ruma_events::EventType` for this event, determined by the `event_type`
|
|
/// field.
|
|
pub event_type: Path,
|
|
|
|
/// Additional named struct fields in the top level event struct.
|
|
pub fields: Option<Vec<Field>>,
|
|
|
|
/// A struct definition or type alias to be used as the event's `content` field.
|
|
pub content: Content,
|
|
}
|
|
|
|
impl Parse for RumaEventInput {
|
|
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
|
let attrs = input.call(Attribute::parse_outer)?;
|
|
let name: Ident = input.parse()?;
|
|
let body;
|
|
braced!(body in input);
|
|
|
|
let mut kind = None;
|
|
let mut event_type = None;
|
|
let mut fields = None;
|
|
let mut content = None;
|
|
|
|
#[allow(clippy::identity_conversion)]
|
|
for field_value_inline_struct in
|
|
body.parse_terminated::<RumaEventField, Token![,]>(RumaEventField::parse)?
|
|
{
|
|
match field_value_inline_struct {
|
|
RumaEventField::Block(field_block) => {
|
|
let ident = match field_block.member {
|
|
Member::Named(ident) => ident,
|
|
Member::Unnamed(_) => panic!("fields with block values in `ruma_event!` must named `content_type_alias`"),
|
|
};
|
|
|
|
if ident == "content_type_alias" {
|
|
content = Some(Content::Typedef(field_block.typedef));
|
|
}
|
|
}
|
|
RumaEventField::InlineStruct(field_inline_struct) => {
|
|
let ident = match field_inline_struct.member {
|
|
Member::Named(ident) => ident,
|
|
Member::Unnamed(_) => panic!("fields with inline struct values in `ruma_event!` must be named `fields` or `content`."),
|
|
};
|
|
|
|
if ident == "fields" {
|
|
fields = Some(field_inline_struct.fields);
|
|
} else if ident == "content" {
|
|
content = Some(Content::Struct(field_inline_struct.fields));
|
|
}
|
|
}
|
|
RumaEventField::Value(field_value) => {
|
|
let ident = match field_value.member {
|
|
Member::Named(ident) => ident,
|
|
Member::Unnamed(_) => panic!("fields with expression values in `ruma_event!` must be named `kind` or `event_type`, ."),
|
|
};
|
|
|
|
if ident == "kind" {
|
|
let event_kind = match field_value.expr {
|
|
Expr::Path(expr_path) => {
|
|
if expr_path.path.is_ident("Event") {
|
|
EventKind::Event
|
|
} else if expr_path.path.is_ident("RoomEvent") {
|
|
EventKind::RoomEvent
|
|
} else if expr_path.path.is_ident("StateEvent") {
|
|
EventKind::StateEvent
|
|
} else {
|
|
panic!("value of field `kind` must be one of `Event`, `RoomEvent`, or `StateEvent`");
|
|
}
|
|
}
|
|
_ => panic!(
|
|
"value of field `kind` is required to be an ident by `ruma_event!`"
|
|
),
|
|
};
|
|
|
|
kind = Some(event_kind);
|
|
} else if ident == "event_type" {
|
|
match field_value.expr {
|
|
Expr::Path(expr_path) => {
|
|
if expr_path.path.segments.len() != 1 {
|
|
panic!("value of field `event_type` is required to be an ident by `ruma_event!`");
|
|
}
|
|
|
|
let path = expr_path.path;
|
|
let variant = path.segments.first().unwrap();
|
|
|
|
let mut punctuated = Punctuated::new();
|
|
punctuated.push(PathSegment {
|
|
ident: Ident::new("ruma_events", Span::call_site()),
|
|
arguments: PathArguments::None,
|
|
});
|
|
punctuated.push(PathSegment {
|
|
ident: Ident::new("EventType", Span::call_site()),
|
|
arguments: PathArguments::None,
|
|
});
|
|
punctuated.push(variant.clone());
|
|
|
|
event_type = Some(Path {
|
|
leading_colon: Some(Default::default()),
|
|
segments: punctuated,
|
|
});
|
|
}
|
|
_ => panic!(
|
|
"value of field `event_type` is required to be an ident by `ruma_event!`"
|
|
),
|
|
}
|
|
} else {
|
|
panic!("unexpected field-value pair with field name `{}`", ident);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if kind.is_none() {
|
|
panic!("field `kind` is required by `ruma_event!`");
|
|
} else if event_type.is_none() {
|
|
panic!("field `event_type` is required by `ruma_event!`");
|
|
} else if content.is_none() {
|
|
panic!(
|
|
"one field named `content` or `content_type_alias` is required by `ruma_event!`"
|
|
);
|
|
}
|
|
|
|
Ok(Self {
|
|
attrs,
|
|
name,
|
|
kind: kind.unwrap(),
|
|
event_type: event_type.unwrap(),
|
|
fields,
|
|
content: content.unwrap(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Which kind of event is being generated.
|
|
///
|
|
/// Determined by the `kind` field in the macro body.
|
|
#[derive(PartialEq)]
|
|
pub enum EventKind {
|
|
/// A basic event.
|
|
Event,
|
|
|
|
/// A room event.
|
|
RoomEvent,
|
|
|
|
/// A state event.
|
|
StateEvent,
|
|
}
|
|
|
|
/// Information for generating the type used for the event's `content` field.
|
|
pub enum Content {
|
|
/// A struct, e.g. `ExampleEventContent { ... }`.
|
|
Struct(Vec<Field>),
|
|
|
|
/// A type alias, e.g. `type ExampleEventContent = SomeExistingType`
|
|
Typedef(Typedef),
|
|
}
|
|
|
|
/// The style of field within the macro body.
|
|
#[allow(clippy::large_enum_variant)]
|
|
enum RumaEventField {
|
|
/// The value of a field is a block with a type alias in it.
|
|
///
|
|
/// Used for `content_type_alias`.
|
|
Block(FieldBlock),
|
|
|
|
/// The value of a field is a block with named struct fields in it.
|
|
///
|
|
/// Used for `content`.
|
|
InlineStruct(FieldInlineStruct),
|
|
|
|
/// A standard named struct field.
|
|
///
|
|
/// Used for `kind` and `event_type`.
|
|
Value(FieldValue),
|
|
}
|
|
|
|
impl Parse for RumaEventField {
|
|
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
|
let ahead = input.fork();
|
|
let field_ident: Ident = ahead.parse()?;
|
|
|
|
match field_ident.to_string().as_ref() {
|
|
"content" | "fields" => {
|
|
let attrs = input.call(Attribute::parse_outer)?;
|
|
let member = input.parse()?;
|
|
let colon_token = input.parse()?;
|
|
let body;
|
|
braced!(body in input);
|
|
let fields = body
|
|
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
|
.into_iter()
|
|
.collect();
|
|
|
|
Ok(RumaEventField::InlineStruct(FieldInlineStruct {
|
|
attrs,
|
|
member,
|
|
colon_token,
|
|
fields,
|
|
}))
|
|
}
|
|
"content_type_alias" => Ok(RumaEventField::Block(FieldBlock {
|
|
attrs: input.call(Attribute::parse_outer)?,
|
|
member: input.parse()?,
|
|
colon_token: input.parse()?,
|
|
typedef: input.parse()?,
|
|
})),
|
|
_ => Ok(RumaEventField::Value(input.parse()?)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The value of a field is a block with a type alias in it.
|
|
///
|
|
/// Used for `content_type_alias`.
|
|
struct FieldBlock {
|
|
/// Outer attributes on the field, such as a docstring.
|
|
pub attrs: Vec<Attribute>,
|
|
|
|
/// The name of the field.
|
|
pub member: Member,
|
|
|
|
/// The colon that appears between the field name and type.
|
|
pub colon_token: Colon,
|
|
|
|
/// The path to the type that will be used in a type alias for the event's `content` type.
|
|
pub typedef: Typedef,
|
|
}
|
|
|
|
/// The value of a field is a block with named struct fields in it.
|
|
///
|
|
/// Used for `content`.
|
|
struct FieldInlineStruct {
|
|
/// Outer attributes on the field, such as a docstring.
|
|
pub attrs: Vec<Attribute>,
|
|
|
|
/// The name of the field.
|
|
pub member: Member,
|
|
|
|
/// The colon that appears between the field name and type.
|
|
pub colon_token: Colon,
|
|
|
|
/// The fields that define the `content` struct.
|
|
pub fields: Vec<Field>,
|
|
}
|
|
|
|
/// Path to a type to be used in a type alias for an event's `content` type.
|
|
pub struct Typedef {
|
|
/// Outer attributes on the field, such as a docstring.
|
|
pub attrs: Vec<Attribute>,
|
|
|
|
/// Path to the type.
|
|
pub path: TypePath,
|
|
}
|
|
|
|
impl Parse for Typedef {
|
|
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
|
let body;
|
|
braced!(body in input);
|
|
|
|
Ok(Self {
|
|
attrs: body.call(Attribute::parse_outer)?,
|
|
path: body.parse()?,
|
|
})
|
|
}
|
|
}
|