Skip to main content

co_primitives/library/
node_reader.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::{from_cbor, MultiCodec, Node, Storage};
5use cid::Cid;
6use serde::de::DeserializeOwned;
7use std::collections::VecDeque;
8
9#[derive(Debug, thiserror::Error)]
10pub enum NodeReaderError {
11	#[error("Invalid argument")]
12	InvalidArgument(#[source] anyhow::Error),
13
14	#[error("Decode failed")]
15	Decode(#[source] anyhow::Error),
16}
17
18pub fn node_reader<T>(storage: &dyn Storage, cid: Option<Cid>) -> impl Iterator<Item = Result<T, NodeReaderError>> + '_
19where
20	T: Clone + DeserializeOwned + 'static,
21{
22	NodeIterator::new(storage, cid)
23}
24
25pub struct NodeIterator<'a, T>
26where
27	T: 'a + Clone + DeserializeOwned,
28{
29	storage: &'a dyn Storage,
30	stack: VecDeque<Cid>,
31	entries: VecDeque<T>,
32}
33
34impl<'a, T> NodeIterator<'a, T>
35where
36	T: Clone + DeserializeOwned,
37{
38	pub fn new(storage: &'a dyn Storage, cid: Option<Cid>) -> Self {
39		let mut stack = VecDeque::new();
40		if let Some(cid) = cid {
41			stack.push_front(cid);
42		}
43		Self { storage, stack, entries: Default::default() }
44	}
45}
46
47impl<'a, T> Iterator for NodeIterator<'a, T>
48where
49	T: 'a + Clone + DeserializeOwned,
50{
51	type Item = Result<T, NodeReaderError>;
52
53	fn next(&mut self) -> Option<Self::Item> {
54		// read node
55		while self.entries.is_empty() && !self.stack.is_empty() {
56			if let Some(next_cid) = self.stack.pop_front() {
57				let node = match read_node(self.storage, &next_cid) {
58					Ok(n) => n,
59					Err(e) => return Some(Err(e)),
60				};
61				match node {
62					Node::Node(links) => {
63						self.stack.extend(links.into_iter().map(|link| -> Cid { link.into() }));
64					},
65					Node::Leaf(entries) => self.entries = entries.into(),
66				}
67			}
68		}
69
70		// read
71		self.entries.pop_front().map(|entry| Ok(entry))
72	}
73}
74
75fn read_node<T: Clone + DeserializeOwned>(storage: &dyn Storage, cid: &Cid) -> Result<Node<T>, NodeReaderError> {
76	// get block
77	let block = storage.get(MultiCodec::with_cbor(cid).map_err(|err| NodeReaderError::InvalidArgument(err.into()))?);
78
79	// get node
80	let node: Node<T> = from_cbor(block.data()).map_err(|e| NodeReaderError::Decode(e.into()))?;
81
82	// result
83	Ok(node)
84}