Skip to main content

cognis_graph/
subgraph.rs

1//! Run a child graph as a node inside a parent graph, with state
2//! mapping in both directions.
3//!
4//! Already partly possible (`CompiledGraph<S>: Runnable<S, S>`), but the
5//! parent and child usually have *different* state types. [`subgraph`]
6//! adapts: parent state → child state → run → child state → parent
7//! update.
8
9use std::sync::Arc;
10
11use async_trait::async_trait;
12
13use cognis_core::{Result, Runnable};
14
15use crate::compiled::CompiledGraph;
16use crate::goto::Goto;
17use crate::node::{Node, NodeCtx, NodeOut};
18use crate::state::GraphState;
19
20/// Adapter `Node` that runs a child graph and projects its state into a
21/// parent update.
22#[allow(clippy::type_complexity)]
23pub struct Subgraph<P, C>
24where
25    P: GraphState,
26    C: GraphState + Clone,
27    <C as GraphState>::Update: Clone,
28{
29    name: String,
30    child: CompiledGraph<C>,
31    project_in: Arc<dyn Fn(&P) -> C + Send + Sync>,
32    project_out: Arc<dyn Fn(&C) -> P::Update + Send + Sync>,
33    next: Goto,
34}
35
36impl<P, C> Subgraph<P, C>
37where
38    P: GraphState,
39    C: GraphState + Clone + Send + 'static,
40    <C as GraphState>::Update: Clone,
41{
42    /// Build a subgraph adapter.
43    ///
44    /// - `project_in`: parent state → fresh child state seed.
45    /// - `project_out`: final child state → parent update.
46    /// - `next`: where the parent goes after the subgraph returns.
47    pub fn new(
48        name: impl Into<String>,
49        child: CompiledGraph<C>,
50        project_in: impl Fn(&P) -> C + Send + Sync + 'static,
51        project_out: impl Fn(&C) -> P::Update + Send + Sync + 'static,
52        next: Goto,
53    ) -> Self {
54        Self {
55            name: name.into(),
56            child,
57            project_in: Arc::new(project_in),
58            project_out: Arc::new(project_out),
59            next,
60        }
61    }
62}
63
64#[async_trait]
65impl<P, C> Node<P> for Subgraph<P, C>
66where
67    P: GraphState + Send + Sync + 'static,
68    C: GraphState + Clone + Send + Sync + 'static,
69    <C as GraphState>::Update: Clone,
70    <P as GraphState>::Update: Send + 'static,
71{
72    async fn execute(&self, parent: &P, ctx: &NodeCtx<'_>) -> Result<NodeOut<P>> {
73        let seed = (self.project_in)(parent);
74        let mut cfg = ctx.config.clone();
75        // Fresh run_id for the child run so observers can correlate.
76        cfg.run_id = uuid::Uuid::new_v4();
77        let final_child = self.child.invoke(seed, cfg).await?;
78        let update = (self.project_out)(&final_child);
79        Ok(NodeOut {
80            update,
81            goto: self.next.clone(),
82        })
83    }
84    fn name(&self) -> &str {
85        &self.name
86    }
87}