Skip to main content

aster_forge_tasks/
registry.rs

1//! Product-facing task registry support traits.
2
3/// Minimal read-only view over a persisted task row.
4///
5/// Product crates implement this for their database model so Forge can decode typed payloads and
6/// results without depending on a concrete ORM entity or schema extension columns.
7pub trait TaskRecord<Kind> {
8    /// Stable database identifier used in diagnostics.
9    fn id(&self) -> i64;
10
11    /// Product-owned task kind enum.
12    fn kind(&self) -> Kind;
13
14    /// Stored JSON payload.
15    fn payload_json(&self) -> &str;
16
17    /// Stored JSON result, if the task has completed with one.
18    fn result_json(&self) -> Option<&str>;
19}
20
21/// Generates a product-local static task registry.
22///
23/// The macro intentionally requires an explicit `kind => static_adapter` mapping. That keeps task
24/// registration auditable in product crates while removing the repetitive match forwarding that
25/// otherwise drifts between `spec_for_kind`, `task_lane`, and `task_lane_kinds`.
26#[macro_export]
27macro_rules! task_registry {
28    (
29        $(#[$meta:meta])*
30        $vis:vis mod $module:ident {
31            state: $state:ty;
32            task: $task:ty;
33            config: $config:ty;
34            context: $context:ty;
35            error: $error:ty;
36            kind: $kind:ty;
37            lane: $lane:ty;
38            payload: $payload:ty;
39            result: $result:ty;
40            specs {
41                $(
42                    $adapter:ident: $spec:ty => $kind_value:path
43                ),+ $(,)?
44            }
45            lanes {
46                $(
47                    $lane_value:path => [$($lane_kind:path),* $(,)?]
48                ),+ $(,)?
49            }
50        }
51    ) => {
52        $(#[$meta])*
53        $vis mod $module {
54            $(
55                static $adapter: $crate::TaskSpecAdapter<$spec> =
56                    $crate::TaskSpecAdapter::new();
57            )+
58
59            /// Returns the registered task spec for a product task kind.
60            pub fn spec_for_kind(
61                kind: $kind,
62            ) -> &'static dyn $crate::ErasedBackgroundTaskSpec<
63                $state,
64                $task,
65                $config,
66                $context,
67                $kind,
68                $lane,
69                $payload,
70                $result,
71                $error,
72            > {
73                match kind {
74                    $(
75                        $kind_value => &$adapter,
76                    )+
77                }
78            }
79
80            /// Returns the lane for a product task kind.
81            pub fn task_lane(kind: $kind) -> $lane {
82                spec_for_kind(kind).lane()
83            }
84
85            /// Returns all task kinds configured for a lane.
86            pub fn task_lane_kinds(lane: $lane) -> &'static [$kind] {
87                match lane {
88                    $(
89                        $lane_value => &[$($lane_kind),*],
90                    )+
91                }
92            }
93
94            #[cfg(test)]
95            mod registry_tests {
96                use super::*;
97
98                #[test]
99                fn registered_task_lanes_are_bidirectionally_consistent() {
100                    $(
101                        let lane = task_lane($kind_value);
102                        assert!(
103                            task_lane_kinds(lane).contains(&$kind_value),
104                            "lane {lane:?} does not list task kind {:?}",
105                            $kind_value
106                        );
107                    )+
108                }
109            }
110        }
111    };
112}