Skip to main content

ferro_rs/schedule/
task.rs

1//! Scheduled task trait and entry types
2//!
3//! This module defines the `Task` trait for creating struct-based
4//! scheduled tasks, as well as internal types for task management.
5
6use super::expression::CronExpression;
7use crate::error::FrameworkError;
8use async_trait::async_trait;
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::Arc;
12
13/// Type alias for boxed task handlers
14pub type BoxedTask = Arc<dyn TaskHandler + Send + Sync>;
15
16/// Type alias for async task result
17pub type TaskResult = Result<(), FrameworkError>;
18
19/// Type alias for boxed future result
20pub type BoxedFuture<'a> = Pin<Box<dyn Future<Output = TaskResult> + Send + 'a>>;
21
22/// Internal trait for task execution
23///
24/// This trait is implemented automatically for `Task` and closure-based tasks.
25#[async_trait]
26pub trait TaskHandler: Send + Sync {
27    /// Execute the task
28    async fn handle(&self) -> TaskResult;
29}
30
31/// Trait for defining scheduled tasks
32///
33/// Implement this trait on a struct to create a reusable scheduled task.
34/// Schedule configuration is done via the fluent builder API when registering.
35///
36/// # Example
37///
38/// ```rust,ignore
39/// use ferro_rs::{Task, TaskResult};
40/// use async_trait::async_trait;
41///
42/// pub struct CleanupLogsTask;
43///
44/// impl CleanupLogsTask {
45///     pub fn new() -> Self {
46///         Self
47///     }
48/// }
49///
50/// #[async_trait]
51/// impl Task for CleanupLogsTask {
52///     async fn handle(&self) -> TaskResult {
53///         // Cleanup logic here
54///         println!("Cleaning up old log files...");
55///         Ok(())
56///     }
57/// }
58///
59/// // Register in schedule.rs with fluent API:
60/// // schedule.add(
61/// //     schedule.task(CleanupLogsTask::new())
62/// //         .daily()
63/// //         .at("03:00")
64/// //         .name("cleanup:logs")
65/// // );
66/// ```
67#[async_trait]
68pub trait Task: Send + Sync {
69    /// Execute the task
70    async fn handle(&self) -> TaskResult;
71}
72
73// Implement TaskHandler for any type implementing Task
74#[async_trait]
75impl<T: Task> TaskHandler for T {
76    async fn handle(&self) -> TaskResult {
77        Task::handle(self).await
78    }
79}
80
81/// A registered task entry in the schedule
82///
83/// This struct holds all the information about a scheduled task,
84/// including its schedule expression, configuration, and the task itself.
85pub struct TaskEntry {
86    /// Unique name for the task
87    pub name: String,
88    /// Cron expression defining when the task runs
89    pub expression: CronExpression,
90    /// The task handler
91    pub task: BoxedTask,
92    /// Optional description
93    pub description: Option<String>,
94    /// Prevent overlapping runs
95    pub without_overlapping: bool,
96    /// Run in background (non-blocking)
97    pub run_in_background: bool,
98}
99
100impl TaskEntry {
101    /// Check if this task is due to run now
102    pub fn is_due(&self) -> bool {
103        self.expression.is_due()
104    }
105
106    /// Run the task
107    pub async fn run(&self) -> TaskResult {
108        self.task.handle().await
109    }
110
111    /// Get a human-readable description of the schedule
112    pub fn schedule_description(&self) -> &str {
113        self.expression.expression()
114    }
115}
116
117/// Wrapper for closure-based tasks
118pub(crate) struct ClosureTask<F>
119where
120    F: Fn() -> BoxedFuture<'static> + Send + Sync,
121{
122    pub(crate) handler: F,
123}
124
125#[async_trait]
126impl<F> TaskHandler for ClosureTask<F>
127where
128    F: Fn() -> BoxedFuture<'static> + Send + Sync,
129{
130    async fn handle(&self) -> TaskResult {
131        (self.handler)().await
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    struct TestTask;
140
141    #[async_trait]
142    impl Task for TestTask {
143        async fn handle(&self) -> TaskResult {
144            Ok(())
145        }
146    }
147
148    #[tokio::test]
149    async fn test_task_trait() {
150        let task = TestTask;
151
152        let result: TaskResult = Task::handle(&task).await;
153        assert!(result.is_ok());
154    }
155
156    #[tokio::test]
157    async fn test_task_entry() {
158        let task = TestTask;
159        let entry = TaskEntry {
160            name: "test-task".to_string(),
161            expression: CronExpression::every_minute(),
162            task: Arc::new(task),
163            description: Some("A test task".to_string()),
164            without_overlapping: false,
165            run_in_background: false,
166        };
167
168        assert_eq!(entry.name, "test-task");
169        assert_eq!(entry.schedule_description(), "* * * * *");
170
171        let result = entry.run().await;
172        assert!(result.is_ok());
173    }
174}