use crate::{Container, Injectable};
use std::marker::PhantomData;
use std::sync::Arc;
pub struct Reg<T, Rest>(PhantomData<(T, Rest)>);
pub trait HasType<T: Injectable> {}
impl<T: Injectable, Rest> HasType<T> for Reg<T, Rest> {}
pub struct TypedBuilder<R = ()> {
container: Container,
_registry: PhantomData<R>,
}
impl TypedBuilder<()> {
#[inline]
pub fn new() -> Self {
Self {
container: Container::new(),
_registry: PhantomData,
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
container: Container::with_capacity(capacity),
_registry: PhantomData,
}
}
}
impl Default for TypedBuilder<()> {
fn default() -> Self {
Self::new()
}
}
impl<R> TypedBuilder<R> {
#[inline]
pub fn singleton<T: Injectable>(self, instance: T) -> TypedBuilder<Reg<T, R>> {
self.container.singleton(instance);
TypedBuilder {
container: self.container,
_registry: PhantomData,
}
}
#[inline]
pub fn lazy<T: Injectable, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
where
F: Fn() -> T + Send + Sync + 'static,
{
self.container.lazy(factory);
TypedBuilder {
container: self.container,
_registry: PhantomData,
}
}
#[inline]
pub fn transient<T: Injectable, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
where
F: Fn() -> T + Send + Sync + 'static,
{
self.container.transient(factory);
TypedBuilder {
container: self.container,
_registry: PhantomData,
}
}
#[inline]
pub fn build(self) -> TypedContainer<R> {
self.container.lock();
TypedContainer {
container: self.container,
_registry: PhantomData,
}
}
#[inline]
pub fn build_dynamic(self) -> Container {
self.container.lock();
self.container
}
#[inline]
pub fn inner(&self) -> &Container {
&self.container
}
}
pub trait DeclaresDeps: Injectable {
fn dependency_names() -> &'static [&'static str] {
&[]
}
}
impl<R> TypedBuilder<R> {
#[inline]
pub fn with_deps<T: DeclaresDeps>(self, instance: T) -> TypedBuilder<Reg<T, R>> {
self.singleton(instance)
}
#[inline]
pub fn lazy_with_deps<T: DeclaresDeps, F>(self, factory: F) -> TypedBuilder<Reg<T, R>>
where
F: Fn() -> T + Send + Sync + 'static,
{
self.lazy(factory)
}
}
pub trait VerifyDeps<D> {}
impl<R, D> VerifyDeps<D> for R {}
pub struct TypedContainer<R> {
container: Container,
_registry: PhantomData<R>,
}
impl<R> TypedContainer<R> {
#[inline]
pub fn get<T: Injectable>(&self) -> Arc<T> {
self.container
.get::<T>()
.expect("TypedContainer: service not found (registration mismatch)")
}
#[inline]
pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
self.container.try_get::<T>()
}
#[inline]
pub fn contains<T: Injectable>(&self) -> bool {
self.container.contains::<T>()
}
#[inline]
pub fn scope(&self) -> Container {
self.container.scope()
}
#[inline]
pub fn inner(&self) -> &Container {
&self.container
}
#[inline]
pub fn into_inner(self) -> Container {
self.container
}
}
impl<R> Clone for TypedContainer<R> {
fn clone(&self) -> Self {
Self {
container: self.container.clone(),
_registry: PhantomData,
}
}
}
impl<R> std::fmt::Debug for TypedContainer<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedContainer")
.field("inner", &self.container)
.finish()
}
}
pub trait Has<T: Injectable>: HasType<T> {}
impl<T: Injectable, R: HasType<T>> Has<T> for R {}
pub trait HasService<T: Injectable>: HasType<T> {}
impl<T: Injectable, R: HasType<T>> HasService<T> for R {}
pub trait DepsPresent<D> {}
impl<R, D> DepsPresent<D> for R {}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct Database {
url: String,
}
#[derive(Clone)]
struct Cache {
size: usize,
}
#[derive(Clone)]
struct UserService;
impl DeclaresDeps for UserService {
fn dependency_names() -> &'static [&'static str] {
&["Database", "Cache"]
}
}
#[test]
fn test_typed_builder_basic() {
let container = TypedBuilder::new()
.singleton(Database {
url: "postgres://localhost".into(),
})
.singleton(Cache { size: 1024 })
.build();
let db = container.get::<Database>();
let cache = container.get::<Cache>();
assert_eq!(db.url, "postgres://localhost");
assert_eq!(cache.size, 1024);
}
#[test]
fn test_typed_builder_lazy() {
let container = TypedBuilder::new()
.lazy(|| Database {
url: "lazy://created".into(),
})
.build();
let db = container.get::<Database>();
assert_eq!(db.url, "lazy://created");
}
#[test]
fn test_typed_builder_transient() {
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
#[derive(Clone)]
struct Counter(u32);
let container = TypedBuilder::new()
.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)))
.build();
let c1 = container.get::<Counter>();
let c2 = container.get::<Counter>();
assert_ne!(c1.0, c2.0);
}
#[test]
fn test_typed_container_clone() {
let container = TypedBuilder::new()
.singleton(Database { url: "test".into() })
.build();
let container2 = container.clone();
let db1 = container.get::<Database>();
let db2 = container2.get::<Database>();
assert!(Arc::ptr_eq(&db1, &db2));
}
#[test]
fn test_with_dependencies() {
let container = TypedBuilder::new()
.singleton(Database { url: "pg".into() })
.singleton(Cache { size: 100 })
.with_deps(UserService)
.build();
let _ = container.get::<UserService>();
}
#[test]
fn test_many_services() {
#[derive(Clone)]
struct S1;
#[derive(Clone)]
struct S2;
#[derive(Clone)]
struct S3;
#[derive(Clone)]
struct S4;
#[derive(Clone)]
struct S5;
let container = TypedBuilder::new()
.singleton(S1)
.singleton(S2)
.singleton(S3)
.singleton(S4)
.singleton(S5)
.build();
let _ = container.get::<S1>();
let _ = container.get::<S2>();
let _ = container.get::<S3>();
let _ = container.get::<S4>();
let _ = container.get::<S5>();
}
#[test]
fn test_scope_from_typed() {
let container = TypedBuilder::new()
.singleton(Database { url: "root".into() })
.build();
let child = container.scope();
child.singleton(Cache { size: 256 });
assert!(child.contains::<Database>());
assert!(child.contains::<Cache>());
}
}