mas_storage/
lib.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7//! Interactions with the storage backend
8//!
9//! This crate provides a set of traits that can be implemented to interact with
10//! the storage backend. Those traits are called repositories and are grouped by
11//! the type of data they manage.
12//!
13//! Each of those reposotories can be accessed via the [`RepositoryAccess`]
14//! trait. This trait can be wrapped in a [`BoxRepository`] to allow using it
15//! without caring about the underlying storage backend, and without carrying
16//! around the generic type parameter.
17//!
18//! This crate also defines a [`Clock`] trait that can be used to abstract the
19//! way the current time is retrieved. It has two implementation:
20//! [`SystemClock`] that uses the system time and [`MockClock`] which is useful
21//! for testing.
22//!
23//! [`MockClock`]: crate::clock::MockClock
24//!
25//! # Defining a new repository
26//!
27//! To define a new repository, you have to:
28//!   1. Define a new (async) repository trait, with the methods you need
29//!   2. Write an implementation of this trait for each storage backend you want
30//!      (currently only for [`mas-storage-pg`])
31//!   3. Make it accessible via the [`RepositoryAccess`] trait
32//!
33//! The repository trait definition should look like this:
34//!
35//! ```ignore
36//! #[async_trait]
37//! pub trait FakeDataRepository: Send + Sync {
38//!     /// The error type returned by the repository
39//!     type Error;
40//!
41//!     /// Lookup a [`FakeData`] by its ID
42//!     ///
43//!     /// Returns `None` if no [`FakeData`] was found
44//!     ///
45//!     /// # Parameters
46//!     ///
47//!     /// * `id`: The ID of the [`FakeData`] to lookup
48//!     ///
49//!     /// # Errors
50//!     ///
51//!     /// Returns [`Self::Error`] if the underlying repository fails
52//!     async fn lookup(&mut self, id: Ulid) -> Result<Option<FakeData>, Self::Error>;
53//!
54//!     /// Create a new [`FakeData`]
55//!     ///
56//!     /// Returns the newly-created [`FakeData`].
57//!     ///
58//!     /// # Parameters
59//!     ///
60//!     /// * `rng`: The random number generator to use
61//!     /// * `clock`: The clock used to generate timestamps
62//!     ///
63//!     /// # Errors
64//!     ///
65//!     /// Returns [`Self::Error`] if the underlying repository fails
66//!     async fn add(
67//!         &mut self,
68//!         rng: &mut (dyn RngCore + Send),
69//!         clock: &dyn Clock,
70//!     ) -> Result<FakeData, Self::Error>;
71//! }
72//!
73//! repository_impl!(FakeDataRepository:
74//!     async fn lookup(&mut self, id: Ulid) -> Result<Option<FakeData>, Self::Error>;
75//!     async fn add(
76//!         &mut self,
77//!         rng: &mut (dyn RngCore + Send),
78//!         clock: &dyn Clock,
79//!     ) -> Result<FakeData, Self::Error>;
80//! );
81//! ```
82//!
83//! Four things to note with the implementation:
84//!
85//!   1. It defined an assocated error type, and all functions are faillible,
86//!      and use that error type
87//!   2. Lookups return an `Result<Option<T>, Self::Error>`, because 'not found'
88//!      errors are usually cases that are handled differently
89//!   3. Operations that need to record the current type use a [`Clock`]
90//!      parameter. Operations that need to generate new IDs also use a random
91//!      number generator.
92//!   4. All the methods use an `&mut self`. This is ensures only one operation
93//!      is done at a time on a single repository instance.
94//!
95//! Then update the [`RepositoryAccess`] trait to make the new repository
96//! available:
97//!
98//! ```ignore
99//! /// Access the various repositories the backend implements.
100//! pub trait RepositoryAccess: Send {
101//!     /// The backend-specific error type used by each repository.
102//!     type Error: std::error::Error + Send + Sync + 'static;
103//!
104//!     // ...other repositories...
105//!
106//!     /// Get a [`FakeDataRepository`]
107//!     fn fake_data<'c>(&'c mut self) -> Box<dyn FakeDataRepository<Error = Self::Error> + 'c>;
108//! }
109//! ```
110
111#![deny(clippy::future_not_send, missing_docs)]
112#![allow(clippy::module_name_repetitions)]
113
114pub mod clock;
115pub mod pagination;
116pub(crate) mod repository;
117mod utils;
118
119pub mod app_session;
120pub mod compat;
121pub mod oauth2;
122pub mod policy_data;
123pub mod queue;
124pub mod upstream_oauth2;
125pub mod user;
126
127pub use self::{
128    clock::{Clock, SystemClock},
129    pagination::{Page, Pagination},
130    repository::{
131        BoxRepository, Repository, RepositoryAccess, RepositoryError, RepositoryTransaction,
132    },
133    utils::{BoxClock, BoxRng, MapErr},
134};