use std::collections::HashMap;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
use serde::de::DeserializeOwned;
use uuid::Uuid;
use super::config::JobModel;
use super::error::{JobError, RegisterError};
use super::validate::{validate_kind, validate_version};
type BoxFut<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Debug)]
pub(crate) enum HandlerError {
Malformed,
Job(JobError),
}
pub(crate) trait ErasedHandler: Send + Sync {
fn handle<'a>(
&'a self,
payload: &'a serde_json::Value,
job_id: Uuid,
) -> BoxFut<'a, Result<(), HandlerError>>;
}
struct TypedHandler<J> {
handler: Arc<dyn Fn(J) -> BoxFut<'static, Result<(), JobError>> + Send + Sync>,
_job: PhantomData<J>,
}
impl<J> ErasedHandler for TypedHandler<J>
where
J: DeserializeOwned + Send + Sync + 'static,
{
fn handle<'a>(
&'a self,
payload: &'a serde_json::Value,
job_id: Uuid,
) -> BoxFut<'a, Result<(), HandlerError>> {
Box::pin(async move {
let job: J =
serde_json::from_value(payload.clone()).map_err(|_| HandlerError::Malformed)?;
let result = (self.handler)(job).await;
let _ = job_id;
result.map_err(HandlerError::Job)
})
}
}
#[derive(Clone)]
pub struct Registry {
handlers: HashMap<(String, i16), Arc<dyn ErasedHandler + Send + Sync>>,
}
impl std::fmt::Debug for Registry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Registry")
.field("handler_count", &self.handlers.len())
.finish_non_exhaustive()
}
}
impl Default for Registry {
fn default() -> Self {
Self::new()
}
}
impl Registry {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
pub fn add<J, F, Fut>(
&mut self,
model: &JobModel<J>,
handler: F,
) -> Result<&mut Self, RegisterError>
where
J: DeserializeOwned + Send + Sync + 'static,
F: Fn(J) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), JobError>> + Send + 'static,
{
validate_kind(model.kind()).map_err(RegisterError::invalid_kind)?;
validate_version(model.version())
.map_err(|_| RegisterError::invalid_version(model.version()))?;
let key = (model.kind().to_string(), model.version());
if self.handlers.contains_key(&key) {
return Err(RegisterError::already_registered(
model.kind(),
model.version(),
));
}
let boxed: Arc<dyn Fn(J) -> BoxFut<'static, Result<(), JobError>> + Send + Sync> =
Arc::new(move |job| Box::pin(handler(job)));
let erased: Arc<dyn ErasedHandler + Send + Sync> = Arc::new(TypedHandler {
handler: boxed,
_job: PhantomData::<J>,
});
self.handlers.insert(key, erased);
Ok(self)
}
pub(crate) fn get(
&self,
kind: &str,
version: i16,
) -> Option<Arc<dyn ErasedHandler + Send + Sync>> {
self.handlers.get(&(kind.to_string(), version)).cloned()
}
pub fn is_empty(&self) -> bool {
self.handlers.is_empty()
}
pub fn len(&self) -> usize {
self.handlers.len()
}
pub fn handles(&self, kind: &str, version: i16) -> bool {
self.handlers.contains_key(&(kind.to_string(), version))
}
}