typesafe_repository 0.2.0

Abstract data persistence in a safe and unified way
Documentation
//! `typesafe_repository` is designed to abstract data persistence, so its implementations can be
//! easily swapped for testing or migration purposes
//!
//! You can create your own repository by implementing traits like [Add](Add),
//! [List](List), [Get](Get), and plenty of others (see [async_ops](async_ops) for complete list)
//!
//!
//! Typical way to define abstract repository for some type is:
//! ```
//! use typesafe_repository::{Identity, Repository, Add, List, Remove};
//! use typesafe_repository::inmemory::InMemoryRepository;
//! use typesafe_repository::macros::Id;
//! use std::sync::Arc;
//!
//! #[derive(Clone, Id)]
//! pub struct Foo {
//!   id: usize,
//! }
//!
//! trait FooRepository:
//!   Repository<Foo, Error = Box<dyn std::error::Error + Send + Sync>>
//!   + Add<Foo>
//!   + List<Foo>
//!   + Remove<Foo>
//! {}
//!
//! impl<T> FooRepository for T
//! where T: Repository<Foo, Error = Box<dyn std::error::Error + Send + Sync>>
//!     + Add<Foo>
//!     + List<Foo>
//!     + Remove<Foo> {}
//!
//! struct FooService {
//!   repo: Arc<dyn FooRepository>,
//! }
//!
//! // You can mock any repository with InMemoryRepository since it implements all operations on
//! // all types
//! let repo = Arc::new(InMemoryRepository::new());
//! FooService { repo };
//! ```
#![deny(
    future_incompatible,
    let_underscore,
    keyword_idents,
    elided_lifetimes_in_paths,
    meta_variable_misuse,
    noop_method_call,
    pointer_structural_match,
    unused_lifetimes,
    unused_qualifications,
    unsafe_op_in_unsafe_fn,
    clippy::undocumented_unsafe_blocks,
    clippy::wildcard_dependencies,
    clippy::debug_assert_with_mut_call,
    clippy::empty_line_after_outer_attr,
    clippy::panic,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::redundant_field_names,
    clippy::rest_pat_in_fully_bound_structs,
    clippy::unneeded_field_pattern,
    clippy::useless_let_if_seq,
    clippy::default_union_representation,
    clippy::arithmetic_side_effects,
    clippy::checked_conversions,
    clippy::dbg_macro
)]
#![warn(
    clippy::pedantic,
    clippy::cargo,
    clippy::cloned_instead_of_copied,
    clippy::cognitive_complexity
)]
#![allow(clippy::must_use_candidate, clippy::doc_markdown)]

#[cfg(feature = "macros")]
pub mod macros {
    pub use typesafe_repository_macro::Id;
}

pub mod prelude {
    pub use super::{Add, Get, Identity, IdentityOf, List, Remove, Repository, Save, Update, GetBy, ListBy};
}

#[cfg(feature = "inmemory")]
pub mod inmemory;

/// Transactional traits and wrapper for [Repository](Repository)
///
/// [RepositoryWrapper](transactional::RepositoryWrapper)
#[cfg(feature = "transactional")]
pub mod transactional;

/// Marker trait with `Error` definition for operations
///
pub trait Repository<V: Identity> {
    type Error: Send + 'static;
}

pub use async_ops::*;

/// async operations for [Repository](Repository)
///
///```
///  use typesafe_repository::{Repository, Add, Identity, macros::Id};
///  use async_trait::async_trait;
///  use std::sync::Mutex;
///  use std::error::Error;
///  use std::sync::Arc;
///
///  #[derive(Id, Debug, Clone, PartialEq)]
///  pub struct Foo {
///    id: usize,
///  }
///
///  pub struct ExampleRepository {
///     foos: Arc<Mutex<Vec<Foo>>>,
///  }
///
///  impl Repository<Foo> for ExampleRepository {
///     type Error = Box<dyn Error + Send + 'static>;
///  }
///
///  #[async_trait]
///  impl Add<Foo> for ExampleRepository {
///     async fn add(&self, f: Foo) -> Result<(), Self::Error> {
///         self.foos.lock().unwrap().push(f);
///         Ok(())
///     }
///  }
///
///  #[tokio::main]
///  async fn main() {
///     let foos = Arc::new(Mutex::new(vec![]));
///     let repo = ExampleRepository { foos: foos.clone() };
///     let foo = Foo { id: 10 };
///     assert_eq!((), repo.add(foo.clone()).await.unwrap());
///     assert_eq!(Some(foo.clone()), foos.lock().unwrap().pop());
///  }
///```
pub mod async_ops {
    use crate::{Dao, Identity, IdentityBy, IdentityOf, Repository, SelectBy, Selector};
    use async_trait::async_trait;

    /// Generate `Id` for [DAO](Dao) of `V`, and persist [DAO](Dao)
    ///
    /// This trait is usable when `Id` cannot be generated in-place (like sequential database id)
    #[async_trait]
    pub trait Create<D: Dao<Parent = V>, V: Identity>: Repository<V> {
        async fn create(&self, d: D) -> Result<IdentityOf<V>, Self::Error>;
    }

    /// Add entity to repository
    #[async_trait]
    pub trait Add<V: Identity>: Repository<V> {
        async fn add(&self, v: V) -> Result<(), Self::Error>;
    }

    /// List all entities in repository
    #[async_trait]
    pub trait List<V: Identity>: Repository<V> {
        async fn list(&self) -> Result<Vec<V>, Self::Error>;
    }

    /// Get one entity by primary key
    #[async_trait]
    pub trait Get<V: Identity>: Repository<V> {
        async fn get_one(&self, k: &V::Id) -> Result<Option<V>, Self::Error>;
    }

    /// Acts as either [Add](Add) or [Update](Update)
    #[async_trait]
    pub trait Save<V: Identity>: Repository<V> {
        async fn save(&self, v: V) -> Result<(), Self::Error>;
    }

    /// Update entity identified by primary key in repository
    #[async_trait]
    pub trait Update<V: Identity>: Repository<V> {
        async fn update(&self, v: V) -> Result<(), Self::Error>;
    }

    /// Remove entity from repository by primary key
    #[async_trait]
    pub trait Remove<V: Identity>: Repository<V> {
        async fn remove(&self, k: &V::Id) -> Result<Option<V>, Self::Error>;
    }

    /// Find one entity by secondary key ([IdentityBy](IdentityBy) trait)
    #[async_trait]
    pub trait GetBy<V, S>: Repository<V>
    where
        V: IdentityBy<S> + Identity,
    {
        async fn get_by(&self, s: &S) -> Result<Option<V>, Self::Error>;
    }

    /// Find many entities by secondary key ([IdentityBy](IdentityBy) trait)
    #[async_trait]
    pub trait ListBy<V, S>: Repository<V>
    where
        V: IdentityBy<S> + Identity,
    {
        async fn list_by(&self, s: &S) -> Result<Vec<V>, Self::Error>;
    }

    /// Find many entities by selector ([SelectBy](SelectBy) trait)
    #[async_trait]
    pub trait Select<V, S>: Repository<V>
    where
        V: Identity + SelectBy<S>,
        S: Selector,
    {
        async fn select(&self, s: &S) -> Result<Vec<V>, Self::Error>;
    }

    /// Find one entity by selector ([SelectBy](SelectBy) trait)
    #[async_trait]
    pub trait Find<V, S>: Repository<V>
    where
        V: Identity + SelectBy<S>,
        S: Selector,
    {
        async fn find(&self, s: &S) -> Result<Option<V>, Self::Error>;
    }
}

/// Defines type's primary key
///
/// Primary key should be unique, but it is not required
pub trait Identity
where
    Self: Send,
{
    type Id: std::fmt::Debug + Clone;
    fn id(&self) -> &Self::Id;
}

/// Partially initialized entity
///
/// ```
/// use typesafe_repository::macros::Id;
/// use typesafe_repository::{Identity, Dao, Create};
///
/// #[derive(Id)]
/// struct Product {
///   id: usize,
///   name: String,
///   count: usize,
/// }
///
/// struct ProductDao {
///   name: String,
///   count: usize,
/// }
///
/// impl Dao for ProductDao {
///   type Parent = Product;
///   type Args = usize;
///
///   fn enrich(self, id: usize) -> Product {
///     let ProductDao { name, count } = self;
///     Product { id, name, count }
///   }
/// }
/// ```
pub trait Dao {
    type Parent: Identity;
    type Args;

    fn enrich(self, t: Self::Args) -> Self::Parent;
}

/// Defines additional type's keys
///
/// ```
/// use typesafe_repository::{Identity, IdentityBy};
/// use typesafe_repository::macros::Id;
///
/// type Login = String;
/// type Email = String;
///
/// #[derive(Id)]
/// struct User {
///   #[id]
///   login: Login,
///   #[id_by]
///   email: Email,
/// }
/// ```
pub trait IdentityBy<T> {
    fn id_by(&self) -> T;
}

/// Mark that Selector can be used with type
///
/// This trait is needed to allow using only explicitly specified selectors for type
pub trait SelectBy<S: Selector> {}

/// Marker identity for advanced operations and parameters
/// ```
/// use typesafe_repository::{Identity, IdentityBy, Repository, Select, Selector, SelectBy};
/// use typesafe_repository::inmemory::InMemoryRepository;
/// use typesafe_repository::macros::Id;
/// use std::error::Error;
/// use std::sync::Arc;
///
/// // ListLatest don't have to contain any information because it's only purpose is to mark that
/// // repository need to return latest products
/// struct ListLatest;
///
/// #[derive(Id)]
/// struct Product {
///   id: usize,
/// }
///
/// impl Selector for ListLatest {}
/// impl SelectBy<ListLatest> for Product {}
///
/// trait ProductRepository:
///     Repository<Product, Error = Box<dyn Error + Send + Sync>>
///     + Select<Product, ListLatest>
///     {}
///
/// ```
/// In above example, `Select<Product, ListLatest>` implementation could look like:
/// ```
/// # use typesafe_repository::prelude::*;
/// # use typesafe_repository::{Selector, SelectBy, Select};
/// # use typesafe_repository::inmemory::InMemoryRepository;
/// # use typesafe_repository::macros::Id;
/// # use std::error::Error;
/// # use std::sync::Arc;
/// use typesafe_repository::ListBy;
/// use async_trait::async_trait;
/// # struct ListLatest;
/// #
/// # #[derive(Clone, Id)]
/// # struct Product {
/// #   id: usize,
/// #   date: usize,
/// # }
/// #
/// # impl Selector for ListLatest {}
/// # impl SelectBy<ListLatest> for Product {}
/// #
/// # struct TestProductRepository {
/// #   storage: InMemoryRepository<Product, Box<dyn Error + Send + Sync>>,
/// # }
/// # impl Repository<Product> for TestProductRepository {
/// #   type Error = Box<dyn Error + Send + Sync>;
/// # }
/// #
///
/// #[async_trait]
/// impl Select<Product, ListLatest> for TestProductRepository {
///   async fn select(&self, _: &ListLatest) -> Result<Vec<Product>, Self::Error> {
///     let mut list = self.storage.list().await?;
///     let len = list.len();
///     list.sort_by_key(|p| p.date);
///     Ok(list.into_iter().skip(len - 5).collect())
///   }
/// }
/// ```
pub trait Selector {}

/// `Identity::Id` shorthand that helps abstract from `Id` type
/// ```
/// use typesafe_repository::{IdentityOf, Identity};
/// use typesafe_repository::macros::Id;
///
/// #[derive(Id)]
/// struct Foo {
///     id: u64,
/// }
/// struct Bar {
///     foo: IdentityOf<Foo>,
/// }
/// ```
pub type IdentityOf<T> = <T as Identity>::Id;

use serde::{de::DeserializeOwned, Deserialize, Serialize};

/// Identity which can be obtained only from `impl Identity` or
/// [ValidIdentityProvider](ValidIdentityProvider)
///
/// `ValidIdentity` intended to be used for internal references.  
///
/// It has two main benefits:
/// - Guarantee that identity is valid (points to existing entity)
/// - `ValidIdentity` of `T` cannot be swapped with other entity id by mistake even if
/// `Identity::Id` is the same for two types
///
/// ```
/// use typesafe_repository::{Identity, ValidIdentity};
/// use typesafe_repository::macros::Id;
/// use serde::{Serialize, Deserialize};
///
/// struct Foo {
///   bar: ValidIdentity<Bar>,
/// }
///
/// #[derive(Id)]
/// struct Bar {
///   id: usize,
/// }
///
/// let bar = Bar { id: 0 };
/// let foo = Foo { bar: ValidIdentity::of(&bar) };
/// ```
///
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ValidIdentity<T: Identity>(IdentityOf<T>)
where
    IdentityOf<T>: PartialEq + Serialize + DeserializeOwned;

impl<T: Identity> ValidIdentity<T>
where
    T::Id: PartialEq + Serialize + DeserializeOwned,
{
    /// Construct `ValidIdentity` from `T`
    pub fn of(t: &T) -> Self {
        Self(t.id().clone())
    }
    pub fn into_inner(self) -> IdentityOf<T> {
        self.0
    }
    /// Construct `ValidIdentity` from [ValidIdentityProvider](ValidIdentityProvider)
    pub fn from<P: ValidIdentityProvider<T>>(p: &P) -> Self {
        Self(p.get())
    }
}

/// Trusted source of [ValidIdentity](ValidIdentity)
///
/// Intended to be used in persistence layer or tests in order to construct
/// [ValidIdentity](ValidIdentity) for `T` without actual `T` loaded
///
pub trait ValidIdentityProvider<T>
where
    T: Identity,
    T::Id: PartialEq + Serialize + DeserializeOwned,
{
    fn get(&self) -> IdentityOf<T>;
}