use crate::error::CanoError;
use crate::resource::Resources;
use crate::task::{TaskConfig, TaskResult};
use std::borrow::Cow;
use std::fmt;
use std::hash::Hash;
#[crate::task::router]
pub trait RouterTask<TState, TResourceKey = Cow<'static, str>>: Send + Sync
where
TState: Clone + fmt::Debug + Send + Sync + 'static,
TResourceKey: Hash + Eq + Send + Sync + 'static,
{
fn config(&self) -> TaskConfig {
TaskConfig::default()
}
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed(std::any::type_name::<Self>())
}
async fn route(&self, res: &Resources<TResourceKey>) -> Result<TaskResult<TState>, CanoError>;
}
pub type DynRouterTask<TState, TResourceKey = Cow<'static, str>> =
dyn RouterTask<TState, TResourceKey> + Send + Sync;
pub type RouterTaskObject<TState, TResourceKey = Cow<'static, str>> =
std::sync::Arc<DynRouterTask<TState, TResourceKey>>;
#[cfg(test)]
mod tests {
use super::*;
use crate::resource::Resources;
use crate::task;
use crate::task::Task;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Step {
Decide,
PathA,
PathB,
Done,
}
struct SingleRouter;
#[task::router]
impl RouterTask<Step> for SingleRouter {
async fn route(&self, _res: &Resources) -> Result<TaskResult<Step>, CanoError> {
Ok(TaskResult::Single(Step::PathA))
}
}
#[tokio::test]
async fn test_router_task_single_via_route() {
let router = SingleRouter;
let res = Resources::new();
let result = RouterTask::route(&router, &res).await.unwrap();
assert_eq!(result, TaskResult::Single(Step::PathA));
}
#[tokio::test]
async fn test_router_task_single_via_task_run() {
let router = SingleRouter;
let res = Resources::new();
let result = Task::run(&router, &res).await.unwrap();
assert_eq!(result, TaskResult::Single(Step::PathA));
}
struct SplitRouter;
#[task::router]
impl RouterTask<Step> for SplitRouter {
async fn route(&self, _res: &Resources) -> Result<TaskResult<Step>, CanoError> {
Ok(TaskResult::Split(vec![Step::PathA, Step::PathB]))
}
}
#[tokio::test]
async fn test_router_task_split() {
let router = SplitRouter;
let res = Resources::new();
let result = Task::run(&router, &res).await.unwrap();
assert_eq!(result, TaskResult::Split(vec![Step::PathA, Step::PathB]));
}
#[tokio::test]
async fn test_router_task_as_dyn_task() {
let router: Arc<dyn Task<Step>> = Arc::new(SingleRouter);
let res = Resources::new();
let result = Task::run(router.as_ref(), &res).await.unwrap();
assert_eq!(result, TaskResult::Single(Step::PathA));
}
struct CustomRouter;
#[task::router]
impl RouterTask<Step> for CustomRouter {
fn config(&self) -> TaskConfig {
TaskConfig::minimal()
}
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed("my-custom-router")
}
async fn route(&self, _res: &Resources) -> Result<TaskResult<Step>, CanoError> {
Ok(TaskResult::Single(Step::Done))
}
}
#[test]
fn test_router_task_config_override() {
let router = CustomRouter;
assert_eq!(RouterTask::config(&router).retry_mode.max_attempts(), 1);
}
#[test]
fn test_router_task_name_override() {
let router = CustomRouter;
assert_eq!(RouterTask::<Step>::name(&router), "my-custom-router");
}
#[test]
fn test_companion_task_forwards_config_and_name() {
let router = CustomRouter;
assert_eq!(Task::config(&router).retry_mode.max_attempts(), 1);
assert_eq!(Task::name(&router), "my-custom-router");
}
#[tokio::test]
async fn test_router_task_in_workflow() {
use crate::workflow::Workflow;
struct PathATask;
#[task]
impl Task<Step> for PathATask {
async fn run_bare(&self) -> Result<TaskResult<Step>, CanoError> {
Ok(TaskResult::Single(Step::Done))
}
}
let workflow = Workflow::bare()
.register(Step::Decide, SingleRouter)
.register(Step::PathA, PathATask)
.add_exit_state(Step::Done);
let result = workflow.orchestrate(Step::Decide).await.unwrap();
assert_eq!(result, Step::Done);
}
struct DefaultConfigRouter;
#[task::router]
impl RouterTask<Step> for DefaultConfigRouter {
async fn route(&self, _res: &Resources) -> Result<TaskResult<Step>, CanoError> {
Ok(TaskResult::Single(Step::Done))
}
}
#[test]
fn test_router_task_default_config() {
let router = DefaultConfigRouter;
assert_eq!(
RouterTask::<Step>::config(&router)
.retry_mode
.max_attempts(),
4
);
}
#[test]
fn test_router_task_default_name_contains_type_name() {
let router = DefaultConfigRouter;
let name = RouterTask::<Step>::name(&router);
assert!(
name.contains("DefaultConfigRouter"),
"default name should contain the type name, got: {name}",
);
}
}