Skip to main content

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}