tawasuyu

tawasuyu/brahman

Rust 467TOML 184Markdown 49JSON 176411 Definitions

⎇ e77a32f4d6a09fe29701811f4d2e60dd2cb678db crates/modules/semantic_dht/minga-vfs/src/source.rs Rust · 154 lines · 4.7 KB History Who touched what raw

feat(minga): minga-vfs — proyecta el repo como filesystem FUSE e77a32f4d · sergio ·

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}