use std::any::TypeId;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::OnceLock;
use crate::core::{AutoBuilder, BuildFn};
use crate::error::TraitKitError;
use super::kit::LazySlot;
#[cfg(feature = "scope")]
pub struct Scope {
lazy_slots: RefCell<HashMap<TypeId, LazySlot>>,
}
#[cfg(feature = "scope")]
impl Scope {
#[must_use]
pub fn new() -> Self {
Scope {
lazy_slots: RefCell::new(HashMap::new()),
}
}
pub fn register<M: AutoBuilder>(&mut self) -> Result<(), TraitKitError> {
let type_id = TypeId::of::<M>();
if self.lazy_slots.borrow().contains_key(&type_id) {
return Err(TraitKitError::AlreadyRegistered { module: M::NAME });
}
let build_fn: BuildFn = Box::new(|kit| {
let cap = M::build(kit)
.map_err(|e| -> Box<dyn std::error::Error + Send + 'static> { Box::new(e) })?;
Ok(Box::new(cap) as Box<dyn std::any::Any>)
});
self.lazy_slots.borrow_mut().insert(
type_id,
LazySlot {
builder: Some(build_fn),
cell: OnceLock::new(),
},
);
Ok(())
}
pub fn require<M: AutoBuilder>(&self) -> Result<M::Capability, TraitKitError> {
let type_id = TypeId::of::<M>();
if let Some(boxed) = self
.lazy_slots
.borrow()
.get(&type_id)
.and_then(|slot| slot.cell.get())
{
return boxed.downcast_ref::<M::Capability>().cloned().ok_or(
TraitKitError::MissingCapability {
key: M::NAME.to_string(),
},
);
}
let builder = self
.lazy_slots
.borrow_mut()
.get_mut(&type_id)
.and_then(|slot| slot.builder.take());
if let Some(builder) = builder {
let temp_kit = crate::kit::Kit::new();
let boxed = (builder)(&temp_kit).map_err(|e| TraitKitError::BuildFailed {
context: M::NAME.to_string(),
source: e,
})?;
if let Some(slot) = self.lazy_slots.borrow().get(&type_id) {
let _ = slot.cell.set(boxed);
}
return self
.lazy_slots
.borrow()
.get(&type_id)
.and_then(|slot| slot.cell.get())
.and_then(|b| b.downcast_ref::<M::Capability>().cloned())
.ok_or(TraitKitError::MissingCapability {
key: M::NAME.to_string(),
});
}
Err(TraitKitError::MissingCapability {
key: M::NAME.to_string(),
})
}
#[must_use]
pub fn contains<M: AutoBuilder>(&self) -> bool {
let type_id = TypeId::of::<M>();
self.lazy_slots.borrow().contains_key(&type_id)
}
}
#[cfg(feature = "scope")]
impl Default for Scope {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "scope")]
impl Drop for Scope {
fn drop(&mut self) {
self.lazy_slots.borrow_mut().clear();
}
}
#[cfg(all(feature = "scope", feature = "async"))]
mod async_scope {
use std::any::TypeId;
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use crate::core::AsyncAutoBuilder;
use crate::error::TraitKitError;
use crate::kit::AsyncTypeMap;
pub struct AsyncScope {
capabilities: AsyncTypeMap,
builders: Arc<RwLock<HashSet<TypeId>>>,
}
impl AsyncScope {
#[must_use]
pub fn new() -> Self {
AsyncScope {
capabilities: AsyncTypeMap::new(),
builders: Arc::new(RwLock::new(HashSet::new())),
}
}
pub fn register<M: AsyncAutoBuilder>(&mut self) -> Result<(), TraitKitError> {
let type_id = TypeId::of::<M>();
let mut guard = self.builders.write().expect("lock poisoned");
if guard.contains(&type_id) {
return Err(TraitKitError::AlreadyRegistered { module: M::NAME });
}
guard.insert(type_id);
Ok(())
}
pub fn insert<M: AsyncAutoBuilder>(&self, capability: M::Capability)
where
M::Capability: Send + Sync + 'static,
{
self.capabilities
.insert_boxed(TypeId::of::<M>(), Box::new(capability));
}
pub fn require<M: AsyncAutoBuilder>(&self) -> Result<M::Capability, TraitKitError>
where
M::Capability: Clone + Send + Sync + 'static,
{
let type_id = TypeId::of::<M>();
self.capabilities
.get_cloned_by_type_id::<M::Capability>(type_id)
.ok_or(TraitKitError::MissingCapability {
key: M::NAME.to_string(),
})
}
#[must_use]
pub fn contains<M: AsyncAutoBuilder>(&self) -> bool {
let type_id = TypeId::of::<M>();
self.capabilities.contains_by_type_id(type_id)
|| self
.builders
.read()
.expect("lock poisoned")
.contains(&type_id)
}
}
impl Default for AsyncScope {
fn default() -> Self {
Self::new()
}
}
}
#[cfg(all(feature = "scope", feature = "async"))]
pub use async_scope::AsyncScope;
#[cfg(all(test, feature = "scope"))]
mod tests {
use super::*;
use crate::core::{AutoBuilder, ModuleMeta};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
static SCOPE_COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone)]
struct ScopeCap {
id: usize,
}
#[derive(Debug)]
struct ScopeTestError;
impl std::fmt::Display for ScopeTestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "scope error")
}
}
impl std::error::Error for ScopeTestError {}
struct ScopeModule;
impl ModuleMeta for ScopeModule {
const NAME: &'static str = "scope-module";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AutoBuilder for ScopeModule {
type Capability = Arc<ScopeCap>;
type Error = ScopeTestError;
fn build(_kit: &crate::kit::Kit) -> Result<Arc<ScopeCap>, ScopeTestError> {
let id = SCOPE_COUNTER.fetch_add(1, Ordering::Relaxed);
Ok(Arc::new(ScopeCap { id }))
}
}
#[test]
fn scope_new_is_empty() {
let scope = Scope::new();
assert!(!scope.contains::<ScopeModule>());
}
#[test]
fn scope_register_then_require() {
let mut scope = Scope::new();
scope
.register::<ScopeModule>()
.expect("register should succeed");
assert!(scope.contains::<ScopeModule>());
let cap = scope
.require::<ScopeModule>()
.expect("require should succeed");
assert!(cap.id < usize::MAX);
}
#[test]
fn scope_require_caches_result() {
let mut scope = Scope::new();
scope.register::<ScopeModule>().expect("register");
let cap1 = scope.require::<ScopeModule>().expect("require 1");
let cap2 = scope.require::<ScopeModule>().expect("require 2");
assert_eq!(cap1.id, cap2.id, "scope should cache the built instance");
}
#[test]
fn scope_register_duplicate_returns_error() {
let mut scope = Scope::new();
scope.register::<ScopeModule>().expect("first register");
let err = scope.register::<ScopeModule>().unwrap_err();
assert!(matches!(
err,
TraitKitError::AlreadyRegistered {
module: "scope-module"
}
));
}
#[test]
fn scope_require_unregistered_returns_missing() {
let scope = Scope::new();
let err = scope.require::<ScopeModule>().unwrap_err();
assert!(matches!(
err,
TraitKitError::MissingCapability {
ref key
} if key == "scope-module"
));
}
#[test]
fn scope_default_creates_empty() {
let scope = Scope::default();
assert!(!scope.contains::<ScopeModule>());
}
#[test]
fn scope_drop_clears_resources() {
let mut scope = Scope::new();
scope.register::<ScopeModule>().expect("register");
assert!(scope.contains::<ScopeModule>());
drop(scope);
}
#[test]
fn scope_test_error_display() {
let e = ScopeTestError;
assert_eq!(format!("{e}"), "scope error");
}
#[test]
fn scope_module_dependencies_empty() {
let deps = ScopeModule::dependencies();
assert!(deps.is_empty());
}
}
#[cfg(all(test, feature = "scope", feature = "async"))]
mod async_tests {
use super::*;
use crate::core::{AsyncAutoBuilder, ModuleMeta};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
struct AsyncScopeCap {
value: i32,
}
#[derive(Debug)]
struct AsyncScopeError;
impl std::fmt::Display for AsyncScopeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "async scope error")
}
}
impl std::error::Error for AsyncScopeError {}
struct AsyncScopeModule;
impl ModuleMeta for AsyncScopeModule {
const NAME: &'static str = "async-scope-module";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for AsyncScopeModule {
type Capability = Arc<AsyncScopeCap>;
type Error = AsyncScopeError;
fn build<'a>(
_kit: &'a crate::kit::AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Arc<AsyncScopeCap>, AsyncScopeError>> + Send + 'a>>
{
Box::pin(async move { Ok(Arc::new(AsyncScopeCap { value: 99 })) })
}
}
#[test]
fn async_scope_new_is_empty() {
let scope = AsyncScope::new();
assert!(!scope.contains::<AsyncScopeModule>());
}
#[test]
fn async_scope_register_then_contains() {
let mut scope = AsyncScope::new();
scope.register::<AsyncScopeModule>().expect("register");
assert!(scope.contains::<AsyncScopeModule>());
}
#[test]
fn async_scope_register_duplicate_returns_error() {
let mut scope = AsyncScope::new();
scope
.register::<AsyncScopeModule>()
.expect("first register");
let err = scope.register::<AsyncScopeModule>().unwrap_err();
assert!(matches!(
err,
TraitKitError::AlreadyRegistered {
module: "async-scope-module"
}
));
}
#[test]
fn async_scope_default_is_empty() {
let scope = AsyncScope::default();
assert!(!scope.contains::<AsyncScopeModule>());
}
#[test]
fn async_scope_module_build_and_require() {
let mut scope = AsyncScope::new();
scope.register::<AsyncScopeModule>().expect("register");
assert!(scope.contains::<AsyncScopeModule>());
let cap = Arc::new(AsyncScopeCap { value: 42 });
scope.insert::<AsyncScopeModule>(cap.clone());
let retrieved = scope
.require::<AsyncScopeModule>()
.expect("require should succeed");
assert_eq!(retrieved.value, 42);
}
#[test]
fn async_scope_require_missing_returns_error() {
let scope = AsyncScope::new();
let err = scope.require::<AsyncScopeModule>().unwrap_err();
assert!(matches!(
err,
TraitKitError::MissingCapability {
ref key
} if key == "async-scope-module"
));
}
#[test]
fn async_scope_error_display() {
let e = AsyncScopeError;
assert_eq!(format!("{e}"), "async scope error");
}
#[test]
fn async_scope_module_dependencies_empty() {
let deps = AsyncScopeModule::dependencies();
assert!(deps.is_empty());
}
}