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}