use async_trait::async_trait;
use floxide_core::{error::FloxideError, ActionType, LifecycleNode, NodeId};
use futures::future::BoxFuture;
use std::fmt::Debug;
use std::marker::PhantomData;
use uuid::Uuid;
#[async_trait]
pub trait TransformNode<Input, Output, Error>: Send + Sync
where
Input: Send + 'static,
Output: Send + 'static,
Error: std::error::Error + Send + Sync + 'static,
{
async fn prep(&self, input: Input) -> Result<Input, Error>;
async fn exec(&self, input: Input) -> Result<Output, Error>;
async fn post(&self, output: Output) -> Result<Output, Error>;
}
pub struct TransformNodeAdapter<TN, Input, Output, Error, Action>
where
TN: TransformNode<Input, Output, Error>,
Input: Clone + Send + Sync + 'static,
Output: Clone + Send + Sync + 'static,
Error: std::error::Error + Send + Sync + 'static,
Action: ActionType + Default + Send + Sync + 'static,
{
node: TN,
id: NodeId,
_phantom: PhantomData<(Input, Output, Error, Action)>,
}
impl<TN, Input, Output, Error, Action> TransformNodeAdapter<TN, Input, Output, Error, Action>
where
TN: TransformNode<Input, Output, Error>,
Input: Clone + Send + Sync + 'static,
Output: Clone + Send + Sync + 'static,
Error: std::error::Error + Send + Sync + 'static,
Action: ActionType + Default + Send + Sync + 'static,
{
pub fn new(node: TN) -> Self {
Self {
node,
id: Uuid::new_v4().to_string(),
_phantom: PhantomData,
}
}
pub fn with_id(node: TN, id: impl Into<String>) -> Self {
Self {
node,
id: id.into(),
_phantom: PhantomData,
}
}
}
impl<TN, Input, Output, Error, Action> Debug
for TransformNodeAdapter<TN, Input, Output, Error, Action>
where
TN: TransformNode<Input, Output, Error> + Debug,
Input: Clone + Send + Sync + 'static,
Output: Clone + Send + Sync + 'static,
Error: std::error::Error + Send + Sync + 'static,
Action: ActionType + Default + Send + Sync + 'static,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransformNodeAdapter")
.field("node", &self.node)
.field("id", &self.id)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct TransformContext<Input> {
pub input: Input,
}
impl<Input> TransformContext<Input> {
pub fn new(input: Input) -> Self {
Self { input }
}
}
#[async_trait]
impl<TN, Input, Output, Error, Action> LifecycleNode<TransformContext<Input>, Action>
for TransformNodeAdapter<TN, Input, Output, Error, Action>
where
TN: TransformNode<Input, Output, Error> + Send + Sync + 'static,
Input: Clone + Send + Sync + 'static,
Output: Clone + Send + Sync + 'static,
Error: std::error::Error + Send + Sync + 'static + Into<FloxideError>,
Action: ActionType + Default + Send + Sync + 'static,
{
type PrepOutput = Input;
type ExecOutput = Output;
fn id(&self) -> NodeId {
self.id.clone()
}
async fn prep(
&self,
ctx: &mut TransformContext<Input>,
) -> Result<Self::PrepOutput, FloxideError> {
self.node
.prep(ctx.input.clone())
.await
.map_err(|e| e.into())
}
async fn exec(&self, prep_result: Self::PrepOutput) -> Result<Self::ExecOutput, FloxideError> {
self.node.exec(prep_result).await.map_err(|e| e.into())
}
async fn post(
&self,
_prep_result: Self::PrepOutput,
exec_result: Self::ExecOutput,
_ctx: &mut TransformContext<Input>,
) -> Result<Action, FloxideError> {
let _result = self.node.post(exec_result).await.map_err(|e| e.into())?;
Ok(Action::default())
}
}
pub fn transform_node<P, E, Po, I, O, Err>(
prep_fn: P,
exec_fn: E,
post_fn: Po,
) -> impl TransformNode<I, O, Err>
where
I: Clone + Send + Sync + 'static,
O: Clone + Send + Sync + 'static,
Err: std::error::Error + Send + Sync + 'static,
P: Fn(I) -> BoxFuture<'static, Result<I, Err>> + Send + Sync + 'static,
E: Fn(I) -> BoxFuture<'static, Result<O, Err>> + Send + Sync + 'static,
Po: Fn(O) -> BoxFuture<'static, Result<O, Err>> + Send + Sync + 'static,
{
struct ClosureTransformNode<P, E, Po, I, O, Err> {
prep_fn: P,
exec_fn: E,
post_fn: Po,
_phantom: PhantomData<(I, O, Err)>,
}
impl<P, E, Po, I, O, Err> Debug for ClosureTransformNode<P, E, Po, I, O, Err> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClosureTransformNode").finish()
}
}
#[async_trait]
impl<P, E, Po, I, O, Err> TransformNode<I, O, Err> for ClosureTransformNode<P, E, Po, I, O, Err>
where
I: Clone + Send + Sync + 'static,
O: Clone + Send + Sync + 'static,
Err: std::error::Error + Send + Sync + 'static,
P: Fn(I) -> BoxFuture<'static, Result<I, Err>> + Send + Sync + 'static,
E: Fn(I) -> BoxFuture<'static, Result<O, Err>> + Send + Sync + 'static,
Po: Fn(O) -> BoxFuture<'static, Result<O, Err>> + Send + Sync + 'static,
{
async fn prep(&self, input: I) -> Result<I, Err> {
(self.prep_fn)(input).await
}
async fn exec(&self, input: I) -> Result<O, Err> {
(self.exec_fn)(input).await
}
async fn post(&self, output: O) -> Result<O, Err> {
(self.post_fn)(output).await
}
}
ClosureTransformNode {
prep_fn,
exec_fn,
post_fn,
_phantom: PhantomData,
}
}
pub fn to_lifecycle_node<TN, I, O, Err, A>(
transform_node: TN,
) -> impl LifecycleNode<TransformContext<I>, A, PrepOutput = I, ExecOutput = O>
where
TN: TransformNode<I, O, Err> + Send + Sync + 'static,
I: Clone + Send + Sync + 'static,
O: Clone + Send + Sync + 'static,
Err: std::error::Error + Send + Sync + 'static + Into<FloxideError>,
A: ActionType + Default + Send + Sync + 'static,
{
TransformNodeAdapter::<TN, I, O, Err, A>::new(transform_node)
}
pub fn create_transform_node<I, O, Err>(
prep_fn: impl Fn(I) -> BoxFuture<'static, Result<I, Err>> + Send + Sync + 'static,
exec_fn: impl Fn(I) -> BoxFuture<'static, Result<O, Err>> + Send + Sync + 'static,
post_fn: impl Fn(O) -> BoxFuture<'static, Result<O, Err>> + Send + Sync + 'static,
) -> impl TransformNode<I, O, Err>
where
I: Clone + Send + Sync + 'static,
O: Clone + Send + Sync + 'static,
Err: std::error::Error + Send + Sync + 'static,
{
transform_node(prep_fn, exec_fn, post_fn)
}