palpo_macros/
lib.rs

1//! Procedural macros used by palpo crates.
2//!
3//! See the documentation for the individual macros for usage details.
4#![allow(unreachable_pub)]
5// https://github.com/rust-lang/rust-clippy/issues/9029
6#![allow(clippy::derive_partial_eq_without_eq)]
7
8use identifiers::expand_id_zst;
9use palpo_identifiers_validation::{
10    device_key_id, event_id, key_id, mxc_uri, room_alias_id, room_id, room_version_id, server_name, user_id,
11};
12use proc_macro::TokenStream;
13use proc_macro2 as pm2;
14use quote::quote;
15use syn::{DeriveInput, ItemEnum, ItemStruct, parse_macro_input};
16
17mod events;
18mod identifiers;
19mod serde;
20mod util;
21
22use self::{
23    events::{
24        event::expand_event,
25        event_content::expand_event_content,
26        event_enum::{expand_event_enums, expand_from_impls_derived},
27        event_parse::EventEnumInput,
28        event_type::expand_event_type_enum,
29    },
30    identifiers::IdentifierInput,
31    serde::{
32        as_str_as_ref_str::expand_as_str_as_ref_str,
33        debug_as_ref_str::expand_debug_as_ref_str,
34        deserialize_from_cow_str::expand_deserialize_from_cow_str,
35        display_as_ref_str::expand_display_as_ref_str,
36        enum_as_ref_str::expand_enum_as_ref_str,
37        enum_from_string::expand_enum_from_string,
38        eq_as_ref_str::expand_partial_eq_as_ref_str,
39        ord_as_ref_str::{expand_ord_as_ref_str, expand_partial_ord_as_ref_str},
40        serialize_as_ref_str::expand_serialize_as_ref_str,
41    },
42    util::import_palpo_core,
43};
44
45/// Generates an enum to represent the various Matrix event types.
46///
47/// This macro also implements the necessary traits for the type to serialize and deserialize
48/// itself.
49///
50/// # Examples
51///
52/// ```ignore
53/// # // HACK: This is "ignore" because of cyclical dependency drama.
54/// use palpo_macros::event_enum;
55///
56/// event_enum! {
57///     enum ToDevice {
58///         "m.any.event",
59///         "m.other.event",
60///     }
61///
62///     enum State {
63///         "m.more.events",
64///         "m.different.event",
65///     }
66/// }
67/// ```
68/// (The enum name has to be a valid identifier for `<EventKind as Parse>::parse`)
69///// TODO: Change above (`<EventKind as Parse>::parse`) to [] after fully qualified syntax is
70///// supported:  https://github.com/rust-lang/rust/issues/74563
71#[proc_macro]
72pub fn event_enum(input: TokenStream) -> TokenStream {
73    let event_enum_input = syn::parse_macro_input!(input as EventEnumInput);
74
75    let palpo_core = import_palpo_core();
76
77    let enums = event_enum_input
78        .enums
79        .iter()
80        .map(|e| expand_event_enums(e).unwrap_or_else(syn::Error::into_compile_error))
81        .collect::<pm2::TokenStream>();
82
83    let event_types =
84        expand_event_type_enum(event_enum_input, &palpo_core).unwrap_or_else(syn::Error::into_compile_error);
85
86    let tokens = quote! {
87        #enums
88        #event_types
89    };
90
91    tokens.into()
92}
93
94/// Generates an implementation of `palpo_core::events::EventContent`.
95///
96/// Also generates type aliases depending on the kind of event, with the final `Content` of the type
97/// name removed and prefixed added. For instance, a message-like event content type
98/// `FooEventContent` will have the following aliases generated:
99///
100/// * `type FooEvent = MessageLikeEvent<FooEventContent>`
101/// * `type SyncFooEvent = SyncMessageLikeEvent<FooEventContent>`
102/// * `type OriginalFooEvent = OriginalMessageLikeEvent<FooEventContent>`
103/// * `type OriginalSyncFooEvent = OriginalSyncMessageLikeEvent<FooEventContent>`
104/// * `type RedactedFooEvent = RedactedMessageLikeEvent<FooEventContent>`
105/// * `type RedactedSyncFooEvent = RedactedSyncMessageLikeEvent<FooEventContent>`
106///
107/// You can use `cargo doc` to find out more details, its `--document-private-items` flag also lets
108/// you generate documentation for binaries or private parts of a library.
109#[proc_macro_derive(EventContent, attributes(palpo_event))]
110pub fn derive_event_content(input: TokenStream) -> TokenStream {
111    let palpo_core = import_palpo_core();
112    let input = parse_macro_input!(input as DeriveInput);
113
114    expand_event_content(&input, &palpo_core)
115        .unwrap_or_else(syn::Error::into_compile_error)
116        .into()
117}
118
119/// Generates implementations needed to serialize and deserialize Matrix events.
120#[proc_macro_derive(Event, attributes(palpo_event))]
121pub fn derive_event(input: TokenStream) -> TokenStream {
122    let input = parse_macro_input!(input as DeriveInput);
123    expand_event(input)
124        .unwrap_or_else(syn::Error::into_compile_error)
125        .into()
126}
127
128/// Generates `From` implementations for event enums.
129#[proc_macro_derive(EventEnumFromEvent)]
130pub fn derive_from_event_to_enum(input: TokenStream) -> TokenStream {
131    let input = parse_macro_input!(input as DeriveInput);
132    expand_from_impls_derived(input).into()
133}
134
135/// Generate methods and trait impl's for ZST identifier type.
136#[proc_macro_derive(IdZst, attributes(palpo_id))]
137pub fn derive_id_zst(input: TokenStream) -> TokenStream {
138    let input = parse_macro_input!(input as ItemStruct);
139    expand_id_zst(input)
140        .unwrap_or_else(syn::Error::into_compile_error)
141        .into()
142}
143
144/// Compile-time checked `DeviceKeyId` construction.
145#[proc_macro]
146pub fn device_key_id(input: TokenStream) -> TokenStream {
147    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
148    assert!(device_key_id::validate(&id.value()).is_ok(), "Invalid device key id");
149
150    let output = quote! {
151        <&#dollar_crate::DeviceKeyId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
152    };
153
154    output.into()
155}
156
157/// Compile-time checked `EventId` construction.
158#[proc_macro]
159pub fn event_id(input: TokenStream) -> TokenStream {
160    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
161    assert!(event_id::validate(&id.value()).is_ok(), "Invalid event id");
162
163    let output = quote! {
164        <&#dollar_crate::EventId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
165    };
166
167    output.into()
168}
169
170/// Compile-time checked `RoomAliasId` construction.
171#[proc_macro]
172pub fn room_alias_id(input: TokenStream) -> TokenStream {
173    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
174    assert!(room_alias_id::validate(&id.value()).is_ok(), "Invalid room_alias_id");
175
176    let output = quote! {
177        <&#dollar_crate::RoomAliasId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
178    };
179
180    output.into()
181}
182
183/// Compile-time checked `RoomId` construction.
184#[proc_macro]
185pub fn room_id(input: TokenStream) -> TokenStream {
186    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
187    assert!(room_id::validate(&id.value()).is_ok(), "Invalid room_id");
188
189    let output = quote! {
190        <&#dollar_crate::RoomId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
191    };
192
193    output.into()
194}
195
196/// Compile-time checked `RoomVersionId` construction.
197#[proc_macro]
198pub fn room_version_id(input: TokenStream) -> TokenStream {
199    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
200    assert!(
201        room_version_id::validate(&id.value()).is_ok(),
202        "Invalid room_version_id"
203    );
204
205    let output = quote! {
206        <#dollar_crate::RoomVersionId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
207    };
208
209    output.into()
210}
211
212/// Compile-time checked `ServerSigningKeyId` construction.
213#[proc_macro]
214pub fn server_signing_key_id(input: TokenStream) -> TokenStream {
215    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
216    assert!(key_id::validate(&id.value()).is_ok(), "Invalid server_signing_key_id");
217
218    let output = quote! {
219        <&#dollar_crate::ServerSigningKeyId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
220    };
221
222    output.into()
223}
224
225/// Compile-time checked `ServerName` construction.
226#[proc_macro]
227pub fn server_name(input: TokenStream) -> TokenStream {
228    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
229    assert!(server_name::validate(&id.value()).is_ok(), "Invalid server_name");
230
231    let output = quote! {
232        <&#dollar_crate::ServerName as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
233    };
234
235    output.into()
236}
237
238/// Compile-time checked `MxcUri` construction.
239#[proc_macro]
240pub fn mxc_uri(input: TokenStream) -> TokenStream {
241    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
242    assert!(mxc_uri::validate(&id.value()).is_ok(), "Invalid mxc://");
243
244    let output = quote! {
245        <&#dollar_crate::MxcUri as ::std::convert::From<&str>>::from(#id)
246    };
247
248    output.into()
249}
250
251/// Compile-time checked `UserId` construction.
252#[proc_macro]
253pub fn user_id(input: TokenStream) -> TokenStream {
254    let IdentifierInput { dollar_crate, id } = parse_macro_input!(input as IdentifierInput);
255    assert!(user_id::validate(&id.value()).is_ok(), "Invalid user_id");
256
257    let output = quote! {
258        <&#dollar_crate::UserId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
259    };
260
261    output.into()
262}
263
264/// Derive the `AsRef<str>` trait for an enum.
265#[proc_macro_derive(AsRefStr, attributes(palpo_enum))]
266pub fn derive_enum_as_ref_str(input: TokenStream) -> TokenStream {
267    let input = parse_macro_input!(input as ItemEnum);
268    expand_enum_as_ref_str(&input)
269        .unwrap_or_else(syn::Error::into_compile_error)
270        .into()
271}
272
273/// Derive the `From<T: AsRef<str> + Into<Box<str>>>` trait for an enum.
274#[proc_macro_derive(FromString, attributes(palpo_enum))]
275pub fn derive_enum_from_string(input: TokenStream) -> TokenStream {
276    let input = parse_macro_input!(input as ItemEnum);
277    expand_enum_from_string(&input)
278        .unwrap_or_else(syn::Error::into_compile_error)
279        .into()
280}
281
282// FIXME: The following macros aren't actually interested in type details beyond name (and possibly
283//        generics in the future). They probably shouldn't use `DeriveInput`.
284
285/// Derive the `as_str()` method using the `AsRef<str>` implementation of the type.
286#[proc_macro_derive(AsStrAsRefStr, attributes(palpo_enum))]
287pub fn derive_as_str_as_ref_str(input: TokenStream) -> TokenStream {
288    let input = parse_macro_input!(input as DeriveInput);
289    expand_as_str_as_ref_str(&input.ident)
290        .unwrap_or_else(syn::Error::into_compile_error)
291        .into()
292}
293
294/// Derive the `fmt::Display` trait using the `AsRef<str>` implementation of the type.
295#[proc_macro_derive(DisplayAsRefStr)]
296pub fn derive_display_as_ref_str(input: TokenStream) -> TokenStream {
297    let input = parse_macro_input!(input as DeriveInput);
298    expand_display_as_ref_str(&input.ident)
299        .unwrap_or_else(syn::Error::into_compile_error)
300        .into()
301}
302
303/// Derive the `fmt::Debug` trait using the `AsRef<str>` implementation of the type.
304#[proc_macro_derive(DebugAsRefStr)]
305pub fn derive_debug_as_ref_str(input: TokenStream) -> TokenStream {
306    let input = parse_macro_input!(input as DeriveInput);
307    expand_debug_as_ref_str(&input.ident)
308        .unwrap_or_else(syn::Error::into_compile_error)
309        .into()
310}
311
312/// Derive the `Serialize` trait using the `AsRef<str>` implementation of the type.
313#[proc_macro_derive(SerializeAsRefStr)]
314pub fn derive_serialize_as_ref_str(input: TokenStream) -> TokenStream {
315    let input = parse_macro_input!(input as DeriveInput);
316    expand_serialize_as_ref_str(&input.ident)
317        .unwrap_or_else(syn::Error::into_compile_error)
318        .into()
319}
320
321/// Derive the `Deserialize` trait using the `From<Cow<str>>` implementation of the type.
322#[proc_macro_derive(DeserializeFromCowStr)]
323pub fn derive_deserialize_from_cow_str(input: TokenStream) -> TokenStream {
324    let input = parse_macro_input!(input as DeriveInput);
325    expand_deserialize_from_cow_str(&input.ident)
326        .unwrap_or_else(syn::Error::into_compile_error)
327        .into()
328}
329
330/// Derive the `PartialOrd` trait using the `AsRef<str>` implementation of the type.
331#[proc_macro_derive(PartialOrdAsRefStr)]
332pub fn derive_partial_ord_as_ref_str(input: TokenStream) -> TokenStream {
333    let input = parse_macro_input!(input as DeriveInput);
334    expand_partial_ord_as_ref_str(&input.ident)
335        .unwrap_or_else(syn::Error::into_compile_error)
336        .into()
337}
338
339/// Derive the `Ord` trait using the `AsRef<str>` implementation of the type.
340#[proc_macro_derive(OrdAsRefStr)]
341pub fn derive_ord_as_ref_str(input: TokenStream) -> TokenStream {
342    let input = parse_macro_input!(input as DeriveInput);
343    expand_ord_as_ref_str(&input.ident)
344        .unwrap_or_else(syn::Error::into_compile_error)
345        .into()
346}
347
348/// Derive the `PartialEq` trait using the `AsRef<str>` implementation of the type.
349#[proc_macro_derive(PartialEqAsRefStr)]
350pub fn derive_partial_eq_as_ref_str(input: TokenStream) -> TokenStream {
351    let input = parse_macro_input!(input as DeriveInput);
352    expand_partial_eq_as_ref_str(&input.ident)
353        .unwrap_or_else(syn::Error::into_compile_error)
354        .into()
355}
356
357/// Shorthand for the derives `AsRefStr`, `FromString`, `DisplayAsRefStr`, `DebugAsRefStr`,
358/// `SerializeAsRefStr` and `DeserializeFromCowStr`.
359#[proc_macro_derive(StringEnum, attributes(palpo_enum))]
360pub fn derive_string_enum(input: TokenStream) -> TokenStream {
361    fn expand_all(input: ItemEnum) -> syn::Result<proc_macro2::TokenStream> {
362        let as_ref_str_impl = expand_enum_as_ref_str(&input)?;
363        let from_string_impl = expand_enum_from_string(&input)?;
364        let as_str_impl = expand_as_str_as_ref_str(&input.ident)?;
365        let display_impl = expand_display_as_ref_str(&input.ident)?;
366        let debug_impl = expand_debug_as_ref_str(&input.ident)?;
367        let serialize_impl = expand_serialize_as_ref_str(&input.ident)?;
368        let deserialize_impl = expand_deserialize_from_cow_str(&input.ident)?;
369
370        Ok(quote! {
371            #as_ref_str_impl
372            #from_string_impl
373            #as_str_impl
374            #display_impl
375            #debug_impl
376            #serialize_impl
377            #deserialize_impl
378        })
379    }
380
381    let input = parse_macro_input!(input as ItemEnum);
382    expand_all(input).unwrap_or_else(syn::Error::into_compile_error).into()
383}