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