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}