Skip to main content

temporalio_macros/
lib.rs

1use proc_macro::TokenStream;
2use syn::parse_macro_input;
3
4mod activities_definitions;
5mod fsm_impl;
6mod macro_utils;
7mod workflow_definitions;
8
9/// Can be used to define Activities for invocation and execution. Using this macro requires that
10/// you also depend on the `temporalio_sdk` crate.
11///
12/// For a usage example, see that crate's documentation.
13#[proc_macro_attribute]
14pub fn activities(_attr: TokenStream, item: TokenStream) -> TokenStream {
15    let def: activities_definitions::ActivitiesDefinition =
16        parse_macro_input!(item as activities_definitions::ActivitiesDefinition);
17    def.codegen()
18}
19
20/// Marks a method within an `#[activities]` impl block as an activity.
21/// This attribute is processed by the `#[activities]` macro and should not be used standalone.
22#[proc_macro_attribute]
23pub fn activity(_attr: TokenStream, item: TokenStream) -> TokenStream {
24    item
25}
26
27/// Marks a struct as a workflow definition.
28///
29/// This attribute can optionally specify a custom workflow name:
30/// `#[workflow(name = "my-custom-workflow")]`
31///
32/// If no name is specified, the struct name is used as the workflow type name.
33///
34/// This attribute must be used in conjunction with `#[workflow_methods]` on an impl block.
35#[proc_macro_attribute]
36pub fn workflow(_attr: TokenStream, item: TokenStream) -> TokenStream {
37    // Pass through - the struct is not modified, just marked for workflow_methods
38    item
39}
40
41/// Defines workflow methods for a workflow struct. Using this macro requires that
42/// you also depend on the `temporalio_sdk` crate.
43///
44/// This macro processes an impl block and generates:
45/// - Marker structs for each workflow method
46/// - Trait implementations for workflow definition and execution
47/// - Registration code for workers
48///
49/// ## Macro Attributes
50///
51/// - `factory_only` - When set, the workflow must be registered using
52///   `register_workflow_with_factory` and does not need to implement `Default` or define an `#[init]`
53///   method. Ex: `#[workflow_methods(factory_only)]`
54///
55/// ## Method Attributes
56///
57/// - `#[init]` - Optional initialization method. Signature: `fn new(input: T, ctx: &WorkflowContext) -> Self`
58/// - `#[run]` - Required main workflow function. Signature: `async fn run(&mut self, ctx: &mut WorkflowContext) -> WorkflowResult<T>`
59/// - `#[signal]` - Signal handler. Sync: `fn signal(&mut self, ctx: &mut SyncWorkflowContext, input: T)`. Async: `async fn signal(ctx: &mut WorkflowContext, input: T)`
60/// - `#[query]` - Query handler. Signature: `fn query(&self, ctx: &WorkflowContextView, input: T) -> R` (must NOT be async)
61/// - `#[update]` - Update handler. Sync: `fn update(&mut self, ctx: &mut SyncWorkflowContext, input: T) -> R`. Async: `async fn update(ctx: &mut WorkflowContext, input: T) -> R`
62///
63/// For a usage example, see the `temporalio_sdk` crate's documentation.
64#[proc_macro_attribute]
65pub fn workflow_methods(attr: TokenStream, item: TokenStream) -> TokenStream {
66    let factory_only = !attr.is_empty() && attr.to_string().contains("factory_only");
67    let def: workflow_definitions::WorkflowMethodsDefinition =
68        parse_macro_input!(item as workflow_definitions::WorkflowMethodsDefinition);
69    def.codegen_with_options(factory_only)
70}
71
72/// Marks a method within a `#[workflow_methods]` impl block as the initialization method.
73/// This attribute is processed by the `#[workflow_methods]` macro and should not be used standalone.
74#[proc_macro_attribute]
75pub fn init(_attr: TokenStream, item: TokenStream) -> TokenStream {
76    item
77}
78
79/// Marks a method within a `#[workflow_methods]` impl block as the main run method.
80/// This attribute is processed by the `#[workflow_methods]` macro and should not be used standalone.
81#[proc_macro_attribute]
82pub fn run(_attr: TokenStream, item: TokenStream) -> TokenStream {
83    item
84}
85
86/// Marks a method within a `#[workflow_methods]` impl block as a signal handler.
87/// This attribute is processed by the `#[workflow_methods]` macro and should not be used standalone.
88///
89/// Supports an optional `name` parameter to override the signal name:
90/// `#[signal(name = "my_signal")]`
91#[proc_macro_attribute]
92pub fn signal(_attr: TokenStream, item: TokenStream) -> TokenStream {
93    item
94}
95
96/// Marks a method within a `#[workflow_methods]` impl block as a query handler.
97/// This attribute is processed by the `#[workflow_methods]` macro and should not be used standalone.
98///
99/// Supports an optional `name` parameter to override the query name:
100/// `#[query(name = "my_query")]`
101#[proc_macro_attribute]
102pub fn query(_attr: TokenStream, item: TokenStream) -> TokenStream {
103    item
104}
105
106/// Marks a method within a `#[workflow_methods]` impl block as an update handler.
107/// This attribute is processed by the `#[workflow_methods]` macro and should not be used standalone.
108///
109/// Supports an optional `name` parameter to override the update name:
110/// `#[update(name = "my_update")]`
111#[proc_macro_attribute]
112pub fn update(_attr: TokenStream, item: TokenStream) -> TokenStream {
113    item
114}
115
116/// Marks a method within a `#[workflow_methods]` impl block as a validator for an update handler.
117/// This attribute is processed by the `#[workflow_methods]` macro and should not be used standalone.
118///
119/// The parameter specifies which update this validator applies to:
120/// `#[update_validator(my_update)]`
121///
122/// The validator method must:
123/// - Take `&self` (not `&mut self`)
124/// - Take `&WorkflowContextView` as the first parameter
125/// - Take a reference to the update's input type as the second parameter
126/// - Return `Result<(), Box<dyn std::error::Error + Send + Sync>>`
127#[proc_macro_attribute]
128pub fn update_validator(_attr: TokenStream, item: TokenStream) -> TokenStream {
129    item
130}
131
132/// Parses a DSL for defining finite state machines, and produces code implementing the
133/// [StateMachine](trait.StateMachine.html) trait.
134///
135/// An example state machine definition of a card reader for unlocking a door:
136/// ```
137/// use std::convert::Infallible;
138/// use temporalio_common::fsm_trait::{StateMachine, TransitionResult};
139/// use temporalio_macros::fsm;
140///
141/// fsm! {
142///     name CardReader; command Commands; error Infallible; shared_state SharedState;
143///
144///     Locked --(CardReadable(CardData), shared on_card_readable) --> ReadingCard;
145///     Locked --(CardReadable(CardData), shared on_card_readable) --> Locked;
146///     ReadingCard --(CardAccepted, on_card_accepted) --> DoorOpen;
147///     ReadingCard --(CardRejected, on_card_rejected) --> Locked;
148///     DoorOpen --(DoorClosed, on_door_closed) --> Locked;
149/// }
150///
151/// #[derive(Clone)]
152/// pub struct SharedState {
153///     last_id: Option<String>,
154/// }
155///
156/// #[derive(Debug, Clone, Eq, PartialEq, Hash)]
157/// pub enum Commands {
158///     StartBlinkingLight,
159///     StopBlinkingLight,
160///     ProcessData(CardData),
161/// }
162///
163/// type CardData = String;
164///
165/// /// Door is locked / idle / we are ready to read
166/// #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
167/// pub struct Locked {}
168///
169/// /// Actively reading the card
170/// #[derive(Debug, Clone, Eq, PartialEq, Hash)]
171/// pub struct ReadingCard {
172///     card_data: CardData,
173/// }
174///
175/// /// The door is open, we shouldn't be accepting cards and should be blinking the light
176/// #[derive(Debug, Clone, Eq, PartialEq, Hash)]
177/// pub struct DoorOpen {}
178/// impl DoorOpen {
179///     fn on_door_closed(&self) -> CardReaderTransition<Locked> {
180///         TransitionResult::ok(vec![], Locked {})
181///     }
182/// }
183///
184/// impl Locked {
185///     fn on_card_readable(
186///         &self,
187///         shared_dat: &mut SharedState,
188///         data: CardData,
189///     ) -> CardReaderTransition<ReadingCardOrLocked> {
190///         match &shared_dat.last_id {
191///             // Arbitrarily deny the same person entering twice in a row
192///             Some(d) if d == &data => TransitionResult::ok(vec![], Locked {}.into()),
193///             _ => {
194///                 // Otherwise issue a processing command. This illustrates using the same handler
195///                 // for different destinations
196///                 shared_dat.last_id = Some(data.clone());
197///                 TransitionResult::ok(
198///                     vec![
199///                         Commands::ProcessData(data.clone()),
200///                         Commands::StartBlinkingLight,
201///                     ],
202///                     ReadingCard { card_data: data }.into(),
203///                 )
204///             }
205///         }
206///     }
207/// }
208///
209/// impl ReadingCard {
210///     fn on_card_accepted(&self) -> CardReaderTransition<DoorOpen> {
211///         TransitionResult::ok(vec![Commands::StopBlinkingLight], DoorOpen {})
212///     }
213///     fn on_card_rejected(&self) -> CardReaderTransition<Locked> {
214///         TransitionResult::ok(vec![Commands::StopBlinkingLight], Locked {})
215///     }
216/// }
217///
218/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
219/// let crs = CardReaderState::Locked(Locked {});
220/// let mut cr = CardReader::from_parts(crs, SharedState { last_id: None });
221/// let cmds = cr.on_event(CardReaderEvents::CardReadable("badguy".to_string()))?;
222/// assert_eq!(cmds[0], Commands::ProcessData("badguy".to_string()));
223/// assert_eq!(cmds[1], Commands::StartBlinkingLight);
224///
225/// let cmds = cr.on_event(CardReaderEvents::CardRejected)?;
226/// assert_eq!(cmds[0], Commands::StopBlinkingLight);
227///
228/// let cmds = cr.on_event(CardReaderEvents::CardReadable("goodguy".to_string()))?;
229/// assert_eq!(cmds[0], Commands::ProcessData("goodguy".to_string()));
230/// assert_eq!(cmds[1], Commands::StartBlinkingLight);
231///
232/// let cmds = cr.on_event(CardReaderEvents::CardAccepted)?;
233/// assert_eq!(cmds[0], Commands::StopBlinkingLight);
234/// # Ok(())
235/// # }
236/// ```
237///
238/// In the above example the first word is the name of the state machine, then after the comma the
239/// type (which you must define separately) of commands produced by the machine.
240///
241/// then each line represents a transition, where the first word is the initial state, the tuple
242/// inside the arrow is `(eventtype[, event handler])`, and the word after the arrow is the
243/// destination state. here `eventtype` is an enum variant , and `event_handler` is a function you
244/// must define outside the enum whose form depends on the event variant. the only variant types
245/// allowed are unit and one-item tuple variants. For unit variants, the function takes no
246/// parameters. For the tuple variants, the function takes the variant data as its parameter. In
247/// either case the function is expected to return a `TransitionResult` to the appropriate state.
248///
249/// The first transition can be interpreted as "If the machine is in the locked state, when a
250/// `CardReadable` event is seen, call `on_card_readable` (passing in `CardData`) and transition to
251/// the `ReadingCard` state.
252///
253/// The macro will generate a few things:
254/// * A struct for the overall state machine, named with the provided name. Here:
255///   ```text
256///   struct CardReader {
257///       state: CardReaderState,
258///       shared_state: SharedState,
259///   }
260///   ```
261/// * An enum with a variant for each state, named with the provided name + "State".
262///   ```text
263///   enum CardReaderState {
264///       Locked(Locked),
265///       ReadingCard(ReadingCard),
266///       DoorOpen(DoorOpen),
267///   }
268///   ```
269///
270///   You are expected to define a type for each state, to contain that state's data. If there is
271///   no data, you can simply: `type StateName = ()`
272/// * For any instance of transitions with the same event/handler which transition to different
273///   destination states (dynamic destinations), an enum named like `DestAOrDestBOrDestC` is
274///   generated. This enum must be used as the destination "state" from those handlers.
275/// * An enum with a variant for each event. You are expected to define the type (if any) contained
276///   in the event variant.
277///   ```text
278///   enum CardReaderEvents {
279///     DoorClosed,
280///     CardAccepted,
281///     CardRejected,
282///     CardReadable(CardData),
283///   }
284///   ```
285/// * An implementation of the [StateMachine](trait.StateMachine.html) trait for the generated state
286///   machine enum (in this case, `CardReader`)
287/// * A type alias for a [TransitionResult](enum.TransitionResult.html) with the appropriate generic
288///   parameters set for your machine. It is named as your machine with `Transition` appended. In
289///   this case, `CardReaderTransition`.
290#[proc_macro]
291pub fn fsm(input: TokenStream) -> TokenStream {
292    let def: fsm_impl::StateMachineDefinition =
293        parse_macro_input!(input as fsm_impl::StateMachineDefinition);
294    def.codegen()
295}