Skip to main content

maiko_macros/
lib.rs

1//! Procedural macros for the Maiko actor runtime.
2//!
3//! - `#[derive(Event)]`: Implements `maiko::Event` for your type, preserving generics and bounds.
4//! - `#[derive(Label)]`: Implements `maiko::Label` for enums, returning variant names.
5//! - `#[derive(SelfRouting)]`: Implements `maiko::Topic<T> for T` for event-as-topic routing.
6//!
7//! Usage:
8//! ```rust,ignore
9//! use maiko::{Event, Label, SelfRouting};
10//!
11//! // Simple event without topic routing
12//! #[derive(Clone, Debug, Event)]
13//! enum MyEvent { Foo, Bar }
14//!
15//! // Event with labels for observability (logging, diagrams)
16//! #[derive(Clone, Debug, Event, Label)]
17//! enum MyEvent { Foo, Bar }
18//!
19//! // Event that routes itself (event-as-topic pattern)
20//! #[derive(Clone, Debug, Hash, PartialEq, Eq, Event, SelfRouting, Label)]
21//! enum PingPong { Ping, Pong }
22//! ```
23use proc_macro::TokenStream;
24use quote::quote;
25use syn::{Data, DeriveInput, Fields, parse_macro_input};
26
27/// Derives `Event` marker trait for the type.
28#[proc_macro_derive(Event)]
29pub fn derive_event(input: TokenStream) -> proc_macro::TokenStream {
30    let input = parse_macro_input!(input as DeriveInput);
31    let ident = input.ident;
32    let generics = input.generics;
33
34    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
35
36    let expanded = quote! {
37        impl #impl_generics maiko::Event for #ident #ty_generics #where_clause {}
38    };
39    TokenStream::from(expanded)
40}
41
42/// Derives `Label` trait for enums, returning variant names.
43///
44/// For each variant, `label()` returns the variant name as a static string.
45/// This is useful for logging, monitoring, and diagram generation.
46///
47/// # Example
48///
49/// ```rust,ignore
50/// use maiko::Label;
51///
52/// #[derive(Label)]
53/// enum MyTopic {
54///     SensorData,
55///     Alerts,
56/// }
57///
58/// assert_eq!(MyTopic::SensorData.label(), "SensorData");
59/// assert_eq!(MyTopic::Alerts.label(), "Alerts");
60/// ```
61///
62/// # Panics
63///
64/// Compilation fails if used on a struct (only enums are supported).
65#[proc_macro_derive(Label)]
66pub fn derive_label(input: TokenStream) -> proc_macro::TokenStream {
67    let input = parse_macro_input!(input as DeriveInput);
68    let ident = input.ident;
69    let generics = input.generics;
70
71    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
72
73    let label_impl = match &input.data {
74        Data::Enum(data_enum) => {
75            let match_arms = data_enum.variants.iter().map(|variant| {
76                let variant_ident = &variant.ident;
77                let variant_name = variant_ident.to_string();
78
79                // Handle different field types (unit, tuple, struct)
80                let pattern = match &variant.fields {
81                    Fields::Unit => quote! { Self::#variant_ident },
82                    Fields::Unnamed(_) => quote! { Self::#variant_ident(..) },
83                    Fields::Named(_) => quote! { Self::#variant_ident { .. } },
84                };
85
86                quote! {
87                    #pattern => ::std::borrow::Cow::Borrowed(#variant_name)
88                }
89            });
90
91            quote! {
92                fn label(&self) -> ::std::borrow::Cow<'static, str> {
93                    match self {
94                        #(#match_arms),*
95                    }
96                }
97            }
98        }
99        _ => {
100            return syn::Error::new_spanned(ident, "Label can only be derived for enums")
101                .to_compile_error()
102                .into();
103        }
104    };
105
106    let expanded = quote! {
107        impl #impl_generics maiko::Label for #ident #ty_generics #where_clause {
108            #label_impl
109        }
110    };
111    TokenStream::from(expanded)
112}
113
114/// Derives `Topic<Self> for Self` enabling event-as-topic routing.
115///
116/// When an event type is used as its own topic, each variant becomes a distinct
117/// routing category. This is common in systems like Kafka where topic names
118/// match event types.
119///
120/// # Requirements
121///
122/// The type must also derive or implement:
123/// - `Clone` (for `from_event` to clone the event)
124/// - `Hash`, `PartialEq`, `Eq` (required by `Topic` trait)
125/// - `Event` (to be used in the actor system)
126///
127/// # Example
128///
129/// ```rust,ignore
130/// use maiko::{Event, SelfRouting};
131///
132/// #[derive(Clone, Debug, Hash, PartialEq, Eq, Event, SelfRouting)]
133/// enum PingPongEvent {
134///     Ping,
135///     Pong,
136/// }
137///
138/// // Now you can use PingPongEvent as both event and topic:
139/// // Supervisor::<PingPongEvent, PingPongEvent>::default()
140/// ```
141#[proc_macro_derive(SelfRouting)]
142pub fn derive_self_routing(input: TokenStream) -> proc_macro::TokenStream {
143    let input = parse_macro_input!(input as DeriveInput);
144    let ident = input.ident;
145    let generics = input.generics;
146
147    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
148
149    let expanded = quote! {
150        impl #impl_generics maiko::Topic<#ident #ty_generics> for #ident #ty_generics #where_clause {
151            fn from_event(event: &Self) -> Self {
152                event.clone()
153            }
154        }
155    };
156    TokenStream::from(expanded)
157}