1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

storage{,-pg}: better documentation of both crates

This commit is contained in:
Quentin Gliech
2023-01-26 17:58:03 +01:00
parent 0bf1a1998e
commit 6ad8b82a35
7 changed files with 544 additions and 264 deletions

View File

@@ -13,6 +13,125 @@
// limitations under the License.
//! Interactions with the storage backend
//!
//! This crate provides a set of traits that can be implemented to interact with
//! the storage backend. Those traits are called repositories and are grouped by
//! the type of data they manage.
//!
//! Each of those reposotories can be accessed via the [`RepositoryAccess`]
//! trait. This trait can be wrapped in a [`BoxRepository`] to allow using it
//! without caring about the underlying storage backend, and without carrying
//! around the generic type parameter.
//!
//! This crate also defines a [`Clock`] trait that can be used to abstract the
//! way the current time is retrieved. It has two implementation:
//! [`SystemClock`] that uses the system time and [`MockClock`] which is useful
//! for testing.
//!
//! [`MockClock`]: crate::clock::MockClock
//!
//! # Defining a new repository
//!
//! To define a new repository, you have to:
//! 1. Define a new (async) repository trait, with the methods you need
//! 2. Write an implementation of this trait for each storage backend you want
//! (currently only for [`mas-storage-pg`])
//! 3. Make it accessible via the [`RepositoryAccess`] trait
//!
//! The repository trait definition should look like this:
//!
//! ```rust
//! # use async_trait::async_trait;
//! # use ulid::Ulid;
//! # use rand_core::RngCore;
//! # use mas_storage::Clock;
//! #
//! # // A fake data structure, usually defined in mas-data-model
//! # struct FakeData {
//! # id: Ulid,
//! # }
//! #
//! # // A fake empty macro, to replace `mas_storage::repository_impl`
//! # macro_rules! repository_impl { ($($tok:tt)*) => {} }
//!
//! #[async_trait]
//! pub trait FakeDataRepository: Send + Sync {
//! /// The error type returned by the repository
//! type Error;
//!
//! /// Lookup a [`FakeData`] by its ID
//! ///
//! /// Returns `None` if no [`FakeData`] was found
//! ///
//! /// # Parameters
//! ///
//! /// * `id`: The ID of the [`FakeData`] to lookup
//! ///
//! /// # Errors
//! ///
//! /// Returns [`Self::Error`] if the underlying repository fails
//! async fn lookup(&mut self, id: Ulid) -> Result<Option<FakeData>, Self::Error>;
//!
//! /// Create a new [`FakeData`]
//! ///
//! /// Returns the newly-created [`FakeData`].
//! ///
//! /// # Parameters
//! ///
//! /// * `rng`: The random number generator to use
//! /// * `clock`: The clock used to generate timestamps
//! ///
//! /// # Errors
//! ///
//! /// Returns [`Self::Error`] if the underlying repository fails
//! async fn add(
//! &mut self,
//! rng: &mut (dyn RngCore + Send),
//! clock: &dyn Clock,
//! ) -> Result<FakeData, Self::Error>;
//! }
//!
//! repository_impl!(FakeDataRepository:
//! async fn lookup(&mut self, id: Ulid) -> Result<Option<FakeData>, Self::Error>;
//! async fn add(
//! &mut self,
//! rng: &mut (dyn RngCore + Send),
//! clock: &dyn Clock,
//! ) -> Result<FakeData, Self::Error>;
//! );
//! ```
//!
//! Four things to note with the implementation:
//!
//! 1. It defined an assocated error type, and all functions are faillible,
//! and use that error type
//! 2. Lookups return an `Result<Option<T>, Self::Error>`, because 'not found'
//! errors are usually cases that are handled differently
//! 3. Operations that need to record the current type use a [`Clock`]
//! parameter. Operations that need to generate new IDs also use a random
//! number generator.
//! 4. All the methods use an `&mut self`. This is ensures only one operation
//! is done at a time on a single repository instance.
//!
//! Then update the [`RepositoryAccess`] trait to make the new repository
//! available:
//!
//! ```rust
//! # trait FakeDataRepository {
//! # type Error;
//! # }
//!
//! /// Access the various repositories the backend implements.
//! pub trait RepositoryAccess: Send {
//! /// The backend-specific error type used by each repository.
//! type Error: std::error::Error + Send + Sync + 'static;
//!
//! // ...other repositories...
//!
//! /// Get a [`FakeDataRepository`]
//! fn fake_data<'c>(&'c mut self) -> Box<dyn FakeDataRepository<Error = Self::Error> + 'c>;
//! }
//! ```
#![forbid(unsafe_code)]
#![deny(
@@ -25,11 +144,10 @@
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
use rand_core::CryptoRngCore;
pub mod clock;
pub mod pagination;
pub(crate) mod repository;
mod utils;
pub mod compat;
pub mod oauth2;
@@ -42,66 +160,5 @@ pub use self::{
repository::{
BoxRepository, Repository, RepositoryAccess, RepositoryError, RepositoryTransaction,
},
utils::{BoxClock, BoxRng, MapErr},
};
/// A wrapper which is used to map the error type of a repository to another
pub struct MapErr<R, F> {
inner: R,
mapper: F,
}
impl<R, F> MapErr<R, F> {
fn new(inner: R, mapper: F) -> Self {
Self { inner, mapper }
}
}
/// A macro to implement a repository trait for the [`MapErr`] wrapper and for
/// [`Box<R>`]
#[macro_export]
macro_rules! repository_impl {
($repo_trait:ident:
$(
async fn $method:ident (
&mut self
$(, $arg:ident: $arg_ty:ty )*
$(,)?
) -> Result<$ret_ty:ty, Self::Error>;
)*
) => {
#[::async_trait::async_trait]
impl<R: ?Sized> $repo_trait for ::std::boxed::Box<R>
where
R: $repo_trait,
{
type Error = <R as $repo_trait>::Error;
$(
async fn $method (&mut self $(, $arg: $arg_ty)*) -> Result<$ret_ty, Self::Error> {
(**self).$method ( $($arg),* ).await
}
)*
}
#[::async_trait::async_trait]
impl<R, F, E> $repo_trait for $crate::MapErr<R, F>
where
R: $repo_trait,
F: FnMut(<R as $repo_trait>::Error) -> E + ::std::marker::Send + ::std::marker::Sync,
{
type Error = E;
$(
async fn $method (&mut self $(, $arg: $arg_ty)*) -> Result<$ret_ty, Self::Error> {
self.inner.$method ( $($arg),* ).await.map_err(&mut self.mapper)
}
)*
}
};
}
/// A boxed [`Clock`]
pub type BoxClock = Box<dyn Clock + Send>;
/// A boxed random number generator
pub type BoxRng = Box<dyn CryptoRngCore + Send>;

View File

@@ -101,6 +101,20 @@ pub trait RepositoryTransaction {
}
/// Access the various repositories the backend implements.
///
/// All the methods return a boxed trait object, which can be used to access a
/// particular repository. The lifetime of the returned object is bound to the
/// lifetime of the whole repository, so that only one mutable reference to the
/// repository is used at a time.
///
/// When adding a new repository, you should add a new method to this trait, and
/// update the implementations for [`MapErr`] and [`Box<R>`] below.
///
/// Note: this used to have generic associated types to avoid boxing all the
/// repository traits, but that was removed because it made almost impossible to
/// box the trait object. This might be a shortcoming of the initial
/// implementation of generic associated types, and might be fixed in the
/// future.
pub trait RepositoryAccess: Send {
/// The backend-specific error type used by each repository.
type Error: std::error::Error + Send + Sync + 'static;

View File

@@ -0,0 +1,86 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Wrappers and useful type aliases
use rand_core::CryptoRngCore;
use crate::Clock;
/// A wrapper which is used to map the error type of a repository to another
pub struct MapErr<R, F> {
pub(crate) inner: R,
pub(crate) mapper: F,
_private: (),
}
impl<R, F> MapErr<R, F> {
pub(crate) fn new(inner: R, mapper: F) -> Self {
Self {
inner,
mapper,
_private: (),
}
}
}
/// A boxed [`Clock`]
pub type BoxClock = Box<dyn Clock + Send>;
/// A boxed random number generator
pub type BoxRng = Box<dyn CryptoRngCore + Send>;
/// A macro to implement a repository trait for the [`MapErr`] wrapper and for
/// [`Box<R>`]
#[macro_export]
macro_rules! repository_impl {
($repo_trait:ident:
$(
async fn $method:ident (
&mut self
$(, $arg:ident: $arg_ty:ty )*
$(,)?
) -> Result<$ret_ty:ty, Self::Error>;
)*
) => {
#[::async_trait::async_trait]
impl<R: ?Sized> $repo_trait for ::std::boxed::Box<R>
where
R: $repo_trait,
{
type Error = <R as $repo_trait>::Error;
$(
async fn $method (&mut self $(, $arg: $arg_ty)*) -> Result<$ret_ty, Self::Error> {
(**self).$method ( $($arg),* ).await
}
)*
}
#[::async_trait::async_trait]
impl<R, F, E> $repo_trait for $crate::MapErr<R, F>
where
R: $repo_trait,
F: FnMut(<R as $repo_trait>::Error) -> E + ::std::marker::Send + ::std::marker::Sync,
{
type Error = E;
$(
async fn $method (&mut self $(, $arg: $arg_ty)*) -> Result<$ret_ty, Self::Error> {
self.inner.$method ( $($arg),* ).await.map_err(&mut self.mapper)
}
)*
}
};
}