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>(
23 name: impl std::string::ToString,
24 spec: impl std::string::ToString,
25 f: F,
26 ) -> Result<Self, Error>
27 where
28 F: (AsyncFn() -> R) + Send + Sync + 'static,
29 R: IntoResponse<B>,
30 B: wstd::http::body::Body,
31 {
32 let spec = spec.to_string();
33 validate_spec(&spec)?;
34
35 let f = std::rc::Rc::new(f);
36
37 return Ok(Self {
38 name: name.to_string(),
39 spec,
40 handler: Box::new(move |responder| {
41 let f = f.clone();
42 Box::pin(async move { responder.respond(f().await.into_response()).await })
43 }),
44 });
45 }
46
47 pub fn minutely<F, R, B>(name: impl std::string::ToString, f: F) -> Self
48 where
49 F: (AsyncFn() -> R) + Send + Sync + 'static,
50 R: IntoResponse<B>,
51 B: wstd::http::body::Body,
52 {
53 return Self::new(name, "37 * * * * *", f).expect("valid spec");
54 }
55
56 pub fn hourly<F, R, B>(name: impl std::string::ToString, f: F) -> Self
57 where
58 F: (AsyncFn() -> R) + Send + Sync + 'static,
59 R: IntoResponse<B>,
60 B: wstd::http::body::Body,
61 {
62 return Self::new(name, "@hourly", f).expect("valid spec");
63 }
64
65 pub fn daily<F, R, B>(name: impl std::string::ToString, f: F) -> Self
66 where
67 F: (AsyncFn() -> R) + Send + Sync + 'static,
68 R: IntoResponse<B>,
69 B: wstd::http::body::Body,
70 {
71 return Self::new(name, "@daily", f).expect("valid spec");
72 }
73}
74
75fn validate_spec(spec: &str) -> Result<(), Error> {
76 return match spec {
77 "@hourly" | "@daily" | "@weekly" | "@monthly" | "@yearly" => Ok(()),
78 spec => match spec.trim().split(" ").count() {
79 6 | 7 => Ok(()),
80 _ => Err(Error::InvalidSpec),
81 },
82 };
83}