sayiir_macros/lib.rs
1//! Procedural macros for the Sayiir durable workflow engine.
2//!
3//! Provides two macros that eliminate boilerplate when defining workflows:
4//!
5//! - **`#[task]`** — Transforms an async function into a `CoreTask` struct with
6//! automatic registration, metadata, and dependency injection.
7//!
8//! - **`workflow!`** — Builds a workflow pipeline with a concise DSL that desugars
9//! to `WorkflowBuilder` method calls.
10//!
11//! # Quick Example
12//!
13//! ```rust,ignore
14//! use sayiir_macros::task;
15//!
16//! #[task(timeout = "30s", retries = 3, backoff = "100ms")]
17//! async fn charge(order: Order, #[inject] stripe: Arc<Stripe>) -> Result<Receipt, BoxError> {
18//! stripe.charge(&order).await
19//! }
20//!
21//! let workflow = workflow!("order-process", JsonCodec, registry,
22//! validate(order: Order) { validate_order(order) }
23//! => charge
24//! => send_email || update_inventory
25//! => finalize
26//! );
27//! ```
28
29mod task;
30mod util;
31mod workflow;
32
33/// Transforms an async function into a `CoreTask` implementation.
34///
35/// # Attributes
36///
37/// - `id = "custom_name"` — override the task ID (default: function name)
38/// - `display_name = "Charge Card"` — human-readable name
39/// - `description = "Charges the customer's card"` — task description
40/// - `timeout = "30s"` — task timeout (supports `ms`, `s`, `m`, `h` suffixes)
41/// - `retries = 3` — maximum retry count
42/// - `backoff = "100ms"` — initial retry delay
43/// - `backoff_multiplier = 2.0` — exponential multiplier (default: 2.0)
44/// - `tags = "io"` — categorization tags (can be repeated)
45///
46/// # Parameters
47///
48/// - Exactly **one** non-`#[inject]` parameter: the task input type
49/// - Zero or more `#[inject]` parameters: dependency-injected fields
50///
51/// # Return Types
52///
53/// - `Result<T, E>` — fallible; `E` is converted via `Into<BoxError>`
54/// - `T` — infallible; automatically wrapped in `Ok(...)`
55///
56/// # Generated Code
57///
58/// - A PascalCase struct (e.g., `fn charge` → `struct Charge`)
59/// - `new()` constructor with positional args for injected dependencies
60/// - `task_id()` and `metadata()` helper methods
61/// - `register()` method for `TaskRegistry` integration
62/// - `CoreTask` trait implementation
63/// - The original function is preserved for direct use/testing
64#[proc_macro_attribute]
65pub fn task(
66 attr: proc_macro::TokenStream,
67 item: proc_macro::TokenStream,
68) -> proc_macro::TokenStream {
69 match task::expand(attr.into(), item.into()) {
70 Ok(tokens) => tokens.into(),
71 Err(e) => e.to_compile_error().into(),
72 }
73}
74
75/// Builds a workflow pipeline with a concise DSL.
76///
77/// # Syntax
78///
79/// ```text
80/// workflow!("workflow-id", CodecType, registry_expr,
81/// step => step => step
82/// )
83/// ```
84///
85/// # Step Types
86///
87/// - `task_name` — reference to a `#[task]`-generated struct
88/// - `name(param: Type) { expr }` — inline task
89/// - `step || step` — parallel fork (branches)
90/// - `delay "5s"` — durable delay (auto-generated ID)
91/// - `delay "wait_24h" "5s"` — durable delay with custom ID
92/// - `=>` — sequential chain (or join after `||`)
93///
94/// # Returns
95///
96/// A `Result<SerializableWorkflow<C, Input, ()>, WorkflowError>` expression.
97#[proc_macro]
98pub fn workflow(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
99 match workflow::expand(input.into()) {
100 Ok(tokens) => tokens.into(),
101 Err(e) => e.to_compile_error().into(),
102 }
103}