use crate::{Container, Injectable, Result};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(feature = "logging")]
use tracing::debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Scope(u64);
impl Scope {
#[inline]
pub fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(1);
Self(COUNTER.fetch_add(1, Ordering::Relaxed))
}
#[inline]
pub fn id(&self) -> u64 {
self.0
}
}
impl Default for Scope {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for Scope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "scope-{}", self.0)
}
}
pub struct ScopedContainer {
container: Container,
scope: Scope,
}
impl ScopedContainer {
#[inline]
pub fn new() -> Self {
let scope = Scope::new();
#[cfg(feature = "logging")]
debug!(
target: "dependency_injector",
scope_id = scope.id(),
"Creating new root ScopedContainer"
);
Self {
container: Container::new(),
scope,
}
}
#[inline]
pub fn from_parent(parent: &Container) -> Self {
let scope = Scope::new();
#[cfg(feature = "logging")]
debug!(
target: "dependency_injector",
scope_id = scope.id(),
parent_depth = parent.depth(),
"Creating ScopedContainer from parent Container"
);
Self {
container: parent.scope(),
scope,
}
}
#[inline]
pub fn from_scope(parent: &ScopedContainer) -> Self {
let scope = Scope::new();
#[cfg(feature = "logging")]
debug!(
target: "dependency_injector",
scope_id = scope.id(),
parent_scope_id = parent.scope.id(),
"Creating child ScopedContainer from parent ScopedContainer"
);
Self {
container: parent.container.scope(),
scope,
}
}
#[inline]
pub fn scope(&self) -> Scope {
self.scope
}
#[inline]
pub fn singleton<T: Injectable>(&self, instance: T) {
self.container.singleton(instance);
}
#[inline]
pub fn lazy<T: Injectable, F>(&self, factory: F)
where
F: Fn() -> T + Send + Sync + 'static,
{
self.container.lazy(factory);
}
#[inline]
pub fn transient<T: Injectable, F>(&self, factory: F)
where
F: Fn() -> T + Send + Sync + 'static,
{
self.container.transient(factory);
}
#[inline]
pub fn register<T: Injectable>(&self, instance: T) {
self.container.register(instance);
}
#[inline]
pub fn register_factory<T: Injectable, F>(&self, factory: F)
where
F: Fn() -> T + Send + Sync + 'static,
{
self.container.register_factory(factory);
}
#[inline]
pub fn get<T: Injectable>(&self) -> Result<Arc<T>> {
self.container.get::<T>()
}
#[inline]
pub fn resolve<T: Injectable>(&self) -> Result<Arc<T>> {
self.get::<T>()
}
#[inline]
pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
self.container.try_get::<T>()
}
#[inline]
pub fn try_resolve<T: Injectable>(&self) -> Option<Arc<T>> {
self.try_get::<T>()
}
#[inline]
pub fn contains<T: Injectable>(&self) -> bool {
self.container.contains::<T>()
}
#[inline]
pub fn has<T: Injectable>(&self) -> bool {
self.contains::<T>()
}
#[inline]
pub fn container(&self) -> &Container {
&self.container
}
#[inline]
pub fn container_mut(&mut self) -> &mut Container {
&mut self.container
}
#[inline]
pub fn depth(&self) -> u32 {
self.container.depth()
}
}
impl Default for ScopedContainer {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for ScopedContainer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScopedContainer")
.field("scope", &self.scope)
.field("container", &self.container)
.finish()
}
}
pub struct ScopeBuilder {
#[allow(clippy::type_complexity)]
factories: Vec<Box<dyn Fn(&Container) + Send + Sync>>,
}
impl ScopeBuilder {
#[inline]
pub fn new() -> Self {
Self {
factories: Vec::new(),
}
}
pub fn with_singleton<T, F>(mut self, factory: F) -> Self
where
T: Injectable + Clone,
F: Fn() -> T + Send + Sync + 'static,
{
self.factories.push(Box::new(move |container| {
container.singleton(factory());
}));
self
}
pub fn with_lazy<T, F>(mut self, factory: F) -> Self
where
T: Injectable,
F: Fn() -> T + Send + Sync + Clone + 'static,
{
self.factories.push(Box::new(move |container| {
let f = factory.clone();
container.lazy(f);
}));
self
}
pub fn with_transient<T, F>(mut self, factory: F) -> Self
where
T: Injectable,
F: Fn() -> T + Send + Sync + Clone + 'static,
{
self.factories.push(Box::new(move |container| {
let f = factory.clone();
container.transient(f);
}));
self
}
pub fn build(&self, parent: &Container) -> ScopedContainer {
let scoped = ScopedContainer::from_parent(parent);
for factory in &self.factories {
factory(&scoped.container);
}
scoped
}
}
impl Default for ScopeBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct GlobalService;
#[derive(Clone)]
struct RequestService {
id: String,
}
#[test]
fn test_scoped_container() {
let root = Container::new();
root.singleton(GlobalService);
let scoped = ScopedContainer::from_parent(&root);
scoped.singleton(RequestService { id: "req-1".into() });
assert!(scoped.contains::<GlobalService>());
assert!(scoped.contains::<RequestService>());
assert!(!root.contains::<RequestService>());
}
#[test]
fn test_scope_ids_unique() {
let s1 = Scope::new();
let s2 = Scope::new();
let s3 = Scope::new();
assert_ne!(s1.id(), s2.id());
assert_ne!(s2.id(), s3.id());
}
#[test]
fn test_scope_builder() {
let root = Container::new();
root.singleton(GlobalService);
let builder = ScopeBuilder::new().with_singleton(|| RequestService { id: "built".into() });
let scoped = builder.build(&root);
assert!(scoped.contains::<GlobalService>());
assert!(scoped.contains::<RequestService>());
let req = scoped.get::<RequestService>().unwrap();
assert_eq!(req.id, "built");
}
#[test]
fn test_scope_display() {
let scope = Scope::new();
let display = format!("{}", scope);
assert!(display.starts_with("scope-"));
}
}