Skip to main content

yoagent_state/
query.rs

1use crate::{Graph, Node, NodeId, Relation};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct Lineage {
7    pub root: Option<Node>,
8    pub incoming: Vec<Relation>,
9    pub outgoing: Vec<Relation>,
10    pub related_nodes: Vec<Node>,
11}
12
13impl Lineage {
14    pub fn from_graph(graph: &Graph, id: &NodeId) -> Self {
15        let incoming = graph.incoming(id, None);
16        let outgoing = graph.outgoing(id, None);
17        let mut related = BTreeMap::new();
18
19        for rel in incoming.iter().chain(outgoing.iter()) {
20            for node_id in [&rel.from, &rel.to] {
21                // Plain nested if (not a let-chain): let-chains stabilized in
22                // Rust 1.88, above this crate's declared 1.85 MSRV.
23                if node_id != id {
24                    if let Some(node) = graph.nodes.get(node_id) {
25                        related.insert(node_id.clone(), node.clone());
26                    }
27                }
28            }
29        }
30
31        Self {
32            root: graph.nodes.get(id).cloned(),
33            incoming,
34            outgoing,
35            related_nodes: related.into_values().collect(),
36        }
37    }
38
39    pub fn to_markdown(&self) -> String {
40        let Some(root) = &self.root else {
41            return "Node not found.\n".to_string();
42        };
43
44        let title = root
45            .props
46            .get("title")
47            .and_then(|value| value.as_str())
48            .unwrap_or(root.id.as_str());
49        let status = root
50            .props
51            .get("status")
52            .and_then(|value| value.as_str())
53            .unwrap_or("unknown");
54
55        let mut lines = vec![
56            format!("# {}", title),
57            String::new(),
58            format!("- id: {}", root.id),
59            format!("- kind: {}", root.kind),
60            format!("- status: {}", status),
61        ];
62
63        if !root.artifacts.is_empty() {
64            lines.push(String::new());
65            lines.push("## Artifacts".to_string());
66            for artifact in &root.artifacts {
67                lines.push(format!("- {}: {}", artifact.kind, artifact.uri));
68            }
69        }
70
71        if !self.outgoing.is_empty() {
72            lines.push(String::new());
73            lines.push("## Outgoing".to_string());
74            for rel in &self.outgoing {
75                lines.push(format!("- {} -> {}", rel.rel, rel.to));
76            }
77        }
78
79        if !self.incoming.is_empty() {
80            lines.push(String::new());
81            lines.push("## Incoming".to_string());
82            for rel in &self.incoming {
83                lines.push(format!("- {} <- {}", rel.rel, rel.from));
84            }
85        }
86
87        lines.push(String::new());
88        lines.join("\n")
89    }
90}