iroh_docs/
store.rs

1//! Storage trait and implementation for iroh-docs documents
2use std::num::NonZeroUsize;
3
4use anyhow::Result;
5use bytes::Bytes;
6use serde::{Deserialize, Serialize};
7
8use crate::{AuthorId, Entry, NamespaceId};
9
10pub mod fs;
11mod pubkeys;
12mod util;
13pub use fs::Store;
14pub use pubkeys::*;
15
16/// Number of peers to cache per document.
17pub(crate) const PEERS_PER_DOC_CACHE_SIZE: NonZeroUsize = match NonZeroUsize::new(5) {
18    Some(val) => val,
19    None => panic!("this is clearly non zero"),
20};
21
22/// Error return from [`Store::open_replica`]
23#[derive(Debug, thiserror::Error)]
24pub enum OpenError {
25    /// The replica does not exist.
26    #[error("Replica not found")]
27    NotFound,
28    /// Other error while opening the replica.
29    #[error("{0}")]
30    Other(#[from] anyhow::Error),
31}
32
33/// Store that gives read access to download policies for a document.
34pub trait DownloadPolicyStore {
35    /// Get the download policy for a document.
36    fn get_download_policy(&mut self, namespace: &NamespaceId) -> Result<DownloadPolicy>;
37}
38
39impl<T: DownloadPolicyStore> DownloadPolicyStore for &mut T {
40    fn get_download_policy(&mut self, namespace: &NamespaceId) -> Result<DownloadPolicy> {
41        DownloadPolicyStore::get_download_policy(*self, namespace)
42    }
43}
44
45impl DownloadPolicyStore for crate::store::Store {
46    fn get_download_policy(&mut self, namespace: &NamespaceId) -> Result<DownloadPolicy> {
47        self.get_download_policy(namespace)
48    }
49}
50
51/// Outcome of [`Store::import_namespace`]
52#[derive(Debug, Clone, Copy)]
53pub enum ImportNamespaceOutcome {
54    /// The namespace did not exist before and is now inserted.
55    Inserted,
56    /// The namespace existed and now has an upgraded capability.
57    Upgraded,
58    /// The namespace existed and its capability remains unchanged.
59    NoChange,
60}
61
62/// Download policy to decide which content blobs shall be downloaded.
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
64pub enum DownloadPolicy {
65    /// Do not download any key unless it matches one of the filters.
66    NothingExcept(Vec<FilterKind>),
67    /// Download every key unless it matches one of the filters.
68    EverythingExcept(Vec<FilterKind>),
69}
70
71impl Default for DownloadPolicy {
72    fn default() -> Self {
73        DownloadPolicy::EverythingExcept(Vec::default())
74    }
75}
76
77/// Filter strategy used in download policies.
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79pub enum FilterKind {
80    /// Matches if the contained bytes are a prefix of the key.
81    Prefix(Bytes),
82    /// Matches if the contained bytes and the key are the same.
83    Exact(Bytes),
84}
85
86impl std::fmt::Display for FilterKind {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        // hardly usable but good enough as a poc
89        let (kind, bytes) = match self {
90            FilterKind::Prefix(bytes) => ("prefix", bytes),
91            FilterKind::Exact(bytes) => ("exact", bytes),
92        };
93        let (encoding, repr) = match String::from_utf8(bytes.to_vec()) {
94            Ok(repr) => ("utf8", repr),
95            Err(_) => ("hex", hex::encode(bytes)),
96        };
97        write!(f, "{kind}:{encoding}:{repr}")
98    }
99}
100
101impl std::str::FromStr for FilterKind {
102    type Err = anyhow::Error;
103
104    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
105        let Some((kind, rest)) = s.split_once(':') else {
106            anyhow::bail!("missing filter kind, either \"prefix:\" or \"exact:\"")
107        };
108        let Some((encoding, rest)) = rest.split_once(':') else {
109            anyhow::bail!("missing encoding: either \"hex:\" or \"utf8:\"")
110        };
111
112        let is_exact = match kind {
113            "exact" => true,
114            "prefix" => false,
115            other => {
116                anyhow::bail!("expected filter kind \"prefix:\" or \"exact:\", found {other}")
117            }
118        };
119
120        let decoded = match encoding {
121            "utf8" => Bytes::from(rest.to_owned()),
122            "hex" => match hex::decode(rest) {
123                Ok(bytes) => Bytes::from(bytes),
124                Err(_) => anyhow::bail!("failed to decode hex"),
125            },
126            other => {
127                anyhow::bail!("expected encoding: either \"hex:\" or \"utf8:\", found {other}")
128            }
129        };
130
131        if is_exact {
132            Ok(FilterKind::Exact(decoded))
133        } else {
134            Ok(FilterKind::Prefix(decoded))
135        }
136    }
137}
138
139impl FilterKind {
140    /// Verifies whether this filter matches a given key
141    pub fn matches(&self, key: impl AsRef<[u8]>) -> bool {
142        match self {
143            FilterKind::Prefix(prefix) => key.as_ref().starts_with(prefix),
144            FilterKind::Exact(expected) => expected == key.as_ref(),
145        }
146    }
147}
148
149impl DownloadPolicy {
150    /// Check if an entry should be downloaded according to this policy.
151    pub fn matches(&self, entry: &Entry) -> bool {
152        let key = entry.key();
153        match self {
154            DownloadPolicy::NothingExcept(patterns) => {
155                patterns.iter().any(|pattern| pattern.matches(key))
156            }
157            DownloadPolicy::EverythingExcept(patterns) => {
158                patterns.iter().all(|pattern| !pattern.matches(key))
159            }
160        }
161    }
162}
163
164/// A query builder for document queries.
165#[derive(Debug, Default)]
166pub struct QueryBuilder<K> {
167    kind: K,
168    filter_author: AuthorFilter,
169    filter_key: KeyFilter,
170    limit: Option<u64>,
171    offset: u64,
172    include_empty: bool,
173    sort_direction: SortDirection,
174}
175
176impl<K> QueryBuilder<K> {
177    /// Call to include empty entries (deletion markers).
178    pub fn include_empty(mut self) -> Self {
179        self.include_empty = true;
180        self
181    }
182    /// Filter by exact key match.
183    pub fn key_exact(mut self, key: impl AsRef<[u8]>) -> Self {
184        self.filter_key = KeyFilter::Exact(key.as_ref().to_vec().into());
185        self
186    }
187    /// Filter by key prefix.
188    pub fn key_prefix(mut self, key: impl AsRef<[u8]>) -> Self {
189        self.filter_key = KeyFilter::Prefix(key.as_ref().to_vec().into());
190        self
191    }
192    /// Filter by author.
193    pub fn author(mut self, author: AuthorId) -> Self {
194        self.filter_author = AuthorFilter::Exact(author);
195        self
196    }
197    /// Set the maximum number of entries to be returned.
198    pub fn limit(mut self, limit: u64) -> Self {
199        self.limit = Some(limit);
200        self
201    }
202    /// Set the offset within the result set from where to start returning results.
203    pub fn offset(mut self, offset: u64) -> Self {
204        self.offset = offset;
205        self
206    }
207}
208
209/// Query on all entries without aggregation.
210#[derive(Debug, Clone, Default, Serialize, Deserialize)]
211pub struct FlatQuery {
212    sort_by: SortBy,
213}
214
215/// Query that only returns the latest entry for a key which has entries from multiple authors.
216#[derive(Debug, Clone, Default, Serialize, Deserialize)]
217pub struct SingleLatestPerKeyQuery {}
218
219impl QueryBuilder<FlatQuery> {
220    /// Set the sort for the query.
221    ///
222    /// The default is to sort by author, then by key, in ascending order.
223    pub fn sort_by(mut self, sort_by: SortBy, direction: SortDirection) -> Self {
224        self.kind.sort_by = sort_by;
225        self.sort_direction = direction;
226        self
227    }
228
229    /// Build the query.
230    pub fn build(self) -> Query {
231        Query::from(self)
232    }
233}
234
235impl QueryBuilder<SingleLatestPerKeyQuery> {
236    /// Set the order direction for the query.
237    ///
238    /// Ordering is always by key for this query type.
239    /// Default direction is ascending.
240    pub fn sort_direction(mut self, direction: SortDirection) -> Self {
241        self.sort_direction = direction;
242        self
243    }
244
245    /// Build the query.
246    pub fn build(self) -> Query {
247        Query::from(self)
248    }
249}
250
251impl From<QueryBuilder<SingleLatestPerKeyQuery>> for Query {
252    fn from(builder: QueryBuilder<SingleLatestPerKeyQuery>) -> Query {
253        Query {
254            kind: QueryKind::SingleLatestPerKey(builder.kind),
255            filter_author: builder.filter_author,
256            filter_key: builder.filter_key,
257            limit: builder.limit,
258            offset: builder.offset,
259            include_empty: builder.include_empty,
260            sort_direction: builder.sort_direction,
261        }
262    }
263}
264
265impl From<QueryBuilder<FlatQuery>> for Query {
266    fn from(builder: QueryBuilder<FlatQuery>) -> Query {
267        Query {
268            kind: QueryKind::Flat(builder.kind),
269            filter_author: builder.filter_author,
270            filter_key: builder.filter_key,
271            limit: builder.limit,
272            offset: builder.offset,
273            include_empty: builder.include_empty,
274            sort_direction: builder.sort_direction,
275        }
276    }
277}
278
279/// Note: When using the `SingleLatestPerKey` query kind, the key filter is applied *before* the
280/// grouping, the author filter is applied *after* the grouping.
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct Query {
283    kind: QueryKind,
284    filter_author: AuthorFilter,
285    filter_key: KeyFilter,
286    limit: Option<u64>,
287    offset: u64,
288    include_empty: bool,
289    sort_direction: SortDirection,
290}
291
292impl Query {
293    /// Query all records.
294    pub fn all() -> QueryBuilder<FlatQuery> {
295        Default::default()
296    }
297    /// Query only the latest entry for each key, omitting older entries if the entry was written
298    /// to by multiple authors.
299    pub fn single_latest_per_key() -> QueryBuilder<SingleLatestPerKeyQuery> {
300        Default::default()
301    }
302
303    /// Create a [`Query::all`] query filtered by a single author.
304    pub fn author(author: AuthorId) -> QueryBuilder<FlatQuery> {
305        Self::all().author(author)
306    }
307
308    /// Create a [`Query::all`] query filtered by a single key.
309    pub fn key_exact(key: impl AsRef<[u8]>) -> QueryBuilder<FlatQuery> {
310        Self::all().key_exact(key)
311    }
312
313    /// Create a [`Query::all`] query filtered by a key prefix.
314    pub fn key_prefix(prefix: impl AsRef<[u8]>) -> QueryBuilder<FlatQuery> {
315        Self::all().key_prefix(prefix)
316    }
317
318    /// Get the limit for this query (max. number of entries to emit).
319    pub fn limit(&self) -> Option<u64> {
320        self.limit
321    }
322
323    /// Get the offset for this query (number of entries to skip at the beginning).
324    pub fn offset(&self) -> u64 {
325        self.offset
326    }
327}
328
329/// Sort direction
330#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
331pub enum SortDirection {
332    /// Sort ascending
333    #[default]
334    Asc,
335    /// Sort descending
336    Desc,
337}
338
339#[derive(derive_more::Debug, Clone, Serialize, Deserialize)]
340enum QueryKind {
341    #[debug("Flat {{ sort_by: {:?}}}", _0)]
342    Flat(FlatQuery),
343    #[debug("SingleLatestPerKey")]
344    SingleLatestPerKey(SingleLatestPerKeyQuery),
345}
346
347/// Fields by which the query can be sorted
348#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
349pub enum SortBy {
350    /// Sort by key, then author.
351    KeyAuthor,
352    /// Sort by author, then key.
353    #[default]
354    AuthorKey,
355}
356
357/// Key matching.
358#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq)]
359pub enum KeyFilter {
360    /// Matches any key.
361    #[default]
362    Any,
363    /// Only keys that are exactly the provided value.
364    Exact(Bytes),
365    /// All keys that start with the provided value.
366    Prefix(Bytes),
367}
368
369impl<T: AsRef<[u8]>> From<T> for KeyFilter {
370    fn from(value: T) -> Self {
371        KeyFilter::Exact(Bytes::copy_from_slice(value.as_ref()))
372    }
373}
374
375impl KeyFilter {
376    /// Test if a key is matched by this [`KeyFilter`].
377    pub fn matches(&self, key: &[u8]) -> bool {
378        match self {
379            Self::Any => true,
380            Self::Exact(k) => &k[..] == key,
381            Self::Prefix(p) => key.starts_with(p),
382        }
383    }
384}
385
386/// Author matching.
387#[derive(Debug, Serialize, Deserialize, Clone, Default, Eq, PartialEq)]
388pub enum AuthorFilter {
389    /// Matches any author.
390    #[default]
391    Any,
392    /// Matches exactly the provided author.
393    Exact(AuthorId),
394}
395
396impl AuthorFilter {
397    /// Test if an author is matched by this [`AuthorFilter`].
398    pub fn matches(&self, author: &AuthorId) -> bool {
399        match self {
400            Self::Any => true,
401            Self::Exact(a) => a == author,
402        }
403    }
404}
405
406impl From<AuthorId> for AuthorFilter {
407    fn from(value: AuthorId) -> Self {
408        AuthorFilter::Exact(value)
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    #[test]
417    fn test_filter_kind_encode_decode() {
418        const REPR: &str = "prefix:utf8:memes/futurama";
419        let filter: FilterKind = REPR.parse().expect("should decode");
420        assert_eq!(
421            filter,
422            FilterKind::Prefix(Bytes::from(String::from("memes/futurama")))
423        );
424        assert_eq!(filter.to_string(), REPR)
425    }
426}