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}