palpo_macros/
lib.rs

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