Skip to main content

synaptic_graph/
node.rs

1use std::future::Future;
2use std::marker::PhantomData;
3
4use async_trait::async_trait;
5use synaptic_core::SynapticError;
6
7use crate::command::NodeOutput;
8use crate::State;
9
10/// A node in the graph that processes state.
11///
12/// Nodes return `NodeOutput<S>` which is either a state update
13/// or a `Command` for dynamic control flow.
14///
15/// For backwards compatibility, returning `Ok(state.into())` works
16/// via the `From<S> for NodeOutput<S>` impl.
17#[async_trait]
18pub trait Node<S: State>: Send + Sync {
19    async fn process(&self, state: S) -> Result<NodeOutput<S>, SynapticError>;
20}
21
22/// Wraps an async function as a Node.
23///
24/// The function can return either `NodeOutput<S>` directly or `S`
25/// (via `Into<NodeOutput<S>>`).
26pub struct FnNode<S, F, Fut>
27where
28    S: State,
29    F: Fn(S) -> Fut + Send + Sync,
30    Fut: Future<Output = Result<NodeOutput<S>, SynapticError>> + Send,
31{
32    func: F,
33    _marker: PhantomData<S>,
34}
35
36impl<S, F, Fut> FnNode<S, F, Fut>
37where
38    S: State,
39    F: Fn(S) -> Fut + Send + Sync,
40    Fut: Future<Output = Result<NodeOutput<S>, SynapticError>> + Send,
41{
42    pub fn new(func: F) -> Self {
43        Self {
44            func,
45            _marker: PhantomData,
46        }
47    }
48}
49
50#[async_trait]
51impl<S, F, Fut> Node<S> for FnNode<S, F, Fut>
52where
53    S: State,
54    F: Fn(S) -> Fut + Send + Sync,
55    Fut: Future<Output = Result<NodeOutput<S>, SynapticError>> + Send,
56{
57    async fn process(&self, state: S) -> Result<NodeOutput<S>, SynapticError> {
58        (self.func)(state).await
59    }
60}