Skip to main content

ironstate_derive/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/kassian-dev/ironstate/main/assets/logo.png",
3    html_favicon_url = "https://raw.githubusercontent.com/kassian-dev/ironstate/main/assets/favicon-32.png"
4)]
5//! Derive macros for [`ironstate`](https://docs.rs/ironstate).
6//!
7//! - `#[derive(StateMachine)]` generates the structural metadata a machine
8//!   needs: its initial state, terminal states, per-state event-kind
9//!   restrictions, and (with `version`/`history`) versioned restore.
10//! - `#[derive(Event)]` reads `#[event_kind]` and `#[likelihood]` and generates
11//!   the event metadata the runtime and verification macros consume.
12#![warn(missing_docs)]
13
14use proc_macro::TokenStream;
15use syn::{DeriveInput, parse_macro_input};
16
17mod common;
18mod event;
19mod statemachine;
20
21/// Derive `StateMachine` for an enum of states.
22///
23/// ```ignore
24/// #[derive(StateMachine, Clone, Debug, PartialEq)]
25/// #[state_machine(initial = Draft, terminal = [Published, Archived])]
26/// enum Article { Draft, Review, Published, Archived }
27/// ```
28///
29/// # Data-carrying fields must implement `Default`
30///
31/// `analyze!` and `test!` walk every state variant, and the derive builds one
32/// representative per variant to walk. Analysis is variant-level, so any
33/// data-carrying fields are filled with `Default::default()` — the payload types
34/// must therefore implement `Default`, or you can hand-write the `StateMachine` impl.
35/// Fieldless enums (including the aggregate tier's phase machines) need nothing
36/// extra.
37#[proc_macro_derive(StateMachine, attributes(state_machine, only_accepts))]
38pub fn derive_state_machine(input: TokenStream) -> TokenStream {
39    let input = parse_macro_input!(input as DeriveInput);
40    statemachine::derive(input)
41        .unwrap_or_else(syn::Error::into_compile_error)
42        .into()
43}
44
45/// Derive `Event` for an enum of events.
46///
47/// ```ignore
48/// #[derive(Event, Clone, Debug, PartialEq)]
49/// enum Edit {
50///     Submit,
51///     #[event_kind = "operator"]
52///     Approve,
53///     #[likelihood = "rare"]
54///     Reject,
55/// }
56/// ```
57///
58/// # Data-carrying fields must implement `Default`
59///
60/// As with `StateMachine`, variant enumeration builds one representative per
61/// variant and fills its fields with `Default::default()`, so the payload field
62/// types of a data-carrying event variant must implement `Default` — the event
63/// enum itself never has to. Alternatively, hand-write the [`EventKind`] impl
64/// this derive would have generated.
65///
66/// [`EventKind`]: https://docs.rs/ironstate/latest/ironstate/trait.EventKind.html
67#[proc_macro_derive(Event, attributes(event_kind, likelihood))]
68pub fn derive_event(input: TokenStream) -> TokenStream {
69    let input = parse_macro_input!(input as DeriveInput);
70    event::derive(input)
71        .unwrap_or_else(syn::Error::into_compile_error)
72        .into()
73}