#![allow(clippy::print_stdout)]
use std::sync::Arc;
use chrono::{TimeZone, Utc};
use entelix::{
Direction, ExecutionContext, GraphMemory, InMemoryGraphMemory, Namespace, NodeId, Result,
TenantId,
};
#[tokio::main]
async fn main() -> Result<()> {
let graph: Arc<dyn GraphMemory<String, String>> = Arc::new(InMemoryGraphMemory::new());
let ctx = ExecutionContext::new();
let ns = Namespace::new(TenantId::new("acme")).with_scope("knowledge");
let alice = graph.add_node(&ctx, &ns, "Alice".to_owned()).await?;
let bob = graph.add_node(&ctx, &ns, "Bob".to_owned()).await?;
let carol = graph.add_node(&ctx, &ns, "Carol".to_owned()).await?;
let dave = graph.add_node(&ctx, &ns, "Dave".to_owned()).await?;
let acme = graph.add_node(&ctx, &ns, "Acme".to_owned()).await?;
let t0 = Utc.with_ymd_and_hms(2026, 1, 1, 9, 0, 0).unwrap();
graph
.add_edge(&ctx, &ns, &alice, &bob, "knows".to_owned(), t0)
.await?;
graph
.add_edge(&ctx, &ns, &bob, &carol, "reports_to".to_owned(), t0)
.await?;
graph
.add_edge(&ctx, &ns, &carol, &dave, "reports_to".to_owned(), t0)
.await?;
graph
.add_edge(&ctx, &ns, &alice, &acme, "works_at".to_owned(), t0)
.await?;
graph
.add_edge(&ctx, &ns, &carol, &acme, "works_at".to_owned(), t0)
.await?;
let alice_out = graph
.neighbors(&ctx, &ns, &alice, Direction::Outgoing)
.await?;
println!("=== one-hop outgoing from Alice ===");
for (_id, target, edge) in &alice_out {
let target_name = graph.get_node(&ctx, &ns, target).await?.unwrap_or_default();
println!(" Alice --{edge}--> {target_name}");
}
let reached = graph
.traverse(&ctx, &ns, &alice, Direction::Both, 2)
.await?;
println!("\n=== BFS up to 2 hops from Alice (Direction::Both) ===");
for hop in &reached {
let from = graph
.get_node(&ctx, &ns, &hop.from)
.await?
.unwrap_or_default();
let to = graph
.get_node(&ctx, &ns, &hop.to)
.await?
.unwrap_or_default();
println!(" {from} --{edge}--> {to}", edge = hop.edge);
}
let path = graph
.find_path(&ctx, &ns, &alice, &dave, Direction::Outgoing, 5)
.await?;
println!("\n=== shortest outgoing path Alice → Dave ===");
print_path(graph.as_ref(), &ctx, &ns, path.as_deref()).await?;
let no_path = graph
.find_path(&ctx, &ns, &dave, &alice, Direction::Outgoing, 5)
.await?;
println!("\n=== outgoing path Dave → Alice (none expected) ===");
print_path(graph.as_ref(), &ctx, &ns, no_path.as_deref()).await?;
let undirected = graph
.find_path(&ctx, &ns, &dave, &alice, Direction::Both, 5)
.await?;
println!("\n=== undirected path Dave → Alice ===");
print_path(graph.as_ref(), &ctx, &ns, undirected.as_deref()).await?;
Ok(())
}
async fn print_path(
graph: &dyn GraphMemory<String, String>,
ctx: &ExecutionContext,
ns: &Namespace,
path: Option<&[entelix::GraphHop<String>]>,
) -> Result<()> {
match path {
None => println!(" (no path within depth cap)"),
Some([]) => println!(" (already at destination)"),
Some(hops) => {
for hop in hops {
let from = node_name(graph, ctx, ns, &hop.from).await?;
let to = node_name(graph, ctx, ns, &hop.to).await?;
println!(" {from} --{edge}--> {to}", edge = hop.edge);
}
}
}
Ok(())
}
async fn node_name(
graph: &dyn GraphMemory<String, String>,
ctx: &ExecutionContext,
ns: &Namespace,
id: &NodeId,
) -> Result<String> {
Ok(graph.get_node(ctx, ns, id).await?.unwrap_or_default())
}