1use crate::node::graph::Graph;
12use crate::node::{self, Node};
13use crate::visit;
14use gantz_ca::{
15 ContentAddr, DataGraph, Datum, DatumError, GraphAddr, NodeData, Registry, SectionId, datum,
16};
17use petgraph::visit::EdgeRef;
18use serde::{Serialize, de::DeserializeOwned};
19use std::collections::{HashMap, HashSet, VecDeque};
20
21#[derive(Debug)]
31pub struct ReifiedGraphs<N> {
32 graphs: HashMap<GraphAddr, Graph<N>>,
33}
34
35#[derive(Clone, Debug, thiserror::Error)]
38pub enum EraseNodeError {
39 #[error("node serde error: {0}")]
41 Datum(#[from] DatumError),
42 #[error("node serde produced a value without a `type`-tagged map")]
44 Untagged,
45}
46
47#[derive(Clone, Debug, thiserror::Error)]
49#[error("node {node_ix}: {source}")]
50pub struct EraseError {
51 pub node_ix: usize,
53 #[source]
55 pub source: EraseNodeError,
56}
57
58#[derive(Clone, Debug, thiserror::Error)]
61#[error("node type `{tag}`: {source}")]
62pub struct ReifyNodeError {
63 pub tag: String,
65 #[source]
67 pub source: DatumError,
68}
69
70#[derive(Clone, Debug, thiserror::Error)]
72#[error("node {node_ix}: {source}")]
73pub struct ReifyError {
74 pub node_ix: usize,
76 #[source]
78 pub source: ReifyNodeError,
79}
80
81#[derive(Clone, Debug, thiserror::Error)]
83#[error("graph {graph}: {source}")]
84pub struct EnsureError {
85 pub graph: GraphAddr,
87 #[source]
89 pub source: ReifyError,
90}
91
92impl<N> ReifiedGraphs<N> {
93 pub fn new() -> Self {
95 Self {
96 graphs: HashMap::new(),
97 }
98 }
99
100 pub fn get(&self, addr: &GraphAddr) -> Option<&Graph<N>> {
102 self.graphs.get(addr)
103 }
104
105 pub fn contains(&self, addr: &GraphAddr) -> bool {
107 self.graphs.contains_key(addr)
108 }
109
110 pub fn retain_live(&mut self, live: &gantz_ca::LiveSet) {
112 self.graphs.retain(|addr, _| live.graphs.contains(addr));
113 }
114}
115
116impl<N> ReifiedGraphs<N> {
117 pub fn ensure_with(
125 &mut self,
126 reg: &Registry,
127 seeds: impl IntoIterator<Item = ContentAddr>,
128 reify_node: impl Fn(&NodeData) -> Result<N, ReifyNodeError>,
129 ) -> Result<(), EnsureError> {
130 let mut queue: VecDeque<GraphAddr> = seeds.into_iter().map(GraphAddr::from).collect();
131 while let Some(addr) = queue.pop_front() {
132 if self.graphs.contains_key(&addr) {
133 continue;
134 }
135 let Some(dg) = reg.graph(&addr) else { continue };
136 queue.extend(
137 dg.node_weights()
138 .flat_map(|n| n.refs.iter().copied().map(GraphAddr::from)),
139 );
140 let g = reify_with(dg, &reify_node).map_err(|source| EnsureError {
141 graph: addr,
142 source,
143 })?;
144 self.graphs.insert(addr, g);
145 }
146 Ok(())
147 }
148
149 pub fn ensure_all_with(
156 &mut self,
157 reg: &Registry,
158 reify_node: impl Fn(&NodeData) -> Result<N, ReifyNodeError>,
159 ) -> Vec<EnsureError> {
160 let mut errs = vec![];
161 for (addr, dg) in reg.graphs() {
162 if self.graphs.contains_key(addr) {
163 continue;
164 }
165 match reify_with(dg, &reify_node) {
166 Ok(g) => {
167 self.graphs.insert(*addr, g);
168 }
169 Err(source) => errs.push(EnsureError {
170 graph: *addr,
171 source,
172 }),
173 }
174 }
175 errs
176 }
177}
178
179impl<N: DeserializeOwned> ReifiedGraphs<N> {
180 pub fn ensure(
183 &mut self,
184 reg: &Registry,
185 seeds: impl IntoIterator<Item = ContentAddr>,
186 ) -> Result<(), EnsureError> {
187 self.ensure_with(reg, seeds, reify_node)
188 }
189
190 pub fn ensure_all(&mut self, reg: &Registry) -> Vec<EnsureError> {
193 self.ensure_all_with(reg, reify_node)
194 }
195}
196
197impl<N> Default for ReifiedGraphs<N> {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203pub fn erase_node<N>(node: &N) -> Result<NodeData, EraseNodeError>
211where
212 N: Serialize + Node,
213{
214 let datum = datum::to_datum(node)?;
215 let Datum::Map(mut entries) = datum else {
216 return Err(EraseNodeError::Untagged);
217 };
218 let Some(ix) = entries.iter().position(|(k, _)| k == "type") else {
219 return Err(EraseNodeError::Untagged);
220 };
221 let (_, tag) = entries.remove(ix);
222 let Datum::Str(tag) = tag else {
223 return Err(EraseNodeError::Untagged);
224 };
225 Ok(node_data(tag, entries, node))
226}
227
228pub fn erase_node_tagged<N>(tag: &str, node: &N) -> Result<NodeData, EraseNodeError>
242where
243 N: Serialize + Node,
244{
245 let mut entries = match datum::to_datum(node)? {
246 Datum::Map(entries) => entries,
247 Datum::Null => vec![],
248 _ => return Err(EraseNodeError::Untagged),
249 };
250 entries.retain(|(k, _)| k != "type");
251 Ok(node_data(tag.to_string(), entries, node))
252}
253
254pub fn erase_node_typed<T>(node: &T) -> Result<NodeData, EraseNodeError>
259where
260 T: gantz_nodetag::NodeTag + Serialize + Node,
261{
262 erase_node_tagged(T::TAG, node)
263}
264
265fn node_data<N: Node>(tag: String, fields: Vec<(String, Datum)>, node: &N) -> NodeData {
269 let (refs, blobs) = node_out_refs(node);
270 let mut node_data = NodeData {
271 tag,
272 data: Datum::Map(fields),
273 refs,
274 blobs,
275 };
276 node_data.canonicalize();
277 node_data
278}
279
280pub fn reify_node<N>(node_data: &NodeData) -> Result<N, ReifyNodeError>
282where
283 N: DeserializeOwned,
284{
285 let err = |source| ReifyNodeError {
286 tag: node_data.tag.clone(),
287 source,
288 };
289 let Datum::Map(fields) = node_data.data.clone() else {
290 return Err(err(serde::de::Error::custom("node data is not a map")));
291 };
292 let datum = Datum::tagged(&node_data.tag, fields);
294 datum::from_datum(datum).map_err(err)
295}
296
297pub fn reify_node_concrete<T>(node_data: &NodeData) -> Result<T, ReifyNodeError>
305where
306 T: DeserializeOwned,
307{
308 let err = |source| ReifyNodeError {
309 tag: node_data.tag.clone(),
310 source,
311 };
312 let Datum::Map(_) = node_data.data else {
313 return Err(err(serde::de::Error::custom("node data is not a map")));
314 };
315 datum::from_datum(node_data.data.clone()).map_err(err)
316}
317
318pub fn erase_with_addr<N>(g: &Graph<N>) -> Result<(DataGraph, GraphAddr), EraseError>
325where
326 N: Serialize + Node,
327{
328 let dg = erase(g)?;
329 let addr = gantz_ca::graph_addr(&dg);
330 Ok((dg, addr))
331}
332
333pub fn erase<N>(g: &Graph<N>) -> Result<DataGraph, EraseError>
336where
337 N: Serialize + Node,
338{
339 let mut out = DataGraph::with_capacity(g.node_count(), g.edge_count());
340 for (node_ix, w) in g.node_weights().enumerate() {
341 let node_data = erase_node(w).map_err(|source| EraseError { node_ix, source })?;
342 out.add_node(node_data);
343 }
344 for e in g.edge_references() {
345 out.add_edge(e.source(), e.target(), *e.weight());
346 }
347 Ok(out)
348}
349
350pub fn reify<N>(g: &DataGraph) -> Result<Graph<N>, ReifyError>
353where
354 N: DeserializeOwned,
355{
356 reify_with(g, reify_node)
357}
358
359pub fn reify_with<N>(
362 g: &DataGraph,
363 reify_node: impl Fn(&NodeData) -> Result<N, ReifyNodeError>,
364) -> Result<Graph<N>, ReifyError> {
365 let mut out = Graph::with_capacity(g.node_count(), g.edge_count());
366 for (node_ix, node_data) in g.node_weights().enumerate() {
367 let node = reify_node(node_data).map_err(|source| ReifyError { node_ix, source })?;
368 out.add_node(node);
369 }
370 for e in g.edge_references() {
371 out.add_edge(e.source(), e.target(), *e.weight());
372 }
373 Ok(out)
374}
375
376fn node_out_refs<N: Node>(node: &N) -> (Vec<ContentAddr>, Vec<(SectionId, ContentAddr)>) {
383 fn no_node(_: &ContentAddr) -> Option<&'static dyn Node> {
384 None
385 }
386 let mut addrs = HashSet::new();
387 let mut blobs = HashSet::new();
388 node::visit(
389 visit::Ctx::new(&no_node, &[], &[]),
390 node,
391 &mut visit::RequiredAddrs { addrs: &mut addrs },
392 );
393 node::visit(
394 visit::Ctx::new(&no_node, &[], &[]),
395 node,
396 &mut visit::RequiredBlobs { blobs: &mut blobs },
397 );
398 let mut refs: Vec<_> = addrs.into_iter().collect();
399 refs.sort();
400 let mut blobs: Vec<_> = blobs.into_iter().collect();
401 blobs.sort();
402 (refs, blobs)
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::node::ExprResult;
409
410 #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
413 #[serde(tag = "type")]
414 enum TestNode {
415 Num { v: i64 },
416 Link { addr: ContentAddr },
417 }
418
419 impl Node for TestNode {
420 fn expr(&self, _: node::ExprCtx) -> ExprResult {
421 unimplemented!("not compiled in these tests")
422 }
423
424 fn required_addrs(&self) -> Vec<ContentAddr> {
425 match self {
426 TestNode::Num { .. } => vec![],
427 TestNode::Link { addr } => vec![*addr],
428 }
429 }
430 }
431
432 fn num(v: i64) -> TestNode {
433 TestNode::Num { v }
434 }
435
436 fn graph(nodes: impl IntoIterator<Item = TestNode>) -> Graph<TestNode> {
437 let mut g = Graph::default();
438 let ixs: Vec<_> = nodes.into_iter().map(|n| g.add_node(n)).collect();
439 for w in ixs.windows(2) {
440 g.add_edge(w[0], w[1], gantz_ca::Edge::from((0, 0)));
441 }
442 g
443 }
444
445 #[test]
446 fn erase_node_splits_tag_and_extracts_refs() {
447 let nd = erase_node(&num(42)).unwrap();
448 assert_eq!(nd.tag, "Num");
449 assert_eq!(nd.data, Datum::Map(vec![("v".into(), Datum::I64(42))]));
450 assert!(nd.refs.is_empty() && nd.blobs.is_empty());
451 assert!(nd.is_canonical());
452
453 let target = ContentAddr([7; 32]);
454 let nd = erase_node(&TestNode::Link { addr: target }).unwrap();
455 assert_eq!(nd.tag, "Link");
456 assert_eq!(nd.refs, vec![target]);
457 }
458
459 #[test]
464 fn erase_node_tagged_matches_erase_node() {
465 let link = TestNode::Link {
466 addr: ContentAddr([7; 32]),
467 };
468 for (tag, node) in [("Num", num(42)), ("Link", link)] {
469 let tagged = erase_node_tagged(tag, &node).unwrap();
470 let split = erase_node(&node).unwrap();
471 assert_eq!(tagged, split, "typed and box erasure diverge for {tag}");
472 assert_eq!(tagged.content_addr(), split.content_addr());
473 }
474 }
475
476 #[test]
481 fn concrete_erase_reify_round_trips() {
482 use gantz_nodetag::NodeTag;
483
484 #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize, NodeTag)]
485 struct Plain {
486 v: i64,
487 }
488
489 #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize, NodeTag)]
490 struct Unit;
491
492 impl Node for Plain {
493 fn expr(&self, _: node::ExprCtx) -> ExprResult {
494 unimplemented!("not compiled in these tests")
495 }
496 }
497
498 impl Node for Unit {
499 fn expr(&self, _: node::ExprCtx) -> ExprResult {
500 unimplemented!("not compiled in these tests")
501 }
502 }
503
504 let nd = erase_node_typed(&Plain { v: 7 }).unwrap();
505 assert_eq!(nd.tag, "Plain");
506 assert_eq!(nd.data, Datum::Map(vec![("v".into(), Datum::I64(7))]));
507 assert!(nd.is_canonical());
508 assert_eq!(reify_node_concrete::<Plain>(&nd).unwrap(), Plain { v: 7 });
509
510 let nd = erase_node_typed(&Unit).unwrap();
511 assert_eq!(nd.tag, "Unit");
512 assert_eq!(nd.data, Datum::Map(vec![]));
513 assert_eq!(reify_node_concrete::<Unit>(&nd).unwrap(), Unit);
514 }
515
516 #[test]
517 fn graph_round_trips_preserving_structure() {
518 let mut g = graph([num(1), num(2), num(3)]);
519 g.add_edge(0.into(), 2.into(), gantz_ca::Edge::from((1, 1)));
521 let dg = erase(&g).unwrap();
522 let back: Graph<TestNode> = reify(&dg).unwrap();
523 let weights: Vec<_> = back.node_weights().cloned().collect();
524 assert_eq!(weights, vec![num(1), num(2), num(3)]);
525 let edges: Vec<_> = back
526 .edge_references()
527 .map(|e| (e.source().index(), e.target().index(), *e.weight()))
528 .collect();
529 let expected: Vec<_> = g
530 .edge_references()
531 .map(|e| (e.source().index(), e.target().index(), *e.weight()))
532 .collect();
533 assert_eq!(edges, expected);
534 }
535
536 #[test]
537 fn reify_unknown_tag_names_node_and_tag() {
538 let mut dg = erase(&graph([num(1)])).unwrap();
539 dg.node_weights_mut().for_each(|n| n.tag = "Mystery".into());
540 let err = reify::<TestNode>(&dg).unwrap_err();
541 assert_eq!(err.node_ix, 0);
542 assert_eq!(err.source.tag, "Mystery");
543 assert!(err.to_string().contains("Mystery"), "{err}");
544 }
545
546 #[test]
547 fn ensure_reifies_transitive_refs_and_ignores_unresolved() {
548 let mut reg = Registry::default();
549 let leaf = reg.add_graph(erase(&graph([num(1)])).unwrap());
550 let mid = {
551 let g = graph([TestNode::Link { addr: leaf.into() }, num(2)]);
552 reg.add_graph(erase(&g).unwrap())
553 };
554 let root = {
555 let mut g = graph([TestNode::Link { addr: mid.into() }]);
557 g.add_node(TestNode::Link {
558 addr: ContentAddr([9; 32]),
559 });
560 reg.add_graph(erase(&g).unwrap())
561 };
562
563 let mut cache = ReifiedGraphs::<TestNode>::new();
564 cache.ensure(®, [root.into()]).unwrap();
565 assert!(cache.contains(&root) && cache.contains(&mid) && cache.contains(&leaf));
566 assert!(!cache.contains(&GraphAddr::from(ContentAddr([9; 32]))));
567 assert_eq!(cache.get(&leaf).unwrap().node_count(), 1);
568
569 cache.ensure(®, [root.into()]).unwrap();
571 }
572}