ruma_macros/
lib.rs

1#![doc(html_favicon_url = "https://ruma.dev/favicon.ico")]
2#![doc(html_logo_url = "https://ruma.dev/images/logo.png")]
3//! Procedural macros used by ruma crates.
4//!
5//! See the documentation for the individual macros for usage details.
6
7#![cfg_attr(feature = "__internal_macro_expand", feature(proc_macro_expand))]
8#![warn(missing_docs)]
9#![allow(unreachable_pub)]
10// https://github.com/rust-lang/rust-clippy/issues/9029
11#![allow(clippy::derive_partial_eq_without_eq)]
12
13use identifiers::expand_id_zst;
14use proc_macro::TokenStream;
15use proc_macro2 as pm2;
16use quote::quote;
17use ruma_identifiers_validation::{
18    base64_public_key, event_id, mxc_uri, room_alias_id, room_id, room_version_id, server_name,
19    server_signing_key_version, user_id,
20};
21use syn::{parse_macro_input, DeriveInput, ItemEnum, ItemStruct};
22
23mod api;
24mod events;
25mod identifiers;
26mod serde;
27mod util;
28
29use self::{
30    api::{
31        request::{expand_derive_request, expand_request},
32        response::{expand_derive_response, expand_response},
33    },
34    events::{
35        event::expand_event,
36        event_content::expand_event_content,
37        event_enum::{expand_event_enums, expand_from_impls_derived},
38        event_parse::EventEnumInput,
39        event_type::expand_event_type_enum,
40    },
41    identifiers::IdentifierInput,
42    serde::{
43        as_str_as_ref_str::expand_as_str_as_ref_str,
44        debug_as_ref_str::expand_debug_as_ref_str,
45        deserialize_from_cow_str::expand_deserialize_from_cow_str,
46        display_as_ref_str::expand_display_as_ref_str,
47        enum_as_ref_str::expand_enum_as_ref_str,
48        enum_from_string::expand_enum_from_string,
49        eq_as_ref_str::expand_partial_eq_as_ref_str,
50        ord_as_ref_str::{expand_ord_as_ref_str, expand_partial_ord_as_ref_str},
51        serialize_as_ref_str::expand_serialize_as_ref_str,
52    },
53    util::{import_ruma_common, import_ruma_events},
54};
55
56/// Generates an enum to represent the various Matrix event types.
57///
58/// This macro also implements the necessary traits for the type to serialize and deserialize
59/// itself.
60///
61/// By default, the types generated by this macro get a `#[non_exhaustive]` attribute. This
62/// behavior can be controlled by setting the `ruma_unstable_exhaustive_types` compile-time
63/// `cfg` setting as `--cfg=ruma_unstable_exhaustive_types` using `RUSTFLAGS` or
64/// `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`). When that setting is
65/// activated, the attribute is not applied so the types are exhaustive.
66///
67/// # Examples
68///
69/// ```ignore
70/// # // HACK: This is "ignore" because of cyclical dependency drama.
71/// use ruma_macros::event_enum;
72///
73/// event_enum! {
74///     enum ToDevice {
75///         "m.any.event",
76///         "m.other.event",
77///     }
78///
79///     enum State {
80///         "m.more.events",
81///         "m.different.event",
82///     }
83/// }
84/// ```
85/// (The enum name has to be a valid identifier for `<EventKind as Parse>::parse`)
86///// TODO: Change above (`<EventKind as Parse>::parse`) to [] after fully qualified syntax is
87///// supported:  https://github.com/rust-lang/rust/issues/74563
88#[proc_macro]
89pub fn event_enum(input: TokenStream) -> TokenStream {
90    let event_enum_input = syn::parse_macro_input!(input as EventEnumInput);
91
92    let ruma_common = import_ruma_common();
93
94    let enums = event_enum_input
95        .enums
96        .iter()
97        .map(|e| expand_event_enums(e).unwrap_or_else(syn::Error::into_compile_error))
98        .collect::<pm2::TokenStream>();
99
100    let event_types = expand_event_type_enum(event_enum_input, ruma_common)
101        .unwrap_or_else(syn::Error::into_compile_error);
102
103    let tokens = quote! {
104        #enums
105        #event_types
106    };
107
108    tokens.into()
109}
110
111/// Generates an implementation of `ruma_events::EventContent`.
112///
113/// Also generates type aliases depending on the kind of event, with the final `Content` of the type
114/// name removed and prefixed added. For instance, a message-like event content type
115/// `FooEventContent` will have the following aliases generated:
116///
117/// * `type FooEvent = MessageLikeEvent<FooEventContent>`
118/// * `type SyncFooEvent = SyncMessageLikeEvent<FooEventContent>`
119/// * `type OriginalFooEvent = OriginalMessageLikeEvent<FooEventContent>`
120/// * `type OriginalSyncFooEvent = OriginalSyncMessageLikeEvent<FooEventContent>`
121/// * `type RedactedFooEvent = RedactedMessageLikeEvent<FooEventContent>`
122/// * `type RedactedSyncFooEvent = RedactedSyncMessageLikeEvent<FooEventContent>`
123///
124/// You can use `cargo doc` to find out more details, its `--document-private-items` flag also lets
125/// you generate documentation for binaries or private parts of a library.
126///
127/// By default, the type this macro is used on and the generated types get a `#[non_exhaustive]`
128/// attribute. This behavior can be controlled by setting the `ruma_unstable_exhaustive_types`
129/// compile-time `cfg` setting as `--cfg=ruma_unstable_exhaustive_types` using `RUSTFLAGS` or
130/// `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`). When that setting is
131/// activated, the attribute is not applied so the types are exhaustive.
132#[proc_macro_derive(EventContent, attributes(ruma_event))]
133pub fn derive_event_content(input: TokenStream) -> TokenStream {
134    let ruma_events = import_ruma_events();
135    let input = parse_macro_input!(input as DeriveInput);
136
137    expand_event_content(&input, &ruma_events).unwrap_or_else(syn::Error::into_compile_error).into()
138}
139
140/// Generates implementations needed to serialize and deserialize Matrix events.
141#[proc_macro_derive(Event, attributes(ruma_event))]
142pub fn derive_event(input: TokenStream) -> TokenStream {
143    let input = parse_macro_input!(input as DeriveInput);
144    expand_event(input).unwrap_or_else(syn::Error::into_compile_error).into()
145}
146
147/// Generates `From` implementations for event enums.
148#[proc_macro_derive(EventEnumFromEvent)]
149pub fn derive_from_event_to_enum(input: TokenStream) -> TokenStream {
150    let input = parse_macro_input!(input as DeriveInput);
151    expand_from_impls_derived(input).into()
152}
153
154/// Generate methods and trait impl's for ZST identifier type.
155///
156/// This macro generates an `Owned*` wrapper type for the identifier type. This wrapper type is
157/// variable, by default it'll use [`Box`], but it can be changed at compile time
158/// by setting `--cfg=ruma_identifiers_storage=...` using `RUSTFLAGS` or `.cargo/config.toml` (under
159/// `[build]` -> `rustflags = ["..."]`). Currently the only supported value is `Arc`, that uses
160/// [`Arc`](std::sync::Arc) as a wrapper type.
161///
162/// This macro implements:
163///
164/// * Conversions to and from string types, `AsRef<[u8]>` and `AsRef<str>`, as well as `as_str()`
165///   and `as_bytes()` methods. The borrowed type can be converted from a borrowed string without
166///   allocation.
167/// * Conversions to and from borrowed and owned type.
168/// * `Deref`, `AsRef` and `Borrow` to the borrowed type for the owned type.
169/// * `PartialEq` implementations for testing equality with string types and owned and borrowed
170///   types.
171///
172/// # Attributes
173///
174/// * `#[ruma_api(validate = PATH)]`: the path to a function to validate the string during parsing
175///   and deserialization. By default, the types implement `From` string types, when this is set
176///   they implement `TryFrom`.
177///
178/// # Examples
179///
180/// ```ignore
181/// # // HACK: This is "ignore" because of cyclical dependency drama.
182/// use ruma_macros::IdZst;
183///
184/// #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, IdZst)]
185/// #[ruma_id(validate = ruma_identifiers_validation::user_id::validate)]
186/// pub struct UserId(str);
187/// ```
188#[proc_macro_derive(IdZst, attributes(ruma_id))]
189pub fn derive_id_zst(input: TokenStream) -> TokenStream {
190    let input = parse_macro_input!(input as ItemStruct);
191    expand_id_zst(input).unwrap_or_else(syn::Error::into_compile_error).into()
192}
193
194/// Compile-time checked `EventId` construction.
195#[proc_macro]
196pub fn event_id(input: TokenStream) -> TokenStream {
197    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
198    assert!(event_id::validate(&id.value()).is_ok(), "Invalid event id");
199
200    let output = quote! {
201        <&#dollar_crate::EventId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
202    };
203
204    output.into()
205}
206
207/// Compile-time checked `RoomAliasId` construction.
208#[proc_macro]
209pub fn room_alias_id(input: TokenStream) -> TokenStream {
210    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
211    assert!(room_alias_id::validate(&id.value()).is_ok(), "Invalid room_alias_id");
212
213    let output = quote! {
214        <&#dollar_crate::RoomAliasId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
215    };
216
217    output.into()
218}
219
220/// Compile-time checked `RoomId` construction.
221#[proc_macro]
222pub fn room_id(input: TokenStream) -> TokenStream {
223    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
224    assert!(room_id::validate(&id.value()).is_ok(), "Invalid room_id");
225
226    let output = quote! {
227        <&#dollar_crate::RoomId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
228    };
229
230    output.into()
231}
232
233/// Compile-time checked `RoomVersionId` construction.
234#[proc_macro]
235pub fn room_version_id(input: TokenStream) -> TokenStream {
236    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
237    assert!(room_version_id::validate(&id.value()).is_ok(), "Invalid room_version_id");
238
239    let output = quote! {
240        <#dollar_crate::RoomVersionId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
241    };
242
243    output.into()
244}
245
246/// Compile-time checked `ServerSigningKeyVersion` construction.
247#[proc_macro]
248pub fn server_signing_key_version(input: TokenStream) -> TokenStream {
249    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
250    assert!(
251        server_signing_key_version::validate(&id.value()).is_ok(),
252        "Invalid server_signing_key_version"
253    );
254
255    let output = quote! {
256        <&#dollar_crate::ServerSigningKeyVersion as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
257    };
258
259    output.into()
260}
261
262/// Compile-time checked `ServerName` construction.
263#[proc_macro]
264pub fn server_name(input: TokenStream) -> TokenStream {
265    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
266    assert!(server_name::validate(&id.value()).is_ok(), "Invalid server_name");
267
268    let output = quote! {
269        <&#dollar_crate::ServerName as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
270    };
271
272    output.into()
273}
274
275/// Compile-time checked `MxcUri` construction.
276#[proc_macro]
277pub fn mxc_uri(input: TokenStream) -> TokenStream {
278    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
279    assert!(mxc_uri::validate(&id.value()).is_ok(), "Invalid mxc://");
280
281    let output = quote! {
282        <&#dollar_crate::MxcUri as ::std::convert::From<&str>>::from(#id)
283    };
284
285    output.into()
286}
287
288/// Compile-time checked `UserId` construction.
289///
290/// The user ID is validated using the same rules as `UserId::validate_strict()`.
291#[proc_macro]
292pub fn user_id(input: TokenStream) -> TokenStream {
293    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
294    assert!(user_id::validate_strict(&id.value()).is_ok(), "Invalid user_id");
295
296    let output = quote! {
297        <&#dollar_crate::UserId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
298    };
299
300    output.into()
301}
302
303/// Compile-time checked `Base64PublicKey` construction.
304#[proc_macro]
305pub fn base64_public_key(input: TokenStream) -> TokenStream {
306    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
307    assert!(base64_public_key::validate(&id.value()).is_ok(), "Invalid base64 public key");
308
309    let output = quote! {
310        <&#dollar_crate::DeviceKeyId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
311    };
312
313    output.into()
314}
315
316/// Derive the `AsRef<str>` trait for an enum.
317#[proc_macro_derive(AsRefStr, attributes(ruma_enum))]
318pub fn derive_enum_as_ref_str(input: TokenStream) -> TokenStream {
319    let input = parse_macro_input!(input as ItemEnum);
320    expand_enum_as_ref_str(&input).unwrap_or_else(syn::Error::into_compile_error).into()
321}
322
323/// Derive the `From<T: AsRef<str> + Into<Box<str>>>` trait for an enum.
324#[proc_macro_derive(FromString, attributes(ruma_enum))]
325pub fn derive_enum_from_string(input: TokenStream) -> TokenStream {
326    let input = parse_macro_input!(input as ItemEnum);
327    expand_enum_from_string(&input).unwrap_or_else(syn::Error::into_compile_error).into()
328}
329
330// FIXME: The following macros aren't actually interested in type details beyond name (and possibly
331//        generics in the future). They probably shouldn't use `DeriveInput`.
332
333/// Derive the `as_str()` method using the `AsRef<str>` implementation of the type.
334#[proc_macro_derive(AsStrAsRefStr, attributes(ruma_enum))]
335pub fn derive_as_str_as_ref_str(input: TokenStream) -> TokenStream {
336    let input = parse_macro_input!(input as DeriveInput);
337    expand_as_str_as_ref_str(&input.ident).unwrap_or_else(syn::Error::into_compile_error).into()
338}
339
340/// Derive the `fmt::Display` trait using the `AsRef<str>` implementation of the type.
341#[proc_macro_derive(DisplayAsRefStr)]
342pub fn derive_display_as_ref_str(input: TokenStream) -> TokenStream {
343    let input = parse_macro_input!(input as DeriveInput);
344    expand_display_as_ref_str(&input.ident).unwrap_or_else(syn::Error::into_compile_error).into()
345}
346
347/// Derive the `fmt::Debug` trait using the `AsRef<str>` implementation of the type.
348#[proc_macro_derive(DebugAsRefStr)]
349pub fn derive_debug_as_ref_str(input: TokenStream) -> TokenStream {
350    let input = parse_macro_input!(input as DeriveInput);
351    expand_debug_as_ref_str(&input.ident).unwrap_or_else(syn::Error::into_compile_error).into()
352}
353
354/// Derive the `Serialize` trait using the `AsRef<str>` implementation of the type.
355#[proc_macro_derive(SerializeAsRefStr)]
356pub fn derive_serialize_as_ref_str(input: TokenStream) -> TokenStream {
357    let input = parse_macro_input!(input as DeriveInput);
358    expand_serialize_as_ref_str(&input.ident).unwrap_or_else(syn::Error::into_compile_error).into()
359}
360
361/// Derive the `Deserialize` trait using the `From<Cow<str>>` implementation of the type.
362#[proc_macro_derive(DeserializeFromCowStr)]
363pub fn derive_deserialize_from_cow_str(input: TokenStream) -> TokenStream {
364    let input = parse_macro_input!(input as DeriveInput);
365    expand_deserialize_from_cow_str(&input.ident)
366        .unwrap_or_else(syn::Error::into_compile_error)
367        .into()
368}
369
370/// Derive the `PartialOrd` trait using the `AsRef<str>` implementation of the type.
371#[proc_macro_derive(PartialOrdAsRefStr)]
372pub fn derive_partial_ord_as_ref_str(input: TokenStream) -> TokenStream {
373    let input = parse_macro_input!(input as DeriveInput);
374    expand_partial_ord_as_ref_str(&input.ident)
375        .unwrap_or_else(syn::Error::into_compile_error)
376        .into()
377}
378
379/// Derive the `Ord` trait using the `AsRef<str>` implementation of the type.
380#[proc_macro_derive(OrdAsRefStr)]
381pub fn derive_ord_as_ref_str(input: TokenStream) -> TokenStream {
382    let input = parse_macro_input!(input as DeriveInput);
383    expand_ord_as_ref_str(&input.ident).unwrap_or_else(syn::Error::into_compile_error).into()
384}
385
386/// Derive the `PartialEq` trait using the `AsRef<str>` implementation of the type.
387#[proc_macro_derive(PartialEqAsRefStr)]
388pub fn derive_partial_eq_as_ref_str(input: TokenStream) -> TokenStream {
389    let input = parse_macro_input!(input as DeriveInput);
390    expand_partial_eq_as_ref_str(&input.ident).unwrap_or_else(syn::Error::into_compile_error).into()
391}
392
393/// Shorthand for the derives `AsRefStr`, `FromString`, `DisplayAsRefStr`, `DebugAsRefStr`,
394/// `SerializeAsRefStr` and `DeserializeFromCowStr`.
395#[proc_macro_derive(StringEnum, attributes(ruma_enum))]
396pub fn derive_string_enum(input: TokenStream) -> TokenStream {
397    fn expand_all(input: ItemEnum) -> syn::Result<proc_macro2::TokenStream> {
398        let as_ref_str_impl = expand_enum_as_ref_str(&input)?;
399        let from_string_impl = expand_enum_from_string(&input)?;
400        let as_str_impl = expand_as_str_as_ref_str(&input.ident)?;
401        let display_impl = expand_display_as_ref_str(&input.ident)?;
402        let debug_impl = expand_debug_as_ref_str(&input.ident)?;
403        let serialize_impl = expand_serialize_as_ref_str(&input.ident)?;
404        let deserialize_impl = expand_deserialize_from_cow_str(&input.ident)?;
405
406        Ok(quote! {
407            #as_ref_str_impl
408            #from_string_impl
409            #as_str_impl
410            #display_impl
411            #debug_impl
412            #serialize_impl
413            #deserialize_impl
414        })
415    }
416
417    let input = parse_macro_input!(input as ItemEnum);
418    expand_all(input).unwrap_or_else(syn::Error::into_compile_error).into()
419}
420
421/// A derive macro that generates no code, but registers the serde attribute so both `#[serde(...)]`
422/// and `#[cfg_attr(..., serde(...))]` are accepted on the type, its fields and (in case the input
423/// is an enum) variants fields.
424#[doc(hidden)]
425#[proc_macro_derive(_FakeDeriveSerde, attributes(serde))]
426pub fn fake_derive_serde(_input: TokenStream) -> TokenStream {
427    TokenStream::new()
428}
429
430/// > ⚠ If this is the only documentation you see, please navigate to the docs for
431/// > `ruma_common::api::request`, where actual documentation can be found.
432#[proc_macro_attribute]
433pub fn request(attr: TokenStream, item: TokenStream) -> TokenStream {
434    let attr = parse_macro_input!(attr);
435    let item = parse_macro_input!(item);
436    expand_request(attr, item).into()
437}
438
439/// > ⚠ If this is the only documentation you see, please navigate to the docs for
440/// > `ruma_common::api::response`, where actual documentation can be found.
441#[proc_macro_attribute]
442pub fn response(attr: TokenStream, item: TokenStream) -> TokenStream {
443    let attr = parse_macro_input!(attr);
444    let item = parse_macro_input!(item);
445    expand_response(attr, item).into()
446}
447
448/// Internal helper that the request macro delegates most of its work to.
449#[proc_macro_derive(Request, attributes(ruma_api))]
450pub fn derive_request(input: TokenStream) -> TokenStream {
451    let input = parse_macro_input!(input);
452    expand_derive_request(input).unwrap_or_else(syn::Error::into_compile_error).into()
453}
454
455/// Internal helper that the response macro delegates most of its work to.
456#[proc_macro_derive(Response, attributes(ruma_api))]
457pub fn derive_response(input: TokenStream) -> TokenStream {
458    let input = parse_macro_input!(input);
459    expand_derive_response(input).unwrap_or_else(syn::Error::into_compile_error).into()
460}
461
462/// A derive macro that generates no code, but registers the ruma_api attribute so both
463/// `#[ruma_api(...)]` and `#[cfg_attr(..., ruma_api(...))]` are accepted on the type, its fields
464/// and (in case the input is an enum) variants fields.
465#[doc(hidden)]
466#[proc_macro_derive(_FakeDeriveRumaApi, attributes(ruma_api))]
467pub fn fake_derive_ruma_api(_input: TokenStream) -> TokenStream {
468    TokenStream::new()
469}