Skip to main content

persistant_blob/
backend.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use std::fmt;
3use std::future::Future;
4
5/// Why a blob operation failed. Plain data, no boxed payloads.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum BlobError {
9    /// The backend or its driver reported a failure; the text is the driver's.
10    Backend(String),
11    /// The backend cannot perform this operation as its contract requires, and says so rather
12    /// than approximating (for example an exclusive create that is check-then-write).
13    Unsupported(String),
14}
15
16impl fmt::Display for BlobError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            BlobError::Backend(m) => write!(f, "blob backend: {m}"),
20            BlobError::Unsupported(m) => write!(f, "blob backend does not support: {m}"),
21        }
22    }
23}
24
25impl std::error::Error for BlobError {}
26
27/// What is stored at one key.
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[non_exhaustive]
30pub struct Entry {
31    /// The key.
32    pub key: String,
33    /// Size of the value in bytes.
34    pub len: u64,
35}
36
37impl Entry {
38    /// An entry for `key` holding `len` bytes.
39    pub fn new(key: impl Into<String>, len: u64) -> Self {
40        Entry {
41            key: key.into(),
42            len,
43        }
44    }
45}
46
47/// One page of a listing.
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[non_exhaustive]
50pub struct Page {
51    /// At most `limit` entries.
52    pub entries: Vec<Entry>,
53    /// `Some(cursor)`: pass it as `after` for the next page. `None`: the listing is complete.
54    pub next: Option<String>,
55}
56
57impl Page {
58    /// A page of `entries` with an optional continuation.
59    pub fn new(entries: Vec<Entry>, next: Option<String>) -> Self {
60        Page { entries, next }
61    }
62}
63
64/// A blob store: bytes by key.
65///
66/// Small on purpose. Every method is a primitive a capability's contract is written against; a
67/// method a backend cannot honour must fail with [`BlobError`] rather than approximate, and the
68/// capability stays off because its test did not pass. Missing keys are `Ok(None)`, not errors.
69pub trait BlobBackend: Send + Sync {
70    /// The backend's name as it appears in matrix rows (`memory`, `fs`, `s3`).
71    fn backend(&self) -> &str;
72
73    /// The backend's version string as it appears in matrix rows.
74    fn version(&self) -> &str;
75
76    /// The value at `key`, or `None` if absent.
77    fn read(&self, key: &str) -> impl Future<Output = Result<Option<Vec<u8>>, BlobError>> + Send;
78
79    /// Store `bytes` at `key`, replacing any earlier value. Atomicity is **not** implied; that is
80    /// `blob.replace_atomic`.
81    fn write(
82        &self,
83        key: &str,
84        bytes: Vec<u8>,
85    ) -> impl Future<Output = Result<(), BlobError>> + Send;
86
87    /// Remove `key`. Removing an absent key is `Ok`.
88    fn delete(&self, key: &str) -> impl Future<Output = Result<(), BlobError>> + Send;
89
90    /// The entry at `key` without reading its value, or `None` if absent.
91    fn stat(&self, key: &str) -> impl Future<Output = Result<Option<Entry>, BlobError>> + Send;
92
93    /// Up to `limit` entries whose key starts with the literal `prefix`, in ascending bytewise
94    /// key order, strictly after `after` if given.
95    fn list(
96        &self,
97        prefix: &str,
98        after: Option<&str>,
99        limit: usize,
100    ) -> impl Future<Output = Result<Page, BlobError>> + Send;
101
102    /// Write `bytes` at `key` only if nothing is there. `Ok(true)` created it, `Ok(false)` found
103    /// a value and changed nothing. Of any number of concurrent callers for one key, exactly one
104    /// gets `Ok(true)` (`blob.create_exclusive`). A backend that cannot promise that returns
105    /// [`BlobError::Unsupported`]; the default does.
106    fn create_exclusive(
107        &self,
108        key: &str,
109        bytes: Vec<u8>,
110    ) -> impl Future<Output = Result<bool, BlobError>> + Send {
111        let _ = (key, bytes);
112        async { Err(BlobError::Unsupported("create_exclusive".to_string())) }
113    }
114
115    /// Replace the value at `key` so that a concurrent reader sees the whole old value or the
116    /// whole new one, never a mix (`blob.replace_atomic.concurrent_read`). A backend that cannot
117    /// promise that returns [`BlobError::Unsupported`]; the default does.
118    fn replace_atomic(
119        &self,
120        key: &str,
121        bytes: Vec<u8>,
122    ) -> impl Future<Output = Result<(), BlobError>> + Send {
123        let _ = (key, bytes);
124        async { Err(BlobError::Unsupported("replace_atomic".to_string())) }
125    }
126
127    /// Add `bytes` after the existing bytes of `key`, in call order (`blob.append`). Of
128    /// concurrent calls, each call's bytes arrive whole and exactly once
129    /// (`blob.append.concurrent_whole`). A backend that cannot promise that returns
130    /// [`BlobError::Unsupported`]; the default does.
131    fn append(
132        &self,
133        key: &str,
134        bytes: Vec<u8>,
135    ) -> impl Future<Output = Result<(), BlobError>> + Send {
136        let _ = (key, bytes);
137        async { Err(BlobError::Unsupported("append".to_string())) }
138    }
139}