tokio_fsm_macros/lib.rs
1#![warn(missing_docs)]
2//! Proc macro for generating Tokio async finite state machines.
3
4use darling::FromMeta;
5use proc_macro::TokenStream;
6use syn::{ItemImpl, parse_macro_input};
7
8mod attrs;
9mod codegen;
10mod validation;
11
12/// Generates an asynchronous Finite State Machine (FSM) from an `impl` block.
13///
14/// This attribute transforms a standard Rust `impl` block into a compile-time
15/// validated state machine driven by a background Tokio task.
16///
17/// # Arguments
18///
19/// * `initial = StateName`: (Required) The name of the starting state.
20/// * `channel_size = usize`: (Optional) The capacity of the internal event
21/// queue (default: 100).
22/// * `tracing = true`: (Optional) Enables tracing instrumentation in the
23/// generated runtime.
24/// * `serde = true`: (Optional) Derives serde support for generated state and
25/// event enums when the `tokio-fsm` crate enables its `serde` feature.
26///
27/// # Generated Types
28///
29/// The macro generates several types based on the name of the `impl` block
30/// (e.g., `WorkerFsm`):
31///
32/// * `WorkerFsmState`: An enum containing all discovered states.
33/// * `WorkerFsmEvent`: An enum containing all discovered events and their data
34/// payloads.
35/// * `WorkerFsmHandle`: A cloneable handle used to interact with the FSM (send
36/// events, query state).
37/// * `WorkerFsmTask`: A [`Future`](std::future::Future) that must be awaited to
38/// run the FSM. Resolves to `Result<Context, TaskError<E>>`.
39///
40/// # Handlers & Attributes
41///
42/// Within the `impl` block, use the following attributes on `async fn` methods:
43///
44/// * `#[on(state = S, event = E)]`: Maps a handler to a specific state and
45/// event trigger.
46/// * `#[state_timeout(duration = "30s")]`: Configures a timeout for the state
47/// reached *after* this transition.
48/// * `#[on_timeout]`: Marks a method as the handler to call when a state
49/// timeout occurs.
50///
51/// A single handler method cannot combine `#[on(...)]` and `#[on_timeout]`.
52/// Define separate methods for event-driven and timeout-driven transitions.
53///
54/// Event and timeout handlers may return:
55///
56/// * `Transition<Next>`
57/// * `Result<Transition<Next>, Transition<Other>>`
58/// * `Result<Transition<Next>, E>`
59///
60/// # Examples
61///
62/// ```rust,ignore
63/// use tokio_fsm::{Transition, fsm};
64///
65/// pub struct MyContext;
66///
67/// #[fsm(initial = Idle)]
68/// impl MyFsm {
69/// type Context = MyContext;
70/// type Error = std::convert::Infallible;
71///
72/// #[on(state = Idle, event = Start)]
73/// #[state_timeout(duration = "10s")]
74/// async fn on_start(&mut self) -> Transition<Running> {
75/// Transition::to(Running)
76/// }
77///
78/// #[on_timeout]
79/// async fn handle_timeout(&mut self) -> Transition<Idle> {
80/// Transition::to(Idle)
81/// }
82/// }
83/// ```
84#[proc_macro_attribute]
85pub fn fsm(args: TokenStream, input: TokenStream) -> TokenStream {
86 let attr_args = match darling::ast::NestedMeta::parse_meta_list(args.into()) {
87 Ok(v) => v,
88 Err(e) => return e.to_compile_error().into(),
89 };
90 let input_impl = parse_macro_input!(input as ItemImpl);
91
92 let fsm_args = match attrs::FsmArgs::from_list(&attr_args) {
93 Ok(args) => args,
94 Err(e) => return TokenStream::from(e.write_errors()),
95 };
96
97 match generate_fsm(fsm_args, input_impl) {
98 Ok(tokens) => tokens.into(),
99 Err(e) => e.to_compile_error().into(),
100 }
101}
102
103fn generate_fsm(args: attrs::FsmArgs, input: ItemImpl) -> syn::Result<proc_macro2::TokenStream> {
104 // 1. Parse + Validate
105 let fsm = validation::FsmStructure::parse(args, &input)?;
106
107 // 2. Generate code
108 codegen::generate(&fsm, &input)
109}