cja/jobs/registry.rs
1use crate::app_state::{self};
2
3use super::worker::JobFromDB;
4
5/// A trait for job registries that can dispatch jobs based on their name.
6///
7/// This trait is typically implemented using the `impl_job_registry!` macro,
8/// which generates the necessary dispatch logic for all registered job types.
9#[async_trait::async_trait]
10pub trait JobRegistry<AppState: app_state::AppState> {
11 /// Run a job from the database by dispatching to the appropriate handler.
12 async fn run_job(
13 &self,
14 job: &JobFromDB,
15 app_state: AppState,
16 cancellation_token: tokio_util::sync::CancellationToken,
17 ) -> color_eyre::Result<()>;
18}
19
20/// A macro for implementing a job registry that handles job dispatch.
21///
22/// This macro generates a `Jobs` struct that implements `JobRegistry` for your application state.
23/// It creates a match statement that routes jobs to their appropriate handlers based on the job name.
24///
25/// # Usage
26///
27/// ```rust
28/// use cja::impl_job_registry;
29/// use cja::jobs::Job;
30/// use cja::app_state::AppState;
31/// use cja::server::cookies::CookieKey;
32/// use serde::{Serialize, Deserialize};
33///
34/// // Define your app state
35/// #[derive(Clone)]
36/// struct MyAppState {
37/// db: sqlx::PgPool,
38/// cookie_key: CookieKey,
39/// }
40///
41/// impl AppState for MyAppState {
42/// fn version(&self) -> &str { "1.0.0" }
43/// fn db(&self) -> &sqlx::PgPool { &self.db }
44/// fn cookie_key(&self) -> &CookieKey { &self.cookie_key }
45/// }
46///
47/// // Define your job types
48/// #[derive(Debug, Serialize, Deserialize, Clone)]
49/// struct ProcessPaymentJob {
50/// user_id: i32,
51/// amount_cents: i64,
52/// }
53///
54/// #[derive(Debug, Serialize, Deserialize, Clone)]
55/// struct SendNotificationJob {
56/// user_id: i32,
57/// message: String,
58/// }
59///
60/// // Implement the Job trait for each job type
61/// #[async_trait::async_trait]
62/// impl Job<MyAppState> for ProcessPaymentJob {
63/// const NAME: &'static str = "ProcessPaymentJob";
64/// async fn run(&self, _: MyAppState) -> color_eyre::Result<()> {
65/// println!("Processing payment for user {}", self.user_id);
66/// Ok(())
67/// }
68/// }
69///
70/// #[async_trait::async_trait]
71/// impl Job<MyAppState> for SendNotificationJob {
72/// const NAME: &'static str = "SendNotificationJob";
73/// async fn run(&self, _: MyAppState) -> color_eyre::Result<()> {
74/// println!("Sending notification to user {}: {}", self.user_id, self.message);
75/// Ok(())
76/// }
77/// }
78///
79/// // Register all your job types with the macro
80/// impl_job_registry!(MyAppState, ProcessPaymentJob, SendNotificationJob);
81/// ```
82#[macro_export]
83macro_rules! impl_job_registry {
84 ($state:ty, $($job_type:ty),*) => {
85 pub struct Jobs;
86
87 #[async_trait::async_trait]
88 impl $crate::jobs::registry::JobRegistry<$state> for Jobs {
89 async fn run_job(
90 &self,
91 job: &$crate::jobs::worker::JobFromDB,
92 app_state: $state,
93 cancellation_token: $crate::jobs::CancellationToken,
94 ) -> $crate::Result<()> {
95 use $crate::jobs::Job as _;
96
97 let payload = job.payload.clone();
98
99 match job.name.as_str() {
100 $(
101 <$job_type>::NAME => <$job_type>::run_from_value(payload, app_state, cancellation_token).await,
102 )*
103 _ => Err($crate::color_eyre::eyre::eyre!("Unknown job type: {}", job.name)),
104 }
105 }
106 }
107 };
108}