1use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use std::time::Duration;
12
13use super::cid::Cid;
14use super::config::Config;
15use super::error::Error;
16use super::tree::Tree;
17
18const ROOT_MANIFEST_VERSION: u64 = 1;
19
20#[derive(Serialize, Deserialize)]
21struct RootManifestWire {
22 version: u64,
23 root: Option<Cid>,
24 config: Config,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 created_at_millis: Option<u64>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 updated_at_millis: Option<u64>,
29}
30
31#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub struct RootManifest {
38 pub root: Option<Cid>,
40 pub config: Config,
42 pub created_at_millis: Option<u64>,
44 pub updated_at_millis: Option<u64>,
46}
47
48impl RootManifest {
49 pub fn new(root: Option<Cid>, config: Config) -> Self {
51 Self {
52 root,
53 config,
54 created_at_millis: None,
55 updated_at_millis: None,
56 }
57 }
58
59 pub fn with_timestamps_millis(
61 mut self,
62 created_at_millis: Option<u64>,
63 updated_at_millis: Option<u64>,
64 ) -> Self {
65 self.created_at_millis = created_at_millis;
66 self.updated_at_millis = updated_at_millis;
67 self
68 }
69
70 pub fn with_created_at_millis(mut self, created_at_millis: u64) -> Self {
72 self.created_at_millis = Some(created_at_millis);
73 self
74 }
75
76 pub fn with_updated_at_millis(mut self, updated_at_millis: u64) -> Self {
78 self.updated_at_millis = Some(updated_at_millis);
79 self
80 }
81
82 pub fn from_tree_with_timestamps_millis(
84 tree: &Tree,
85 created_at_millis: Option<u64>,
86 updated_at_millis: Option<u64>,
87 ) -> Self {
88 Self {
89 root: tree.root.clone(),
90 config: tree.config.clone(),
91 created_at_millis,
92 updated_at_millis,
93 }
94 }
95
96 pub fn from_tree(tree: &Tree) -> Self {
98 Self {
99 root: tree.root.clone(),
100 config: tree.config.clone(),
101 created_at_millis: None,
102 updated_at_millis: None,
103 }
104 }
105
106 pub fn into_tree(self) -> Tree {
108 Tree {
109 root: self.root,
110 config: self.config,
111 }
112 }
113
114 pub fn to_tree(&self) -> Tree {
116 Tree {
117 root: self.root.clone(),
118 config: self.config.clone(),
119 }
120 }
121
122 pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
124 let wire = RootManifestWire {
125 version: ROOT_MANIFEST_VERSION,
126 root: self.root.clone(),
127 config: self.config.clone(),
128 created_at_millis: self.created_at_millis,
129 updated_at_millis: self.updated_at_millis,
130 };
131 serde_cbor::ser::to_vec_packed(&wire).map_err(|err| Error::Deserialize(err.to_string()))
132 }
133
134 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
136 let wire: RootManifestWire =
137 serde_cbor::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))?;
138 if wire.version != ROOT_MANIFEST_VERSION {
139 return Err(Error::Deserialize(format!(
140 "unsupported root manifest version: {}",
141 wire.version
142 )));
143 }
144 Ok(Self {
145 root: wire.root,
146 config: wire.config,
147 created_at_millis: wire.created_at_millis,
148 updated_at_millis: wire.updated_at_millis,
149 })
150 }
151}
152
153impl From<Tree> for RootManifest {
154 fn from(tree: Tree) -> Self {
155 Self {
156 root: tree.root,
157 config: tree.config,
158 created_at_millis: None,
159 updated_at_millis: None,
160 }
161 }
162}
163
164impl From<&Tree> for RootManifest {
165 fn from(tree: &Tree) -> Self {
166 Self::from_tree(tree)
167 }
168}
169
170impl From<RootManifest> for Tree {
171 fn from(manifest: RootManifest) -> Self {
172 manifest.into_tree()
173 }
174}
175
176#[derive(Clone, Debug, PartialEq)]
178pub struct NamedRootManifest {
179 pub name: Vec<u8>,
181 pub manifest: RootManifest,
183}
184
185impl NamedRootManifest {
186 pub fn new(name: Vec<u8>, manifest: RootManifest) -> Self {
188 Self { name, manifest }
189 }
190
191 pub fn into_named_root(self) -> NamedRoot {
193 NamedRoot {
194 name: self.name,
195 tree: self.manifest.into_tree(),
196 }
197 }
198
199 pub fn to_named_root(&self) -> NamedRoot {
201 NamedRoot {
202 name: self.name.clone(),
203 tree: self.manifest.to_tree(),
204 }
205 }
206}
207
208#[derive(Clone, Debug, PartialEq)]
210pub struct NamedRoot {
211 pub name: Vec<u8>,
213 pub tree: Tree,
215}
216
217impl NamedRoot {
218 pub fn new(name: Vec<u8>, tree: Tree) -> Self {
220 Self { name, tree }
221 }
222}
223
224#[derive(Clone, Debug, Default, PartialEq)]
226pub struct NamedRootSelection {
227 pub roots: Vec<NamedRoot>,
229 pub missing_names: Vec<Vec<u8>>,
231}
232
233impl NamedRootSelection {
234 pub fn new(roots: Vec<NamedRoot>, missing_names: Vec<Vec<u8>>) -> Self {
236 Self {
237 roots,
238 missing_names,
239 }
240 }
241
242 pub fn is_complete(&self) -> bool {
244 self.missing_names.is_empty()
245 }
246
247 pub fn trees(&self) -> Vec<Tree> {
249 self.roots.iter().map(|root| root.tree.clone()).collect()
250 }
251
252 pub fn into_trees(self) -> Vec<Tree> {
254 self.roots.into_iter().map(|root| root.tree).collect()
255 }
256}
257
258#[derive(Clone, Debug, PartialEq)]
265pub enum NamedRootRetention {
266 All,
268 Exact {
270 names: Vec<Vec<u8>>,
272 },
273 Prefix {
275 prefix: Vec<u8>,
277 },
278 NewestByName {
280 prefix: Vec<u8>,
282 count: usize,
284 },
285 UpdatedSince {
287 prefix: Vec<u8>,
289 min_updated_at_millis: u64,
291 },
292}
293
294impl NamedRootRetention {
295 pub fn all() -> Self {
297 Self::All
298 }
299
300 pub fn exact<I, N>(names: I) -> Self
302 where
303 I: IntoIterator<Item = N>,
304 N: AsRef<[u8]>,
305 {
306 Self::Exact {
307 names: names
308 .into_iter()
309 .map(|name| name.as_ref().to_vec())
310 .collect(),
311 }
312 }
313
314 pub fn prefix(prefix: impl AsRef<[u8]>) -> Self {
316 Self::Prefix {
317 prefix: prefix.as_ref().to_vec(),
318 }
319 }
320
321 pub fn newest_by_name(prefix: impl AsRef<[u8]>, count: usize) -> Self {
323 Self::NewestByName {
324 prefix: prefix.as_ref().to_vec(),
325 count,
326 }
327 }
328
329 pub fn updated_since(prefix: impl AsRef<[u8]>, min_updated_at_millis: u64) -> Self {
331 Self::UpdatedSince {
332 prefix: prefix.as_ref().to_vec(),
333 min_updated_at_millis,
334 }
335 }
336
337 pub fn updated_within(prefix: impl AsRef<[u8]>, now_millis: u64, max_age: Duration) -> Self {
343 Self::updated_within_millis(prefix, now_millis, duration_millis_saturating(max_age))
344 }
345
346 pub fn updated_within_millis(
348 prefix: impl AsRef<[u8]>,
349 now_millis: u64,
350 window_millis: u64,
351 ) -> Self {
352 Self::updated_since(prefix, now_millis.saturating_sub(window_millis))
353 }
354
355 pub fn updated_within_days(prefix: impl AsRef<[u8]>, now_millis: u64, days: u64) -> Self {
361 Self::updated_within_millis(prefix, now_millis, days.saturating_mul(86_400_000))
362 }
363}
364
365fn duration_millis_saturating(duration: Duration) -> u64 {
366 duration.as_millis().min(u128::from(u64::MAX)) as u64
367}
368
369pub(crate) fn sort_named_root_manifests(roots: &mut [NamedRootManifest]) {
370 roots.sort_by(|left, right| left.name.cmp(&right.name));
371}
372
373#[derive(Clone, Debug, PartialEq)]
375#[allow(clippy::large_enum_variant)]
376pub enum ManifestUpdate {
377 Applied,
379 Conflict {
381 current: Option<RootManifest>,
383 },
384}
385
386impl ManifestUpdate {
387 pub fn is_applied(&self) -> bool {
389 matches!(self, Self::Applied)
390 }
391
392 pub fn is_conflict(&self) -> bool {
394 matches!(self, Self::Conflict { .. })
395 }
396
397 pub fn current(&self) -> Option<&RootManifest> {
399 match self {
400 Self::Applied => None,
401 Self::Conflict { current } => current.as_ref(),
402 }
403 }
404}
405
406#[derive(Clone, Debug, PartialEq)]
408#[allow(clippy::large_enum_variant)]
409pub enum NamedRootUpdate {
410 Applied,
412 Conflict {
414 current: Option<Tree>,
416 },
417}
418
419impl NamedRootUpdate {
420 pub fn is_applied(&self) -> bool {
422 matches!(self, Self::Applied)
423 }
424
425 pub fn is_conflict(&self) -> bool {
427 matches!(self, Self::Conflict { .. })
428 }
429
430 pub fn current(&self) -> Option<&Tree> {
432 match self {
433 Self::Applied => None,
434 Self::Conflict { current } => current.as_ref(),
435 }
436 }
437}
438
439impl From<ManifestUpdate> for NamedRootUpdate {
440 fn from(update: ManifestUpdate) -> Self {
441 match update {
442 ManifestUpdate::Applied => Self::Applied,
443 ManifestUpdate::Conflict { current } => Self::Conflict {
444 current: current.map(RootManifest::into_tree),
445 },
446 }
447 }
448}
449
450pub trait ManifestStore: Send + Sync {
456 type Error: std::error::Error + Send + Sync + 'static;
458
459 fn get_root(&self, name: &[u8]) -> Result<Option<RootManifest>, Self::Error>;
461
462 fn put_root(&self, name: &[u8], manifest: &RootManifest) -> Result<(), Self::Error>;
464
465 fn delete_root(&self, name: &[u8]) -> Result<(), Self::Error>;
467
468 fn compare_and_swap_root(
474 &self,
475 name: &[u8],
476 expected: Option<&RootManifest>,
477 new: Option<&RootManifest>,
478 ) -> Result<ManifestUpdate, Self::Error>;
479}
480
481pub trait ManifestStoreScan: ManifestStore {
488 fn list_roots(&self) -> Result<Vec<NamedRootManifest>, Self::Error>;
490}
491
492#[cfg(feature = "async-store")]
499#[allow(async_fn_in_trait)]
500pub trait AsyncManifestStore {
501 type Error: std::error::Error + 'static;
503
504 async fn get_root(&self, name: &[u8]) -> Result<Option<RootManifest>, Self::Error>;
506
507 async fn put_root(&self, name: &[u8], manifest: &RootManifest) -> Result<(), Self::Error>;
509
510 async fn delete_root(&self, name: &[u8]) -> Result<(), Self::Error>;
512
513 async fn compare_and_swap_root(
519 &self,
520 name: &[u8],
521 expected: Option<&RootManifest>,
522 new: Option<&RootManifest>,
523 ) -> Result<ManifestUpdate, Self::Error>;
524}
525
526#[cfg(feature = "async-store")]
531#[allow(async_fn_in_trait)]
532pub trait AsyncManifestStoreScan: AsyncManifestStore {
533 async fn list_roots(&self) -> Result<Vec<NamedRootManifest>, Self::Error>;
535}
536
537impl<T: ManifestStore> ManifestStore for Arc<T> {
538 type Error = T::Error;
539
540 fn get_root(&self, name: &[u8]) -> Result<Option<RootManifest>, Self::Error> {
541 (**self).get_root(name)
542 }
543
544 fn put_root(&self, name: &[u8], manifest: &RootManifest) -> Result<(), Self::Error> {
545 (**self).put_root(name, manifest)
546 }
547
548 fn delete_root(&self, name: &[u8]) -> Result<(), Self::Error> {
549 (**self).delete_root(name)
550 }
551
552 fn compare_and_swap_root(
553 &self,
554 name: &[u8],
555 expected: Option<&RootManifest>,
556 new: Option<&RootManifest>,
557 ) -> Result<ManifestUpdate, Self::Error> {
558 (**self).compare_and_swap_root(name, expected, new)
559 }
560}
561
562impl<T: ManifestStoreScan> ManifestStoreScan for Arc<T> {
563 fn list_roots(&self) -> Result<Vec<NamedRootManifest>, Self::Error> {
564 (**self).list_roots()
565 }
566}
567
568#[cfg(feature = "async-store")]
569impl<T: AsyncManifestStore> AsyncManifestStore for Arc<T> {
570 type Error = T::Error;
571
572 async fn get_root(&self, name: &[u8]) -> Result<Option<RootManifest>, Self::Error> {
573 (**self).get_root(name).await
574 }
575
576 async fn put_root(&self, name: &[u8], manifest: &RootManifest) -> Result<(), Self::Error> {
577 (**self).put_root(name, manifest).await
578 }
579
580 async fn delete_root(&self, name: &[u8]) -> Result<(), Self::Error> {
581 (**self).delete_root(name).await
582 }
583
584 async fn compare_and_swap_root(
585 &self,
586 name: &[u8],
587 expected: Option<&RootManifest>,
588 new: Option<&RootManifest>,
589 ) -> Result<ManifestUpdate, Self::Error> {
590 (**self).compare_and_swap_root(name, expected, new).await
591 }
592}
593
594#[cfg(feature = "async-store")]
595impl<T: AsyncManifestStoreScan> AsyncManifestStoreScan for Arc<T> {
596 async fn list_roots(&self) -> Result<Vec<NamedRootManifest>, Self::Error> {
597 (**self).list_roots().await
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604 use crate::{Config, Encoding};
605 use std::time::Duration;
606
607 #[derive(Serialize)]
608 struct LegacyRootManifestWire {
609 version: u64,
610 root: Option<Cid>,
611 config: Config,
612 }
613
614 #[test]
615 fn manifest_round_trips_through_bytes() {
616 let cid = Cid::from_bytes(b"root");
617 let manifest = RootManifest::new(
618 Some(cid),
619 Config::builder()
620 .min_chunk_size(2)
621 .max_chunk_size(8)
622 .chunking_factor(4)
623 .hash_seed(99)
624 .encoding(Encoding::Json)
625 .node_cache_max_nodes(128)
626 .build(),
627 )
628 .with_timestamps_millis(Some(100), Some(200));
629
630 let bytes = manifest.to_bytes().unwrap();
631 assert_eq!(RootManifest::from_bytes(&bytes).unwrap(), manifest);
632 }
633
634 #[test]
635 fn manifest_reads_legacy_bytes_without_timestamps() {
636 let legacy = LegacyRootManifestWire {
637 version: ROOT_MANIFEST_VERSION,
638 root: Some(Cid::from_bytes(b"legacy-root")),
639 config: Config::default(),
640 };
641 let bytes = serde_cbor::ser::to_vec_packed(&legacy).unwrap();
642 let manifest = RootManifest::from_bytes(&bytes).unwrap();
643
644 assert_eq!(manifest.root, legacy.root);
645 assert_eq!(manifest.config, legacy.config);
646 assert_eq!(manifest.created_at_millis, None);
647 assert_eq!(manifest.updated_at_millis, None);
648 }
649
650 #[test]
651 fn manifest_converts_to_and_from_tree() {
652 let tree = Tree {
653 root: Some(Cid::from_bytes(b"root")),
654 config: Config::default(),
655 };
656
657 let manifest = RootManifest::from_tree(&tree);
658 assert_eq!(manifest.to_tree(), tree);
659 assert_eq!(Tree::from(manifest), tree);
660 }
661
662 #[test]
663 fn named_root_update_converts_conflict_to_tree() {
664 let tree = Tree {
665 root: Some(Cid::from_bytes(b"root")),
666 config: Config::default(),
667 };
668 let update = ManifestUpdate::Conflict {
669 current: Some(RootManifest::from_tree(&tree)),
670 };
671
672 assert_eq!(
673 NamedRootUpdate::from(update),
674 NamedRootUpdate::Conflict {
675 current: Some(tree)
676 }
677 );
678 }
679
680 #[test]
681 fn retention_duration_helpers_build_cutoffs() {
682 assert_eq!(
683 NamedRootRetention::updated_within(b"checkpoint/", 1_000, Duration::from_millis(250)),
684 NamedRootRetention::updated_since(b"checkpoint/", 750)
685 );
686 assert_eq!(
687 NamedRootRetention::updated_within_millis(b"checkpoint/", 100, 250),
688 NamedRootRetention::updated_since(b"checkpoint/", 0)
689 );
690 assert_eq!(
691 NamedRootRetention::updated_within_days(b"checkpoint/", 172_800_050, 1),
692 NamedRootRetention::updated_since(b"checkpoint/", 86_400_050)
693 );
694 assert_eq!(
695 NamedRootRetention::updated_within_days(b"checkpoint/", 42, u64::MAX),
696 NamedRootRetention::updated_since(b"checkpoint/", 0)
697 );
698 }
699}