⎇ e77a32f4d6a09fe29701811f4d2e60dd2cb678db
crates/modules/semantic_dht/minga-vfs/src/source.rs
History
Who touched what
raw
feat(minga): minga-vfs — proyecta el repo como filesystem FUSE
1//! El contrato [`NodeSource`] —lo mínimo que el VFS necesita de un
2//! repositorio Minga— y sus dos backends. Agnóstico de `fuser`.
3//!
4//! El VFS no quiere conocer `sled` ni la estructura interna del store:
5//! sólo necesita (a) enumerar las raíces del MST y (b) resolver un nodo
6//! por hash. Eso es [`NodeSource`]. [`RepoSource`] lo implementa sobre
7//! el [`PersistentRepo`] en disco; [`MemSource`] sobre un `MemStore` en
8//! RAM (tests, índices efímeros recién sincronizados).
9
10use minga_core::{ContentHash, SemanticNode, StoredNode};
11use minga_store::PersistentRepo;
12
13/// Lo que el VFS necesita de un repositorio para proyectarlo.
14pub trait NodeSource {
15 /// Hashes raíz: el conjunto de claves del MST, un elemento por
16 /// archivo ingerido. Es lo que se lista bajo `roots/`.
17 fn roots(&self) -> Vec<ContentHash>;
18
19 /// Resuelve un único nodo (un eslabón del grafo) por su hash.
20 /// `None` si no está en el almacén.
21 fn get(&self, hash: &ContentHash) -> Option<StoredNode>;
22}
23
24/// Reconstruye el `SemanticNode` completo de un hash, resolviendo
25/// recursivamente sus hijos contra `source`.
26///
27/// Devuelve `None` si el almacén está incompleto: o el propio `hash`
28/// falta, o lo hace algún descendiente (puede ocurrir en un repo a
29/// medio sincronizar).
30pub fn reconstruct<S>(source: &S, hash: &ContentHash) -> Option<SemanticNode>
31where
32 S: NodeSource + ?Sized,
33{
34 let stored = source.get(hash)?;
35 let mut children = Vec::with_capacity(stored.children.len());
36 for child in &stored.children {
37 children.push(reconstruct(source, child)?);
38 }
39 Some(SemanticNode {
40 kind: stored.kind,
41 field_name: stored.field_name,
42 leaf_text: stored.leaf_text,
43 children,
44 })
45}
46
47/// [`NodeSource`] respaldado por un [`PersistentRepo`] de `minga-store`
48/// (almacén `sled` en disco). Es la fuente que usa `minga mount`.
49pub struct RepoSource {
50 repo: PersistentRepo,
51}
52
53impl RepoSource {
54 /// Envuelve un repo ya abierto. La propiedad pasa al `RepoSource`:
55 /// el repo se cierra cuando éste se dropea.
56 pub fn new(repo: PersistentRepo) -> Self {
57 Self { repo }
58 }
59}
60
61impl NodeSource for RepoSource {
62 fn roots(&self) -> Vec<ContentHash> {
63 // Las claves del MST corruptas (si las hubiera) se descartan en
64 // silencio: un par de entradas ilegibles no deben tirar el `ls`.
65 self.repo.mst.iter().filter_map(Result::ok).collect()
66 }
67
68 fn get(&self, hash: &ContentHash) -> Option<StoredNode> {
69 self.repo.nodes.get(hash).ok().flatten()
70 }
71}
72
73/// [`NodeSource`] en memoria: un `MemStore` más un conjunto explícito
74/// de raíces. Para tests y para montar índices que viven sólo en RAM.
75#[derive(Default)]
76pub struct MemSource {
77 store: minga_core::MemStore,
78 roots: Vec<ContentHash>,
79}
80
81impl MemSource {
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 /// Inserta un árbol como raíz (un "archivo") y devuelve su hash.
87 /// Idempotente: ingerir dos veces el mismo árbol no lo duplica.
88 pub fn add_root(&mut self, node: &SemanticNode) -> ContentHash {
89 use minga_core::NodeStore;
90 let hash = self.store.put(node);
91 if !self.roots.contains(&hash) {
92 self.roots.push(hash);
93 }
94 hash
95 }
96}
97
98impl NodeSource for MemSource {
99 fn roots(&self) -> Vec<ContentHash> {
100 self.roots.clone()
101 }
102
103 fn get(&self, hash: &ContentHash) -> Option<StoredNode> {
104 use minga_core::NodeStore;
105 self.store.get(hash).cloned()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use minga_core::ast::SemanticNode;
113
114 fn leaf(kind: &str, text: &str) -> SemanticNode {
115 SemanticNode {
116 kind: kind.to_string(),
117 field_name: None,
118 leaf_text: Some(text.as_bytes().to_vec()),
119 children: Vec::new(),
120 }
121 }
122
123 #[test]
124 fn mem_source_reconstructs_what_it_stored() {
125 let tree = SemanticNode {
126 kind: "root".to_string(),
127 field_name: None,
128 leaf_text: None,
129 children: vec![leaf("a", "1"), leaf("b", "2")],
130 };
131 let mut src = MemSource::new();
132 let hash = src.add_root(&tree);
133
134 assert_eq!(src.roots(), vec![hash]);
135 let back = reconstruct(&src, &hash).expect("debe reconstruir");
136 assert_eq!(back, tree);
137 }
138
139 #[test]
140 fn add_root_is_idempotent() {
141 let tree = leaf("only", "x");
142 let mut src = MemSource::new();
143 let h1 = src.add_root(&tree);
144 let h2 = src.add_root(&tree);
145 assert_eq!(h1, h2);
146 assert_eq!(src.roots().len(), 1);
147 }
148
149 #[test]
150 fn unknown_hash_reconstructs_to_none() {
151 let src = MemSource::new();
152 assert!(reconstruct(&src, &ContentHash([0u8; 32])).is_none());
153 }
154}