Skip to content

Commit

Permalink
feat(repos): built basic repos crate
Browse files Browse the repository at this point in the history
  • Loading branch information
johnbchron committed Nov 25, 2024
1 parent a3624c9 commit 2928edb
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 1 deletion.
14 changes: 13 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ thiserror = "2"

# inherited from rambit
crunch = { git = "https://github.com/rambit-systems/rambit" }
db = { git = "https://github.com/rambit-systems/rambit" }
dvf = { git = "https://github.com/rambit-systems/rambit" }
hex = { git = "https://github.com/rambit-systems/rambit" }
model = { git = "https://github.com/rambit-systems/rambit" }
Expand Down
19 changes: 19 additions & 0 deletions crates/repos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "repos"
version = "0.1.0"
edition = "2021"

[dependencies]
models = { path = "../models" }

db.workspace = true
hex.workspace = true

tracing.workspace = true

miette.workspace = true

async-trait.workspace = true

[lints]
workspace = true
97 changes: 97 additions & 0 deletions crates/repos/src/base.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use std::marker::PhantomData;

pub use db::CreateModelError;
pub(crate) use db::{DatabaseAdapter, FetchModelByIndexError, FetchModelError};
use hex::health;
use miette::Result;
use tracing::instrument;

use crate::ModelRepository;

/// Provides a base repository implementation for any model.
pub struct BaseRepository<M: models::Model, DB: DatabaseAdapter> {
db_adapter: DB,
_phantom: PhantomData<M>,
}

impl<M: models::Model, DB: DatabaseAdapter + Clone> Clone
for BaseRepository<M, DB>
{
fn clone(&self) -> Self {
Self {
db_adapter: self.db_adapter.clone(),
_phantom: PhantomData,
}
}
}

impl<M: models::Model, DB: DatabaseAdapter> BaseRepository<M, DB> {
/// Creates a new `BaseRepository` instance.
pub fn new(db_adapter: DB) -> Self {
tracing::info!(
"creating new `BaseRepository<{:?}>` instance",
M::TABLE_NAME
);

Self {
db_adapter,
_phantom: PhantomData,
}
}
}

#[async_trait::async_trait]
impl<M: models::Model, DB: DatabaseAdapter> health::HealthReporter
for BaseRepository<M, DB>
{
fn name(&self) -> &'static str { stringify!(BaseRepository<M, DB>) }
async fn health_check(&self) -> health::ComponentHealth {
health::AdditiveComponentHealth::from_futures(Some(
self.db_adapter.health_report(),
))
.await
.into()
}
}

#[async_trait::async_trait]
impl<M: models::Model, DB: DatabaseAdapter> ModelRepository
for BaseRepository<M, DB>
{
type Model = M;
type ModelCreateRequest = M;
type CreateError = CreateModelError;

#[instrument(skip(self))]
async fn create_model(
&self,
input: Self::ModelCreateRequest,
) -> Result<Self::Model, CreateModelError> {
self.db_adapter.create_model::<Self::Model>(input).await
}

#[instrument(skip(self))]
async fn fetch_model_by_id(
&self,
id: models::RecordId<Self::Model>,
) -> Result<Option<Self::Model>, FetchModelError> {
self.db_adapter.fetch_model_by_id(id).await
}

#[instrument(skip(self))]
async fn fetch_model_by_index(
&self,
index_name: String,
index_value: models::EitherSlug,
) -> Result<Option<Self::Model>, FetchModelByIndexError> {
self
.db_adapter
.fetch_model_by_index(index_name, index_value)
.await
}

#[instrument(skip(self))]
async fn enumerate_models(&self) -> Result<Vec<Self::Model>> {
self.db_adapter.enumerate_models::<Self::Model>().await
}
}
78 changes: 78 additions & 0 deletions crates/repos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Repositories for use in services.
use db::{FetchModelByIndexError, FetchModelError};
use hex::Hexagonal;
use miette::Result;
use models::EitherSlug;

mod base;
pub use self::base::BaseRepository;

/// Defines a repository interface for models.
#[async_trait::async_trait]
pub trait ModelRepository: Hexagonal {
/// The model type.
type Model: models::Model;
/// The request type for creating a model.
type ModelCreateRequest: std::fmt::Debug + Send + Sync + 'static;
/// The error type for creating a model.
type CreateError: std::error::Error + Send + Sync + 'static;

/// Creates a new model.
async fn create_model(
&self,
input: Self::ModelCreateRequest,
) -> Result<Self::Model, Self::CreateError>;

/// Fetches a model by its ID.
async fn fetch_model_by_id(
&self,
id: models::RecordId<Self::Model>,
) -> Result<Option<Self::Model>, FetchModelError>;

/// Fetches a model by an index.
///
/// Must be a valid index, defined in the model's `INDICES` constant.
async fn fetch_model_by_index(
&self,
index_name: String,
index_value: EitherSlug,
) -> Result<Option<Self::Model>, FetchModelByIndexError>;

/// Produces a list of all model IDs.
async fn enumerate_models(&self) -> Result<Vec<Self::Model>>;
}

#[async_trait::async_trait]
impl<T, I> ModelRepository for T
where
T: std::ops::Deref<Target = I> + Hexagonal + Sized,
I: ModelRepository + ?Sized,
{
type Model = I::Model;
type ModelCreateRequest = I::ModelCreateRequest;
type CreateError = I::CreateError;

async fn create_model(
&self,
input: Self::ModelCreateRequest,
) -> Result<Self::Model, Self::CreateError> {
I::create_model(self, input).await
}
async fn fetch_model_by_id(
&self,
id: models::RecordId<Self::Model>,
) -> Result<Option<Self::Model>, FetchModelError> {
I::fetch_model_by_id(self, id).await
}
async fn fetch_model_by_index(
&self,
index_name: String,
index_value: EitherSlug,
) -> Result<Option<Self::Model>, FetchModelByIndexError> {
I::fetch_model_by_index(self, index_name, index_value).await
}
async fn enumerate_models(&self) -> Result<Vec<Self::Model>> {
I::enumerate_models(self).await
}
}

0 comments on commit 2928edb

Please sign in to comment.