common/bucket_log/provider.rs
1use std::fmt::{Debug, Display};
2
3use async_trait::async_trait;
4use uuid::Uuid;
5
6use crate::linked_data::Link;
7
8// TODO (amiller68): it might be easier to design this to work
9// with dependency injection over a generic type
10
11#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
12pub enum BucketLogError<T> {
13 /// The bucket log is empty
14 #[error("unhandled bucket log provider error: {0}")]
15 Provider(#[from] T),
16 /// The bucket log is empty
17 #[error("head not found at height {0}")]
18 HeadNotFound(u64),
19 /// An append causes a conflict with the current of the
20 /// log i.e. same link at the same height
21 #[error("conflict with current log entry")]
22 Conflict,
23 /// An append does not implement a valid link structure
24 /// st the previous link pointed at by the new log does
25 /// not exist in the log at the expected height --
26 /// current, previous, height
27 #[error("invalid append: {0}, {1}, {2}")]
28 InvalidAppend(Link, Link, u64),
29}
30
31#[async_trait]
32pub trait BucketLogProvider: Send + Sync + std::fmt::Debug + Clone + 'static {
33 type Error: Display + Debug;
34
35 async fn exists(&self, id: Uuid) -> Result<bool, BucketLogError<Self::Error>>;
36
37 /// Get the possible heads for a bucket
38 /// based on passed height
39 ///
40 /// # Arguments
41 /// * `id` - The UUID of the bucket
42 /// * `height` - The height to query the candidate heads for
43 ///
44 /// # Returns
45 /// * `Ok(Vec<Link>)` - The candidate heads for the bucket
46 /// * `Err(Self::Error)` - An error occurred while fetching the candidate heads
47 async fn heads(&self, id: Uuid, height: u64) -> Result<Vec<Link>, BucketLogError<Self::Error>>;
48
49 // NOTE (amiller68): maybe name is more of a
50 // implementation detail or product concern,
51 // but maybe its not such thing to mandate a
52 // cache for.
53 /// Append a version of the bucket to the log
54 ///
55 /// # Arguments
56 /// * `id` - The UUID of the bucket
57 /// * `name` - The friendly name for the bucket
58 /// * `current` - The current link of the record
59 /// * `previous` - The previous link of the record
60 /// * `height` - The reported depth of the bucket version within the chain
61 /// * `published` - Whether this version is published (mirrors can decrypt)
62 ///
63 /// Should fail with the following errors to be considered
64 /// correct:
65 /// * `Err(BucketLogError::Conflict)` - The append causes a conflict with the current log
66 /// * `Err(BucketLogError::InvalidHeight)` - The height is not greater than the previous height
67 async fn append(
68 &self,
69 id: Uuid,
70 name: String,
71 current: Link,
72 // NOTE (amiller68): this should *only*
73 // be null for the genesis of a bucket
74 previous: Option<Link>,
75 height: u64,
76 published: bool,
77 ) -> Result<(), BucketLogError<Self::Error>>;
78
79 /// Return the greatest height of the bucket version within the chain
80 ///
81 /// # Arguments
82 /// * `id` - The UUID of the bucket
83 ///
84 /// # Returns
85 /// * `Result<u64, BucketLogError<Self::Error>>` - The height of the bucket version within the chain
86 ///
87 /// NOTE: while this returns a BucketLogError, it should only ever return a BucketLogError::NotFound
88 /// or ProviderError
89 async fn height(&self, id: Uuid) -> Result<u64, BucketLogError<Self::Error>>;
90
91 /// Check if a link exists within a bucket
92 ///
93 /// # Arguments
94 /// * `id` - The UUID of the bucket
95 /// * `link` - The link to check for existence as current
96 ///
97 /// # Returns
98 /// * `Result<Vec<u64>, BucketLogError<Self::Error>>`
99 /// The heights at which the link exists within the bucket
100 async fn has(&self, id: Uuid, link: Link) -> Result<Vec<u64>, BucketLogError<Self::Error>>;
101
102 /// Get the peers canonical head based on its log entries
103 async fn head(
104 &self,
105 id: Uuid,
106 height: Option<u64>,
107 ) -> Result<(Link, u64), BucketLogError<Self::Error>> {
108 let height = height.unwrap_or(self.height(id).await?);
109 let heads = self.heads(id, height).await?;
110 Ok((
111 heads
112 .into_iter()
113 .max()
114 .ok_or(BucketLogError::HeadNotFound(height))?,
115 height,
116 ))
117 }
118
119 /// List all bucket IDs that have log entries
120 ///
121 /// # Returns
122 /// * `Ok(Vec<Uuid>)` - The list of bucket IDs
123 /// * `Err(BucketLogError)` - An error occurred while fetching bucket IDs
124 async fn list_buckets(&self) -> Result<Vec<Uuid>, BucketLogError<Self::Error>>;
125
126 /// Get the latest published version of a bucket
127 ///
128 /// # Arguments
129 /// * `id` - The UUID of the bucket
130 ///
131 /// # Returns
132 /// * `Ok(Some((link, height)))` - The latest published version's link and height
133 /// * `Ok(None)` - No published version exists
134 /// * `Err(BucketLogError)` - An error occurred while fetching
135 async fn latest_published(
136 &self,
137 id: Uuid,
138 ) -> Result<Option<(Link, u64)>, BucketLogError<Self::Error>>;
139}