Skip to main content

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}