use crate::server::Server;
use anyhow::{Result, anyhow};
use mnem_core::id::NodeId;
use mnem_core::index::Query;
use serde_json::Value;
pub(in crate::tools) fn traverse(server: &mut Server, args: Value) -> Result<String> {
let start_str = args
.get("start")
.and_then(Value::as_str)
.ok_or_else(|| anyhow!("missing 'start'"))?;
let start = match NodeId::parse_uuid(start_str) {
Ok(id) => id,
Err(e) => {
return Err(anyhow!(
"'start' must be a node UUID; got `{start_str}` ({e}). \
Resolve a name to a UUID first via `mnem_resolve_or_create` \
(pass {{name: \"{start_str}\", kind: \"<Label>\"}}), then \
pass the returned UUID here."
));
}
};
let edge_labels: Vec<String> = args
.get("edge_labels")
.and_then(Value::as_array)
.map(|a| {
a.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
let limit = args.get("limit").and_then(Value::as_u64).unwrap_or(25) as usize;
let repo = server.load_repo()?;
let Some(node) = repo.lookup_node(&start)? else {
return Ok(format!("mnem_traverse: start node {start_str} not found\n"));
};
let mut q = Query::new(&repo).label(node.ntype.as_str());
for lbl in &edge_labels {
q = q.with_outgoing(lbl.as_str());
}
let hits = q.limit(usize::MAX).execute()?;
let hit = hits.into_iter().find(|h| h.node.id == start);
let mut out = String::new();
out.push_str(&format!(
"mnem_traverse from {} ({}): ",
node.ntype,
start.to_uuid_string()
));
match hit {
None => {
out.push_str("0 edges\n");
}
Some(h) => {
let edges = h.edges.into_iter().take(limit).collect::<Vec<_>>();
out.push_str(&format!("{} edge(s)\n", edges.len()));
for e in edges {
out.push_str(&format!(" -[{}]-> {}\n", e.etype, e.dst.to_uuid_string()));
}
}
}
Ok(out)
}