1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Crate `ruma_events_macros` provides a procedural macro for generating
//! [ruma-events](https://github.com/ruma/ruma-events) events.
//!
//! See the documentation for the `ruma_event!` macro for usage details.
#![deny(
    missing_copy_implementations,
    missing_debug_implementations,
    // missing_docs, # Uncomment when https://github.com/rust-lang/rust/pull/60562 is released.
)]
#![warn(
    clippy::empty_line_after_outer_attr,
    clippy::expl_impl_clone_on_copy,
    clippy::if_not_else,
    clippy::items_after_statements,
    clippy::match_same_arms,
    clippy::mem_forget,
    clippy::missing_docs_in_private_items,
    clippy::multiple_inherent_impl,
    clippy::mut_mut,
    clippy::needless_borrow,
    clippy::needless_continue,
    clippy::single_match_else,
    clippy::unicode_not_nfc,
    clippy::use_self,
    clippy::used_underscore_binding,
    clippy::wrong_pub_self_convention,
    clippy::wrong_self_convention
)]
// Since we support Rust 1.36.0, we can't apply this suggestion yet
#![allow(clippy::use_self)]
#![recursion_limit = "128"]

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse_macro_input, DeriveInput};

use self::{from_raw::expand_from_raw, gen::RumaEvent, parse::RumaEventInput};

mod from_raw;
mod gen;
mod parse;

// A note about the `example` modules that appears in doctests:
//
// This is necessary because otherwise the expanded code appears in function context, which makes
// the compiler interpret the output of the macro as a statement, and proc macros currently aren't
// allowed to expand to statements, resulting in a compiler error.

/// Generates a Rust type for a Matrix event.
///
/// # Examples
///
/// 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:
///
/// ```ignore
/// # pub mod example {
/// # 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_identifiers::RoomAliasId>,
///         }
///     }
/// }
/// # }
/// ```
///
/// 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`:
///
/// ```ignore
/// # pub mod example {
/// # use ruma_events_macros::ruma_event;
/// 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<String>,
///         },
///     }
/// }
/// # }
/// ```
///
/// Sometimes the type of the `content` should be a type alias rather than a struct or enum. This
/// is designated with `content_type_alias`:
///
/// ```ignore
/// # pub mod example {
/// # use ruma_events_macros::ruma_event;
/// 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::BTreeMap<ruma_identifiers::UserId, Vec<ruma_identifiers::RoomId>>
///         }
///     }
/// }
/// # }
/// ```
///
/// If `content` and `content_type_alias` are both supplied, the second one listed will overwrite
/// the first.
///
/// The event type and content type will have copies generated inside a private `raw` module. These
/// "raw" versions are the same, except they implement `serde::Deserialize`. An implementation of
/// `FromRaw` will be provided, which will allow the user to deserialize the event type as
/// `EventJson<EventType>`.
#[proc_macro]
pub fn ruma_event(input: TokenStream) -> TokenStream {
    let ruma_event_input = syn::parse_macro_input!(input as RumaEventInput);

    let ruma_event = RumaEvent::from(ruma_event_input);

    ruma_event.into_token_stream().into()
}

/// Generates an implementation of `ruma_events::FromRaw`. Only usable inside of `ruma_events`.
/// Requires there to be a `raw` module in the same scope, with a type with the same name and fields
/// as the one that this macro is used on.
#[proc_macro_derive(FromRaw)]
pub fn derive_from_raw(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    expand_from_raw(input)
        .unwrap_or_else(|err| err.to_compile_error())
        .into()
}