trailbase_wasm/
job.rs

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  // NOTE: We use anyhow here specifically to allow guests to attach context.
22  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}