cloacina_macros/lib.rs
1/*
2 * Copyright 2025-2026 Colliery Software
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#![allow(unexpected_cfgs)]
18//! # Cloacina Macros
19//!
20//! Procedural macros for defining tasks and workflows in the Cloacina framework.
21//!
22//! ## Key Features
23//!
24//! - `#[task]` — define tasks with retry policies and trigger rules
25//! - `#[workflow]` — define workflows as modules containing `#[task]` functions
26//! - Compile-time validation of task dependencies and workflow structure
27//! - Automatic task and workflow registration
28//! - Code fingerprinting for task versioning
29//!
30//! ## Example
31//!
32//! ```rust,ignore
33//! use cloacina::{task, workflow, Context, TaskError};
34//!
35//! #[workflow(name = "my_pipeline", description = "Process data")]
36//! pub mod my_pipeline {
37//! use super::*;
38//!
39//! #[task(id = "fetch", dependencies = [])]
40//! pub async fn fetch(ctx: &mut Context<Value>) -> Result<(), TaskError> { Ok(()) }
41//!
42//! #[task(id = "process", dependencies = ["fetch"])]
43//! pub async fn process(ctx: &mut Context<Value>) -> Result<(), TaskError> { Ok(()) }
44//! }
45//! ```
46
47pub(crate) mod computation_graph;
48pub(crate) mod packaged_workflow;
49mod reactor_attr;
50mod registry;
51pub(crate) mod tasks;
52mod trigger_attr;
53mod workflow_attr;
54
55use proc_macro::TokenStream;
56
57/// Define a task with retry policies and trigger rules.
58#[proc_macro_attribute]
59pub fn task(args: TokenStream, input: TokenStream) -> TokenStream {
60 tasks::task(args, input)
61}
62
63/// Define a workflow as a module containing `#[task]` functions.
64///
65/// Applied to a `pub mod` containing `#[task]` functions. Auto-discovers tasks,
66/// validates dependencies, and generates registration code based on delivery mode:
67///
68/// - **Embedded** (default): `inventory::submit!` auto-registration consumed by
69/// `cloacina::Runtime::seed_from_inventory`
70/// - **Packaged** (`features = ["packaged"]`): FFI exports for `.cloacina` packages
71///
72/// # Example
73///
74/// ```rust,ignore
75/// #[workflow(name = "my_pipeline", description = "Process data")]
76/// pub mod my_pipeline {
77/// use super::*;
78///
79/// #[task(id = "fetch", dependencies = [])]
80/// pub async fn fetch(ctx: &mut Context<Value>) -> Result<(), TaskError> { Ok(()) }
81///
82/// #[task(id = "process", dependencies = ["fetch"])]
83/// pub async fn process(ctx: &mut Context<Value>) -> Result<(), TaskError> { Ok(()) }
84/// }
85/// ```
86#[proc_macro_attribute]
87pub fn workflow(args: TokenStream, input: TokenStream) -> TokenStream {
88 workflow_attr::workflow_attr(args, input)
89}
90
91/// Define a trigger that fires a workflow on a schedule or condition.
92///
93/// # Custom poll trigger
94///
95/// ```rust,ignore
96/// #[trigger(on = "my_workflow", poll_interval = "5s")]
97/// pub async fn check_inbox() -> Result<TriggerResult, TriggerError> {
98/// // check condition, return Fire(ctx) or Skip
99/// }
100/// ```
101///
102/// # Cron trigger (T-0305)
103///
104/// ```rust,ignore
105/// #[trigger(on = "my_workflow", cron = "0 2 * * *", timezone = "UTC")]
106/// ```
107#[proc_macro_attribute]
108pub fn trigger(args: TokenStream, input: TokenStream) -> TokenStream {
109 trigger_attr::trigger_attr(args, input)
110}
111
112/// Define a computation graph as a module containing async node functions.
113///
114/// The topology is declared in the macro attribute. Nodes are pure async functions
115/// within the module. The macro compiles the topology into a single async function
116/// with nested match arms for enum routing.
117///
118/// # Example
119///
120/// ```rust,ignore
121/// #[computation_graph(
122/// react = when_any(alpha, beta),
123/// graph = {
124/// decision(alpha, beta) => {
125/// Signal -> output_handler,
126/// NoAction -> audit_logger,
127/// },
128/// }
129/// )]
130/// mod my_strategy {
131/// async fn decision(alpha: Option<&AlphaData>, beta: Option<&BetaData>) -> DecisionOutcome { ... }
132/// async fn output_handler(signal: &Signal) -> OutputConfirmation { ... }
133/// async fn audit_logger(reason: &NoActionReason) -> AuditRecord { ... }
134/// }
135/// ```
136#[proc_macro_attribute]
137pub fn computation_graph(args: TokenStream, input: TokenStream) -> TokenStream {
138 computation_graph::computation_graph_attr(args, input)
139}
140
141/// Declare a reactor as a unit struct.
142///
143/// The reactor bundles accumulators + firing criteria and publishes an
144/// `InputCache` whenever its criteria are met. Graphs declared with
145/// `#[computation_graph(trigger = reactor(ReactorType), ...)]` bind to it
146/// by type path.
147///
148/// ```rust,ignore
149/// #[reactor(
150/// name = "risk_signals",
151/// accumulators = [alpha, beta],
152/// criteria = when_any(alpha, beta),
153/// )]
154/// pub struct RiskSignals;
155/// ```
156#[proc_macro_attribute]
157pub fn reactor(args: TokenStream, input: TokenStream) -> TokenStream {
158 reactor_attr::reactor_attr(args, input)
159}
160
161/// Define a passthrough accumulator (socket-only, no event loop).
162///
163/// ```rust,ignore
164/// #[passthrough_accumulator]
165/// fn beta(event: PricingUpdate) -> BetaData {
166/// BetaData { estimate: event.mid_price }
167/// }
168/// ```
169#[proc_macro_attribute]
170pub fn passthrough_accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
171 match computation_graph::accumulator_macros::passthrough_accumulator_impl(
172 args.into(),
173 input.into(),
174 ) {
175 Ok(output) => output.into(),
176 Err(err) => err.to_compile_error().into(),
177 }
178}
179
180/// Define a stream-backed accumulator.
181///
182/// ```rust,ignore
183/// #[stream_accumulator(type = "kafka", topic = "market.orderbook")]
184/// fn alpha(event: OrderBookUpdate) -> AlphaData {
185/// AlphaData { top_high: event.best_ask, top_low: event.best_bid }
186/// }
187/// ```
188#[proc_macro_attribute]
189pub fn stream_accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
190 match computation_graph::accumulator_macros::stream_accumulator_impl(args.into(), input.into())
191 {
192 Ok(output) => output.into(),
193 Err(err) => err.to_compile_error().into(),
194 }
195}
196
197/// Define a batch accumulator (buffers events, flushes on timer or size threshold).
198///
199/// ```rust,ignore
200/// #[batch_accumulator(flush_interval = "1s", max_buffer_size = 100)]
201/// fn aggregate_fills(events: Vec<FillEvent>) -> Option<AggregatedFills> {
202/// if events.is_empty() { return None; }
203/// Some(AggregatedFills { total: events.len(), volume: events.iter().map(|e| e.qty).sum() })
204/// }
205/// ```
206#[proc_macro_attribute]
207pub fn batch_accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
208 match computation_graph::accumulator_macros::batch_accumulator_impl(args.into(), input.into()) {
209 Ok(output) => output.into(),
210 Err(err) => err.to_compile_error().into(),
211 }
212}
213
214/// Define a polling accumulator (timer-based, queries pull-based sources).
215///
216/// ```rust,ignore
217/// #[polling_accumulator(interval = "5s")]
218/// async fn config_source() -> Option<ConfigData> {
219/// let data = fetch_config().await;
220/// if data.changed() { Some(data) } else { None }
221/// }
222/// ```
223#[proc_macro_attribute]
224pub fn polling_accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
225 match computation_graph::accumulator_macros::polling_accumulator_impl(args.into(), input.into())
226 {
227 Ok(output) => output.into(),
228 Err(err) => err.to_compile_error().into(),
229 }
230}
231
232/// Define a state accumulator (bounded history buffer with DAL persistence).
233///
234/// ```rust,ignore
235/// #[state_accumulator(capacity = 10)]
236/// fn previous_outputs() -> VecDeque<DecisionOutput>;
237/// ```
238#[proc_macro_attribute]
239pub fn state_accumulator(args: TokenStream, input: TokenStream) -> TokenStream {
240 match computation_graph::accumulator_macros::state_accumulator_impl(args.into(), input.into()) {
241 Ok(output) => output.into(),
242 Err(err) => err.to_compile_error().into(),
243 }
244}