pub mod builder;
pub mod expression;
pub mod task;
pub use builder::TaskBuilder;
pub use expression::{CronExpression, DayOfWeek};
pub use task::{BoxedFuture, BoxedTask, Task, TaskEntry, TaskHandler, TaskResult};
use crate::error::FrameworkError;
pub struct Schedule {
tasks: Vec<TaskEntry>,
}
impl Schedule {
pub fn new() -> Self {
Self { tasks: Vec::new() }
}
pub fn task<T: Task + 'static>(&self, task: T) -> TaskBuilder {
TaskBuilder::from_task(task)
}
pub fn call<F, Fut>(&mut self, f: F) -> TaskBuilder
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<(), FrameworkError>> + Send + 'static,
{
TaskBuilder::from_async(f)
}
pub fn add(&mut self, builder: TaskBuilder) -> &mut Self {
let task_index = self.tasks.len();
self.tasks.push(builder.build(task_index));
self
}
pub fn tasks(&self) -> &[TaskEntry] {
&self.tasks
}
pub fn len(&self) -> usize {
self.tasks.len()
}
pub fn is_empty(&self) -> bool {
self.tasks.is_empty()
}
pub fn due_tasks(&self) -> Vec<&TaskEntry> {
self.tasks.iter().filter(|t| t.is_due()).collect()
}
pub async fn run_due_tasks(&self) -> Vec<(&str, Result<(), FrameworkError>)> {
let due = self.due_tasks();
let mut results = Vec::new();
for task in due {
let result = task.run().await;
results.push((task.name.as_str(), result));
}
results
}
pub async fn run_all_tasks(&self) -> Vec<(&str, Result<(), FrameworkError>)> {
let mut results = Vec::new();
for task in &self.tasks {
let result = task.run().await;
results.push((task.name.as_str(), result));
}
results
}
pub fn find(&self, name: &str) -> Option<&TaskEntry> {
self.tasks.iter().find(|t| t.name == name)
}
pub async fn run_task(&self, name: &str) -> Option<Result<(), FrameworkError>> {
if let Some(task) = self.find(name) {
Some(task.run().await)
} else {
None
}
}
}
impl Default for Schedule {
fn default() -> Self {
Self::new()
}
}
#[macro_export]
macro_rules! schedule_task {
($f:expr) => {
$crate::schedule::TaskBuilder::from_async($f)
};
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
struct TestTask;
#[async_trait]
impl Task for TestTask {
async fn handle(&self) -> Result<(), FrameworkError> {
Ok(())
}
}
#[test]
fn test_schedule_new() {
let schedule = Schedule::new();
assert!(schedule.is_empty());
assert_eq!(schedule.len(), 0);
}
#[test]
fn test_schedule_add_trait_task() {
let mut schedule = Schedule::new();
schedule.add(schedule.task(TestTask).every_minute().name("test-1"));
schedule.add(schedule.task(TestTask).every_minute().name("test-2"));
assert_eq!(schedule.len(), 2);
assert!(!schedule.is_empty());
}
#[test]
fn test_schedule_add_closure_task() {
let mut schedule = Schedule::new();
let builder = schedule
.call(|| async { Ok(()) })
.daily()
.name("closure-task");
schedule.add(builder);
assert_eq!(schedule.len(), 1);
}
#[test]
fn test_schedule_find_task() {
let mut schedule = Schedule::new();
schedule.add(schedule.task(TestTask).every_minute().name("find-me"));
let found = schedule.find("find-me");
assert!(found.is_some());
assert_eq!(found.unwrap().name, "find-me");
let not_found = schedule.find("not-exists");
assert!(not_found.is_none());
}
#[tokio::test]
async fn test_schedule_run_task() {
let mut schedule = Schedule::new();
schedule.add(schedule.task(TestTask).every_minute().name("run-me"));
let result = schedule.run_task("run-me").await;
assert!(result.is_some());
assert!(result.unwrap().is_ok());
let not_found = schedule.run_task("not-exists").await;
assert!(not_found.is_none());
}
#[tokio::test]
async fn test_schedule_run_all_tasks() {
let mut schedule = Schedule::new();
schedule.add(schedule.task(TestTask).every_minute().name("task-1"));
schedule.add(schedule.task(TestTask).every_minute().name("task-2"));
let results = schedule.run_all_tasks().await;
assert_eq!(results.len(), 2);
for (name, result) in results {
assert!(result.is_ok(), "Task {name} failed");
}
}
}