lean_ctx_protocol/hive/
graph.rs1use serde::{Deserialize, Serialize};
20
21use super::{require_schema, validate_text};
22use crate::ValidationError;
23
24pub const GRAPH_SCHEMA: &str = "leanctx.hive.graph/v1";
25pub const MAX_GRAPH_NODES: usize = 20_000;
26pub const MAX_GRAPH_EDGES: usize = 60_000;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum GraphNodeKind {
31 Code,
33 Knowledge,
35 Work,
37 Actor,
39 #[serde(other)]
40 Unknown,
41}
42
43pub const GRAPH_EDGE_TYPES: [&str; 8] = [
46 "applies_to",
48 "contains",
51 "imports",
52 "calls",
53 "shows",
55 "changed",
57 "worked_on",
59 "questions",
61];
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct GraphNodeV1 {
65 pub id: String,
66 pub kind: GraphNodeKind,
67 pub label: String,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct GraphEdgeV1 {
72 pub from: String,
73 pub to: String,
74 #[serde(rename = "type")]
75 pub kind: String,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub struct GraphV1 {
80 pub schema: String,
81 pub vault_id: String,
82 pub revision: u64,
83 pub nodes: Vec<GraphNodeV1>,
85 pub edges: Vec<GraphEdgeV1>,
87}
88
89impl GraphV1 {
90 pub fn validate(&self) -> Result<(), ValidationError> {
91 require_schema(&self.schema, GRAPH_SCHEMA)?;
92 super::validate_vault_id(&self.vault_id)?;
93 if self.nodes.len() > MAX_GRAPH_NODES || self.edges.len() > MAX_GRAPH_EDGES {
94 return Err(ValidationError::new(format!(
95 "a graph has at most {MAX_GRAPH_NODES} nodes and {MAX_GRAPH_EDGES} edges"
96 )));
97 }
98 for (i, node) in self.nodes.iter().enumerate() {
99 validate_text(&node.id, 700, "node id", false)?;
100 validate_text(&node.label, 300, "node label", false)?;
101 if i > 0 && self.nodes[i - 1].id >= node.id {
102 return Err(ValidationError::new(
103 "nodes must be sorted by id without repeats",
104 ));
105 }
106 }
107 let known = |id: &str| {
108 self.nodes
109 .binary_search_by(|node| node.id.as_str().cmp(id))
110 .is_ok()
111 };
112 for (i, edge) in self.edges.iter().enumerate() {
113 if !known(&edge.from) || !known(&edge.to) {
114 return Err(ValidationError::new("an edge names a missing node"));
115 }
116 if !GRAPH_EDGE_TYPES.contains(&edge.kind.as_str())
117 && !super::knowledge::RELATION_TYPES.contains(&edge.kind.as_str())
118 {
119 return Err(ValidationError::new("unknown edge type"));
120 }
121 if i > 0 {
122 let prev = &self.edges[i - 1];
123 if (&prev.from, &prev.to, &prev.kind) >= (&edge.from, &edge.to, &edge.kind) {
124 return Err(ValidationError::new("edges must be sorted without repeats"));
125 }
126 }
127 }
128 Ok(())
129 }
130}