Skip to main content

co_primitives/library/
block_links.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::{from_cbor, Block, CoReference, KnownMultiCodec, MultiCodec};
5use cid::Cid;
6use ipld_core::codec::Links;
7use serde::de::IgnoredAny;
8use std::{collections::BTreeSet, fmt::Debug};
9
10#[derive(Debug, Default, Clone)]
11pub struct BlockLinks {
12	filters: JoinFilter,
13}
14impl BlockLinks {
15	pub fn new() -> Self {
16		Self::default()
17	}
18
19	/// Filter.
20	pub fn with_filter(mut self, filter: impl BlockLinksFilter + 'static) -> Self {
21		self.filters = self.filters.with_filter(filter);
22		self
23	}
24
25	/// Test if the CID codec possibly contains links.
26	pub fn has_links(&self, cid: impl Into<MultiCodec>) -> bool {
27		matches!(
28			cid.into(),
29			MultiCodec::Known(KnownMultiCodec::DagPb)
30				| MultiCodec::Known(KnownMultiCodec::DagCbor)
31				| MultiCodec::Known(KnownMultiCodec::DagJson)
32				| MultiCodec::Known(KnownMultiCodec::CoReference)
33		)
34	}
35
36	/// Get block references.
37	///
38	/// # Notes
39	/// - This because of the block size limit should usually small.
40	/// - The same [`Cid`] possibly is referenced multiple times.
41	pub fn links<'a>(
42		&self,
43		block: &'a Block,
44	) -> Result<impl Iterator<Item = Cid> + Send + Sync + use<'_, 'a>, anyhow::Error> {
45		let iter: Box<dyn Iterator<Item = Cid> + Send + Sync> =
46			if !self.filters.filter_block(block.cid(), block.data())? {
47				Box::new(std::iter::empty())
48			} else {
49				match MultiCodec::from(block.cid()) {
50					MultiCodec::Known(KnownMultiCodec::DagPb) => Box::new(ipld_dagpb::DagPbCodec::links(block.data())?),
51					MultiCodec::Known(KnownMultiCodec::DagCbor) | MultiCodec::Known(KnownMultiCodec::CoReference) => {
52						Box::new(serde_ipld_dagcbor::codec::DagCborCodec::links(block.data())?)
53					},
54					MultiCodec::Known(KnownMultiCodec::DagJson) => {
55						Box::new(serde_ipld_dagjson::codec::DagJsonCodec::links(block.data())?)
56					},
57					_ => Box::new(std::iter::empty()),
58				}
59			};
60		Ok(iter.filter(|cid| self.filters.filter(cid)))
61	}
62}
63
64pub trait BlockLinksFilter: Debug + BlockLinksFilterClone + Send + Sync {
65	/// Filter `cid`. Only cids which returned true will be returned.
66	fn filter(&self, cid: &Cid) -> bool;
67
68	/// Filter the block if its links should be resolve at all.
69	fn filter_block(&self, cid: &Cid, data: &[u8]) -> Result<bool, anyhow::Error>;
70}
71
72pub trait BlockLinksFilterClone {
73	fn box_clone(&self) -> Box<dyn BlockLinksFilter>;
74}
75impl Clone for Box<dyn BlockLinksFilter> {
76	fn clone(&self) -> Self {
77		self.box_clone()
78	}
79}
80impl<T> BlockLinksFilterClone for T
81where
82	T: BlockLinksFilter + Clone + 'static,
83{
84	fn box_clone(&self) -> Box<dyn BlockLinksFilter> {
85		Box::new(self.clone())
86	}
87}
88
89#[derive(Debug, Default, Clone)]
90pub struct JoinFilter {
91	filters: Vec<Box<dyn BlockLinksFilter>>,
92}
93impl JoinFilter {
94	/// Filter.
95	pub fn with_filter(mut self, filter: impl BlockLinksFilter + 'static) -> Self {
96		self.filters.push(Box::new(filter));
97		self
98	}
99}
100impl BlockLinksFilter for JoinFilter {
101	fn filter(&self, cid: &Cid) -> bool {
102		for filter in self.filters.iter() {
103			if !filter.filter(cid) {
104				return false;
105			}
106		}
107		true
108	}
109
110	fn filter_block(&self, cid: &Cid, data: &[u8]) -> Result<bool, anyhow::Error> {
111		for filter in self.filters.iter() {
112			if !filter.filter_block(cid, data)? {
113				return Ok(false);
114			}
115		}
116		Ok(true)
117	}
118}
119
120/// Filter out Cid links.
121#[derive(Debug, Clone)]
122pub struct IgnoreFilter {
123	/// Ignore the specified [`Cid`]'s when found in links.
124	ignore: BTreeSet<Cid>,
125}
126impl IgnoreFilter {
127	pub fn new(ignore: BTreeSet<Cid>) -> Self {
128		Self { ignore }
129	}
130}
131impl BlockLinksFilter for IgnoreFilter {
132	fn filter(&self, cid: &Cid) -> bool {
133		!self.ignore.contains(cid)
134	}
135
136	fn filter_block(&self, _cid: &Cid, _data: &[u8]) -> Result<bool, anyhow::Error> {
137		Ok(true)
138	}
139}
140
141/// Filter out [`CoReference::Weak`] blocks.
142#[derive(Debug, Default, Clone)]
143pub struct WeakCoReferenceFilter {}
144impl WeakCoReferenceFilter {
145	pub fn new() -> Self {
146		Self::default()
147	}
148}
149impl BlockLinksFilter for WeakCoReferenceFilter {
150	fn filter(&self, _cid: &Cid) -> bool {
151		true
152	}
153
154	fn filter_block(&self, cid: &Cid, data: &[u8]) -> Result<bool, anyhow::Error> {
155		Ok(if MultiCodec::is(cid, KnownMultiCodec::CoReference) {
156			let reference: CoReference<IgnoredAny> = from_cbor(data)?;
157			match reference {
158				CoReference::Weak(_) => false,
159			}
160		} else {
161			true
162		})
163	}
164}