Crate mas_storage

source ·
Expand description

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.

§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:

#[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:

/// 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>;
}

Re-exports§

Modules§

  • Repositories to interact with all kinds of sessions
  • A Clock is a way to get the current date and time.
  • Repositories to interact with entities of the compatibility layer
  • Repository to schedule persistent jobs.
  • Repositories to interact with entities related to the OAuth 2.0 protocol
  • Utilities to manage paginated queries.
  • Repositories to interact with entities related to the upstream OAuth 2.0 providers
  • Repositories to interact with entities related to user accounts

Macros§

Structs§

  • A wrapper which is used to map the error type of a repository to another
  • An opaque, type-erased error

Traits§

Type Aliases§