use crate::connection::Connection;
use crate::datasource_error::DatasourceError;
use crate::repository::{as_readonly, ICRUDRepository, IReadonlyRepository};
use crate::repository_access::RepositoryAccess;
use crate::result::Result;
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
type Binder = Arc<dyn Fn(&RepositoryFactory) + Send + Sync>;
fn binders() -> &'static Mutex<Vec<Binder>> {
static BINDERS: Mutex<Vec<Binder>> = Mutex::new(Vec::new());
&BINDERS
}
struct Named {
create: Arc<dyn Fn(Arc<dyn Connection>) -> Arc<dyn ICRUDRepository> + Send + Sync>,
create_readonly: Arc<dyn Fn(Arc<dyn Connection>) -> Arc<dyn IReadonlyRepository> + Send + Sync>,
}
struct Maps {
readonly: HashMap<TypeId, Arc<dyn IReadonlyRepository>>,
crud: HashMap<TypeId, Arc<dyn ICRUDRepository>>,
named: HashMap<String, Named>,
}
pub trait ReadonlyRepositoryFactory: Send + Sync {
fn register(&self, repository: Arc<dyn IReadonlyRepository>);
fn create<T: 'static>(&self) -> Result<Arc<dyn IReadonlyRepository>>;
}
pub trait CRUDRepositoryFactory: Send + Sync {
fn register(&self, repository: Arc<dyn ICRUDRepository>);
fn create<T: 'static>(&self) -> Result<Arc<dyn ICRUDRepository>>;
}
#[derive(Clone)]
pub struct RepositoryFactory {
maps: Arc<Mutex<Maps>>,
}
impl Default for RepositoryFactory {
fn default() -> Self {
Self::new()
}
}
impl RepositoryFactory {
pub fn new() -> Self {
let factory = Self {
maps: Arc::new(Mutex::new(Maps {
readonly: HashMap::new(),
crud: HashMap::new(),
named: HashMap::new(),
})),
};
factory.apply_bound();
factory
}
pub fn bind(register: impl Fn(&RepositoryFactory) + Send + Sync + 'static) {
binders()
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(Arc::new(register));
}
pub fn reset_binders() {
binders().lock().unwrap_or_else(|e| e.into_inner()).clear();
}
fn apply_bound(&self) {
let snapshot = binders().lock().unwrap_or_else(|e| e.into_inner()).clone();
for register in snapshot {
register(self);
}
}
pub fn register(&self, repository: Arc<dyn ICRUDRepository>) {
let type_id = repository.entity_type();
let readonly = as_readonly(repository.clone());
let mut maps = self.lock();
maps.readonly.insert(type_id, readonly);
maps.crud.insert(type_id, repository);
}
pub fn register_named(
&self,
type_name: impl Into<String>,
plural_name: impl Into<String>,
create: impl Fn(Arc<dyn Connection>) -> Arc<dyn ICRUDRepository> + Send + Sync + 'static,
create_readonly: impl Fn(Arc<dyn Connection>) -> Arc<dyn IReadonlyRepository>
+ Send
+ Sync
+ 'static,
) {
let named = Named {
create: Arc::new(create),
create_readonly: Arc::new(create_readonly),
};
let mut maps = self.lock();
maps.named.insert(type_name.into(), named.clone_entry());
maps.named.insert(plural_name.into(), named);
}
pub fn create<T: 'static>(&self) -> Result<Arc<dyn ICRUDRepository>> {
let type_id = TypeId::of::<T>();
let repository = {
let maps = self.lock();
maps.crud.get(&type_id).cloned()
};
repository.ok_or_else(|| missing(&short_name::<T>()))
}
pub fn create_named(
&self,
name: &str,
connection: Arc<dyn Connection>,
) -> Result<Arc<dyn ICRUDRepository>> {
self.apply_bound();
let create = {
let maps = self.lock();
maps.named.get(name).map(|named| Arc::clone(&named.create))
};
Ok((create.ok_or_else(|| missing_named(name))?)(connection))
}
pub fn create_readonly_named(
&self,
name: &str,
connection: Arc<dyn Connection>,
) -> Result<Arc<dyn IReadonlyRepository>> {
self.apply_bound();
let create = {
let maps = self.lock();
maps.named
.get(name)
.map(|named| Arc::clone(&named.create_readonly))
};
Ok((create.ok_or_else(|| missing_named(name))?)(connection))
}
pub fn access(&self, name: &str, connection: Arc<dyn Connection>) -> RepositoryAccess {
self.apply_bound();
RepositoryAccess::new(self.clone(), name, connection)
}
fn lock(&self) -> std::sync::MutexGuard<'_, Maps> {
self.maps.lock().unwrap_or_else(|e| e.into_inner())
}
}
impl Named {
fn clone_entry(&self) -> Self {
Self {
create: Arc::clone(&self.create),
create_readonly: Arc::clone(&self.create_readonly),
}
}
}
impl ReadonlyRepositoryFactory for RepositoryFactory {
fn register(&self, repository: Arc<dyn IReadonlyRepository>) {
let type_id = repository.entity_type();
self.lock().readonly.insert(type_id, repository);
}
fn create<T: 'static>(&self) -> Result<Arc<dyn IReadonlyRepository>> {
let type_id = TypeId::of::<T>();
let repository = {
let maps = self.lock();
maps.readonly.get(&type_id).cloned()
};
repository.ok_or_else(|| missing(&short_name::<T>()))
}
}
impl CRUDRepositoryFactory for RepositoryFactory {
fn register(&self, repository: Arc<dyn ICRUDRepository>) {
RepositoryFactory::register(self, repository);
}
fn create<T: 'static>(&self) -> Result<Arc<dyn ICRUDRepository>> {
RepositoryFactory::create::<T>(self)
}
}
fn short_name<T: ?Sized>() -> String {
std::any::type_name::<T>()
.rsplit("::")
.next()
.unwrap_or(std::any::type_name::<T>())
.to_string()
}
fn missing(name: &str) -> DatasourceError {
DatasourceError::message(format!("CRUDRepository \"{name}\" is not registered"))
}
fn missing_named(name: &str) -> DatasourceError {
DatasourceError::message(format!("Repository \"{name}\" is not registered"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stubs::{StubConnection, StubRepository};
struct Item;
#[test]
fn registers_and_creates() {
let factory = RepositoryFactory::new();
factory.register(Arc::new(StubRepository::typed::<Item>("memory")));
let created = factory.create::<Item>().unwrap();
assert_eq!(created.entity_type(), TypeId::of::<Item>());
assert_eq!(
ReadonlyRepositoryFactory::create::<Item>(&factory)
.unwrap()
.entity_type(),
TypeId::of::<Item>()
);
}
#[test]
fn readonly_registration_is_not_crud() {
let factory = RepositoryFactory::new();
ReadonlyRepositoryFactory::register(
&factory,
as_readonly(Arc::new(StubRepository::typed::<Item>("memory"))),
);
assert!(ReadonlyRepositoryFactory::create::<Item>(&factory).is_ok());
match factory.create::<Item>() {
Ok(_) => panic!("expected missing crud"),
Err(err) => assert!(err.to_string().contains("Item")),
}
}
#[test]
fn missing_throws() {
let factory = RepositoryFactory::new();
match factory.create::<Item>() {
Ok(_) => panic!("expected missing type"),
Err(err) => assert_eq!(err.to_string(), "CRUDRepository \"Item\" is not registered"),
}
}
#[test]
fn registers_and_creates_by_name() {
let factory = RepositoryFactory::new();
factory.register_named(
"Contact",
"contacts",
|_| Arc::new(StubRepository::typed::<Item>("crud")),
|_| as_readonly(Arc::new(StubRepository::typed::<Item>("read"))),
);
let connection: Arc<dyn Connection> = Arc::new(StubConnection::new("memory"));
let created = factory
.create_named("contacts", Arc::clone(&connection))
.unwrap();
assert_eq!(created.entity_type(), TypeId::of::<Item>());
let readonly = factory
.create_readonly_named("Contact", connection)
.unwrap();
assert_eq!(readonly.entity_type(), TypeId::of::<Item>());
}
#[test]
fn bind_registers_on_construct() {
RepositoryFactory::reset_binders();
let crud = Arc::new(StubRepository::typed::<Item>("crud"));
let read = as_readonly(Arc::new(StubRepository::typed::<Item>("read")));
RepositoryFactory::bind(move |factory| {
factory.register_named(
"Contact",
"contacts",
{
let crud = Arc::clone(&crud);
move |_| Arc::clone(&crud) as Arc<dyn ICRUDRepository>
},
{
let read = Arc::clone(&read);
move |_| Arc::clone(&read)
},
);
});
let factory = RepositoryFactory::new();
let connection: Arc<dyn Connection> = Arc::new(StubConnection::new("memory"));
let created = factory
.create_named("contacts", Arc::clone(&connection))
.unwrap();
assert_eq!(created.entity_type(), TypeId::of::<Item>());
RepositoryFactory::reset_binders();
}
#[test]
fn bind_registers_on_access() {
RepositoryFactory::reset_binders();
let crud = Arc::new(StubRepository::typed::<Item>("crud"));
let read = as_readonly(Arc::new(StubRepository::typed::<Item>("read")));
RepositoryFactory::bind(move |factory| {
factory.register_named(
"Contact",
"contacts",
{
let crud = Arc::clone(&crud);
move |_| Arc::clone(&crud) as Arc<dyn ICRUDRepository>
},
{
let read = Arc::clone(&read);
move |_| Arc::clone(&read)
},
);
});
let factory = RepositoryFactory::new();
let connection: Arc<dyn Connection> = Arc::new(StubConnection::new("memory"));
let created = factory
.access("contacts", Arc::clone(&connection))
.create()
.unwrap();
assert_eq!(created.entity_type(), TypeId::of::<Item>());
RepositoryFactory::reset_binders();
}
#[test]
fn missing_name_throws() {
let factory = RepositoryFactory::new();
let connection: Arc<dyn Connection> = Arc::new(StubConnection::new("memory"));
match factory.create_named("Contacts", connection) {
Ok(_) => panic!("expected missing name"),
Err(err) => {
assert_eq!(err.to_string(), "Repository \"Contacts\" is not registered")
}
}
}
}