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    ///
62    /// Should fail with the following errors to be considered
63    ///  correct:
64    /// * `Err(BucketLogError::Conflict)` - The append causes a conflict with the current log
65    /// * `Err(BucketLogError::InvalidHeight)` - The height is not greater than the previous height
66    async fn append(
67        &self,
68        id: Uuid,
69        name: String,
70        current: Link,
71        // NOTE (amiller68): this should *only*
72        //  be null for the genesis of a bucket
73        previous: Option<Link>,
74        height: u64,
75    ) -> Result<(), BucketLogError<Self::Error>>;
76
77    /// Return the greatest height of the bucket version within the chain
78    ///
79    /// # Arguments
80    /// * `id` - The UUID of the bucket
81    ///
82    /// # Returns
83    /// * `Result<u64, BucketLogError<Self::Error>>` - The height of the bucket version within the chain
84    ///
85    /// NOTE: while this returns a BucketLogError, it should only ever return a BucketLogError::NotFound
86    ///  or ProviderError
87    async fn height(&self, id: Uuid) -> Result<u64, BucketLogError<Self::Error>>;
88
89    /// Check if a link exists within a bucket
90    ///
91    /// # Arguments
92    /// * `id` - The UUID of the bucket
93    /// * `link` - The link to check for existence as current
94    ///
95    /// # Returns
96    /// * `Result<Vec<u64>, BucketLogError<Self::Error>>`
97    ///     The heights at which the link exists within the bucket
98    async fn has(&self, id: Uuid, link: Link) -> Result<Vec<u64>, BucketLogError<Self::Error>>;
99
100    /// Get the peers canonical head based on its log entries
101    async fn head(
102        &self,
103        id: Uuid,
104        height: Option<u64>,
105    ) -> Result<(Link, u64), BucketLogError<Self::Error>> {
106        let height = height.unwrap_or(self.height(id).await?);
107        let heads = self.heads(id, height).await?;
108        Ok((
109            heads
110                .into_iter()
111                .max()
112                .ok_or(BucketLogError::HeadNotFound(height))?,
113            height,
114        ))
115    }
116
117    /// List all bucket IDs that have log entries
118    ///
119    /// # Returns
120    /// * `Ok(Vec<Uuid>)` - The list of bucket IDs
121    /// * `Err(BucketLogError)` - An error occurred while fetching bucket IDs
122    async fn list_buckets(&self) -> Result<Vec<Uuid>, BucketLogError<Self::Error>>;
123}