use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use serde_json::Value;
use crate::error::{GraphError, Result};
use crate::node::{Node, NodeContext};
#[derive(Debug, Clone, Default)]
pub struct RunNodeOptions {
pub run_id: Option<String>,
}
impl RunNodeOptions {
pub fn with_run_id(run_id: impl Into<String>) -> Self {
Self { run_id: Some(run_id.into()) }
}
}
pub(crate) struct ChildInvoker {
nodes: HashMap<String, Arc<dyn Node>>,
ledger: Arc<Mutex<HashMap<String, Value>>>,
parent_path: String,
counters: Mutex<HashMap<String, u32>>,
}
impl ChildInvoker {
pub(crate) fn new(
nodes: HashMap<String, Arc<dyn Node>>,
ledger: Arc<Mutex<HashMap<String, Value>>>,
parent_path: String,
) -> Self {
Self { nodes, ledger, parent_path, counters: Mutex::new(HashMap::new()) }
}
fn path_for(&self, child: &str, options: &RunNodeOptions) -> String {
let run_id = match &options.run_id {
Some(id) => id.clone(),
None => {
let mut counters = self.counters.lock().expect("child counters");
let count = counters.entry(child.to_string()).or_insert(0);
*count += 1;
count.to_string()
}
};
format!("{}/{}@{}", self.parent_path, child, run_id)
}
pub(crate) async fn run(
&self,
child: &str,
input: Value,
options: RunNodeOptions,
parent: &NodeContext,
) -> Result<Value> {
let path = self.path_for(child, &options);
if let Some(recorded) = self.ledger.lock().expect("child ledger").get(&path) {
tracing::debug!(path = %path, "child already completed, serving its recorded output");
return Ok(recorded.clone());
}
let node = self
.nodes
.get(child)
.ok_or_else(|| GraphError::NodeNotFound(child.to_string()))?
.clone();
let mut state = parent.state.clone();
if let Value::Object(map) = input {
for (key, value) in map {
state.insert(key, value);
}
}
let child_ctx = NodeContext::new(state, parent.config.clone(), parent.step);
let output = node.execute(&child_ctx).await?;
if let Some(interrupt) = output.interrupt {
return Err(GraphError::Interrupted(Box::new(
crate::error::InterruptedExecution::new(
parent.config.thread_id.clone(),
String::new(),
interrupt,
child_ctx.state.clone(),
parent.step,
),
)));
}
let value = Value::Object(output.updates.into_iter().collect());
self.ledger.lock().expect("child ledger").insert(path.clone(), value.clone());
tracing::debug!(path = %path, "child completed");
Ok(value)
}
}