use crate::{
Site,
callables::{self, CallError, Callable, HasSite, IntoPayloadData, PayloadData},
};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::{any::TypeId, collections::HashMap, sync::Arc, time::Duration};
#[derive(Clone)]
pub struct SignalHandler {
type_id: TypeId,
func: Callable<SignalContext, SignalError>,
}
impl SignalHandler {
async fn call(&self, ctx: SignalContext) -> Result<(), SignalError> {
self.func.call(ctx).await?;
Ok(())
}
}
#[derive(Clone)]
pub struct SignalContext {
site: Site,
payload: PayloadData,
}
impl HasSite for SignalContext {
fn site(&self) -> &Site {
&self.site
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct SignalConf {}
pub(crate) struct Signaller {
pub(crate) handler: SignalHandler,
pub(crate) options: SignalConf,
}
impl Signaller {
pub(crate) fn operation(&self) -> crate::callables::Operation {
let spec = self.handler.func.inspect();
crate::callables::Operation::from_specs(crate::callables::OperationKind::Signal, spec)
.with_conf(&self.options)
}
}
pub(crate) fn signal<T, H, Args>(handler: H, options: SignalConf) -> Signaller
where
T: callables::Payloadable,
H: callables::Specable<Args, Output = ()> + Send + Sync + 'static,
Args: callables::FromContext<SignalContext>
+ callables::IntoArgSpecs
+ callables::HasPayload<T>
+ Send
+ 'static,
{
let callable = Callable::new(handler);
Signaller {
handler: SignalHandler {
func: callable,
type_id: TypeId::of::<T>(),
},
options,
}
}
impl IntoPayloadData for SignalContext {
fn into_payload_data(self) -> PayloadData {
self.payload
}
}
#[derive(Debug, thiserror::Error)]
pub enum SignalError {
#[error("Signal payload type mismatch")]
PayloadTypeMismatch,
#[error("Signal not found")]
NotFound,
#[error(transparent)]
CallError(#[from] CallError),
}
pub struct SignalRegistry {
handlers: HashMap<TypeId, Vec<SignalHandler>>,
}
impl fmt::Debug for SignalRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SignalRegistry")
.field("handlers", &self.handlers.keys().collect::<Vec<_>>())
.finish()
}
}
impl SignalRegistry {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
pub(crate) fn register(&mut self, signaller: Signaller) {
let type_id = signaller.handler.type_id;
let entry = self.handlers.entry(type_id).or_default();
entry.push(signaller.handler);
}
pub(crate) fn merge(&mut self, other: SignalRegistry) {
for (name, other_container) in other.handlers.into_iter() {
let entry = self.handlers.entry(name).or_default();
entry.extend(other_container);
}
}
pub fn engine(&self) -> SignalEngine {
let registry = Self {
handlers: self.handlers.clone(),
};
SignalEngine::new(registry)
}
}
#[derive(Clone)]
pub struct SignalClient {
site: Site,
engine: SignalEngine,
}
impl SignalClient {
pub(crate) fn new(site: Site, engine: SignalEngine) -> Self {
Self { site, engine }
}
pub fn submit<T>(&self, item: T) -> Result<(), SignalError>
where
T: callables::Payloadable,
{
let payload = PayloadData::new(item);
self.submit_payload(payload)
}
pub fn schedule<T>(&self, item: T, delay: Duration) -> Result<(), SignalError>
where
T: callables::Payloadable,
{
let payload = PayloadData::new(item);
self.ensure_payload_handler(&payload)?;
let site = self.site.clone();
let engine = self.engine.clone();
let shutdown = self.site.shutdown_notifier();
self.site.spawn(async move {
tokio::select! {
_ = tokio::time::sleep(delay) => {
engine.dispatch_payload_fire_and_forget(site, payload).await;
}
_ = shutdown.notified() => {}
}
});
Ok(())
}
fn submit_payload(&self, payload: PayloadData) -> Result<(), SignalError> {
self.ensure_payload_handler(&payload)?;
let site = self.site.clone();
let engine = self.engine.clone();
self.site.spawn(async move {
engine.dispatch_payload_fire_and_forget(site, payload).await;
});
Ok(())
}
fn ensure_payload_handler(&self, payload: &PayloadData) -> Result<(), SignalError> {
if self.engine.has_handler(payload.payload_type_id()) {
Ok(())
} else {
Err(SignalError::NotFound)
}
}
}
#[derive(Clone)]
pub struct SignalEngine {
registry: Arc<SignalRegistry>,
}
impl SignalEngine {
pub fn new(registry: SignalRegistry) -> Self {
Self {
registry: Arc::new(registry),
}
}
pub(crate) fn has_handler(&self, type_id: TypeId) -> bool {
self.registry.handlers.contains_key(&type_id)
}
pub(crate) async fn dispatch_payload_fire_and_forget(&self, site: Site, payload: PayloadData) {
if let Err(err) = self.dispatch_payload(site, payload).await {
tracing::error!("Error dispatching signal: {}", err);
}
}
pub(crate) async fn dispatch_payload(
&self,
site: Site,
payload: PayloadData,
) -> Result<(), SignalError> {
let type_id = payload.payload_type_id();
let handlers = match self.registry.handlers.get(&type_id) {
Some(handlers) => handlers,
None => return Err(SignalError::NotFound),
};
for handler in handlers.iter() {
let ctx = SignalContext {
site: site.clone(),
payload: payload.clone(),
};
if let Err(err) = handler.call(ctx).await {
tracing::error!("Error executing signal handler: {}", err);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::callables::Payload;
use schemars::JsonSchema;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
struct TestSignal {
value: usize,
}
async fn record_signal(_payload: Payload<TestSignal>) {}
#[test]
fn direct_registration_records_signal_operation() {
let signaller = signal::<TestSignal, _, _>(record_signal, SignalConf::default());
let op = signaller.operation();
assert_eq!(op.kind, crate::callables::OperationKind::Signal);
assert!(!op.hidden);
assert_eq!(op.path, "");
}
#[test]
fn registry_engine_detects_registered_payload_type() {
let signaller = signal::<TestSignal, _, _>(record_signal, SignalConf::default());
let mut registry = SignalRegistry::new();
registry.register(signaller);
let engine = registry.engine();
assert!(engine.has_handler(TypeId::of::<TestSignal>()));
assert!(!engine.has_handler(TypeId::of::<String>()));
}
#[test]
fn merge_appends_handlers_for_same_payload_type() {
async fn first(_payload: Payload<TestSignal>) {}
async fn second(_payload: Payload<TestSignal>) {}
let mut left = SignalRegistry::new();
left.register(signal::<TestSignal, _, _>(first, SignalConf::default()));
let mut right = SignalRegistry::new();
right.register(signal::<TestSignal, _, _>(second, SignalConf::default()));
left.merge(right);
assert_eq!(
left.handlers.get(&TypeId::of::<TestSignal>()).map(Vec::len),
Some(2)
);
}
}