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}