1use futures_util::future::LocalBoxFuture;
2use wstd::http::server::{Finished, Responder};
3
4use crate::http::IntoResponse;
5
6#[derive(Debug, thiserror::Error)]
7pub enum Error {
8 #[error("InvalidSpec")]
9 InvalidSpec,
10}
11
12pub type JobHandler = Box<dyn Fn(Responder) -> LocalBoxFuture<'static, Finished>>;
13
14pub struct Job {
15 pub name: String,
16 pub spec: String,
17 pub handler: JobHandler,
18}
19
20impl Job {
21 pub fn new<F, R, B>(
22 name: impl std::string::ToString,
23 spec: impl std::string::ToString,
24 f: F,
25 ) -> Result<Self, Error>
26 where
27 F: (AsyncFn() -> R) + Send + Sync + 'static,
28 R: IntoResponse<B>,
29 B: wstd::http::body::Body,
30 {
31 let spec = spec.to_string();
32 validate_spec(&spec)?;
33
34 let f = std::rc::Rc::new(f);
35
36 return Ok(Self {
37 name: name.to_string(),
38 spec,
39 handler: Box::new(move |responder| {
40 let f = f.clone();
41 Box::pin(async move { responder.respond(f().await.into_response()).await })
42 }),
43 });
44 }
45
46 pub fn minutely<F, R, B>(name: impl std::string::ToString, f: F) -> Self
47 where
48 F: (AsyncFn() -> R) + Send + Sync + 'static,
49 R: IntoResponse<B>,
50 B: wstd::http::body::Body,
51 {
52 return Self::new(name, "37 * * * * *", f).expect("valid spec");
53 }
54
55 pub fn hourly<F, R, B>(name: impl std::string::ToString, f: F) -> Self
56 where
57 F: (AsyncFn() -> R) + Send + Sync + 'static,
58 R: IntoResponse<B>,
59 B: wstd::http::body::Body,
60 {
61 return Self::new(name, "@hourly", f).expect("valid spec");
62 }
63
64 pub fn daily<F, R, B>(name: impl std::string::ToString, f: F) -> Self
65 where
66 F: (AsyncFn() -> R) + Send + Sync + 'static,
67 R: IntoResponse<B>,
68 B: wstd::http::body::Body,
69 {
70 return Self::new(name, "@daily", f).expect("valid spec");
71 }
72}
73
74fn validate_spec(spec: &str) -> Result<(), Error> {
75 return match spec {
76 "@hourly" | "@daily" | "@weekly" | "@monthly" | "@yearly" => Ok(()),
77 spec => match spec.trim().split(" ").count() {
78 6 | 7 => Ok(()),
79 _ => Err(Error::InvalidSpec),
80 },
81 };
82}