Skip to main content

co_storage/storage/
links.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::{BlockStorageContentMapping, ExtendedBlock, ExtendedBlockStorage};
5use async_trait::async_trait;
6use cid::Cid;
7use co_primitives::{
8	Block, BlockLinks, BlockStat, BlockStorage, BlockStorageCloneSettings, BlockStorageStoreParams,
9	CloneWithBlockStorageSettings, MappedCid, StorageError,
10};
11use std::collections::BTreeSet;
12
13/// A [`BlockStorage`] which verfies all links exist when create a new block.
14#[derive(Debug, Default, Clone)]
15pub struct LinksBlockStorage<S> {
16	links: Option<BlockLinks>,
17	next: S,
18}
19impl<S> LinksBlockStorage<S> {
20	pub fn new(next: S, links: Option<BlockLinks>) -> Self {
21		Self { next, links }
22	}
23}
24#[async_trait]
25impl<S> BlockStorage for LinksBlockStorage<S>
26where
27	S: BlockStorage + 'static,
28{
29	async fn get(&self, cid: &Cid) -> Result<Block, StorageError> {
30		self.next.get(cid).await
31	}
32
33	async fn set(&self, block: Block) -> Result<Cid, StorageError> {
34		// verify all links exist
35		if let Some(block_links) = &self.links {
36			if block_links.has_links(block.cid()) {
37				let links = block_links.links(&block)?;
38				for link in links {
39					match self.next.get(&link).await {
40						Ok(_) => {},
41						Err(err) => {
42							let err = StorageError::InvalidArgument(anyhow::Error::from(err).context(format!(
43								"Create block failed: {} reference failed: {}",
44								block.cid(),
45								&link
46							)));
47							tracing::error!(?err, "create-block-failed");
48							return Err(err);
49						},
50					}
51				}
52			}
53		}
54
55		// next
56		self.next.set(block).await
57	}
58
59	async fn stat(&self, cid: &Cid) -> Result<BlockStat, StorageError> {
60		self.next.stat(cid).await
61	}
62
63	async fn remove(&self, cid: &Cid) -> Result<(), StorageError> {
64		self.next.remove(cid).await
65	}
66
67	fn max_block_size(&self) -> usize {
68		self.next.max_block_size()
69	}
70}
71#[async_trait]
72impl<S> ExtendedBlockStorage for LinksBlockStorage<S>
73where
74	S: ExtendedBlockStorage + 'static,
75{
76	async fn set_extended(&self, block: ExtendedBlock) -> Result<Cid, StorageError> {
77		self.next.set_extended(block).await
78	}
79
80	async fn exists(&self, cid: &Cid) -> Result<bool, StorageError> {
81		self.next.exists(cid).await
82	}
83
84	async fn clear(&self) -> Result<(), StorageError> {
85		self.next.clear().await
86	}
87}
88#[async_trait]
89impl<S> BlockStorageContentMapping for LinksBlockStorage<S>
90where
91	S: BlockStorage + BlockStorageContentMapping + 'static,
92{
93	async fn is_content_mapped(&self) -> bool {
94		self.next.is_content_mapped().await
95	}
96
97	async fn to_plain(&self, mapped: &Cid) -> Option<Cid> {
98		self.next.to_plain(mapped).await
99	}
100
101	async fn to_mapped(&self, plain: &Cid) -> Option<Cid> {
102		self.next.to_mapped(plain).await
103	}
104
105	async fn insert_mappings(&self, mappings: BTreeSet<MappedCid>) {
106		self.next.insert_mappings(mappings).await
107	}
108}
109#[async_trait]
110impl<S> CloneWithBlockStorageSettings for LinksBlockStorage<S>
111where
112	S: BlockStorage + CloneWithBlockStorageSettings + 'static,
113{
114	fn clone_with_settings(&self, settings: BlockStorageCloneSettings) -> Self {
115		Self::new(self.next.clone_with_settings(settings), self.links.clone())
116	}
117}
118impl<S> BlockStorageStoreParams for LinksBlockStorage<S>
119where
120	S: BlockStorageStoreParams,
121{
122	type StoreParams = S::StoreParams;
123}