use std::sync::Arc;
use entelix_core::{Error, ExecutionContext, Result};
use crate::runnable::Runnable;
type Predicate<I> = Arc<dyn Fn(&I) -> bool + Send + Sync>;
type Branch<I, O> = (Predicate<I>, Arc<dyn Runnable<I, O>>);
pub struct RunnableRouter<I, O>
where
I: Send + 'static,
O: Send + 'static,
{
routes: Vec<Branch<I, O>>,
fallback: Option<Arc<dyn Runnable<I, O>>>,
}
impl<I, O> RunnableRouter<I, O>
where
I: Send + 'static,
O: Send + 'static,
{
pub fn new() -> Self {
Self {
routes: Vec::new(),
fallback: None,
}
}
#[must_use]
pub fn route<F, R>(mut self, predicate: F, runnable: R) -> Self
where
F: Fn(&I) -> bool + Send + Sync + 'static,
R: Runnable<I, O> + 'static,
{
self.routes.push((Arc::new(predicate), Arc::new(runnable)));
self
}
#[must_use]
pub fn fallback<R>(mut self, runnable: R) -> Self
where
R: Runnable<I, O> + 'static,
{
self.fallback = Some(Arc::new(runnable));
self
}
pub fn len(&self) -> usize {
self.routes.len()
}
pub fn is_empty(&self) -> bool {
self.routes.is_empty()
}
}
impl<I, O> Default for RunnableRouter<I, O>
where
I: Send + 'static,
O: Send + 'static,
{
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl<I, O> Runnable<I, O> for RunnableRouter<I, O>
where
I: Send + 'static,
O: Send + 'static,
{
async fn invoke(&self, input: I, ctx: &ExecutionContext) -> Result<O> {
for (predicate, runnable) in &self.routes {
if predicate(&input) {
return runnable.invoke(input, ctx).await;
}
}
if let Some(fallback) = &self.fallback {
return fallback.invoke(input, ctx).await;
}
Err(Error::invalid_request(
"RunnableRouter: no route matched and no fallback was set",
))
}
}