use std::any::Any;
use crate::registry::ServiceRegistry;
reactive_local::surface_local! {
slot STACK: Vec<ServiceRegistry> = vec![ServiceRegistry::new()];
access with_stack, with_stack_ref;
context ServiceContext, ServiceGuard;
}
pub fn provide<T: Any + 'static>(service: T) -> Result<(), crate::registry::ServiceError> {
with_stack(|stack| {
stack
.last_mut()
.expect("service stack is empty — this is a bug")
.insert(service)
})
}
pub fn try_inject<T: Any + Clone + 'static>() -> Option<T> {
with_stack_ref(|stack| stack.last().and_then(|scope| scope.get::<T>()).cloned())
}
pub fn with_service<T: Any + 'static, R>(f: impl FnOnce(&T) -> R) -> Option<R> {
with_stack_ref(|stack| stack.last().and_then(|scope| scope.get::<T>()).map(f))
}
pub struct Scope(());
impl Scope {
pub fn with<R>(f: impl FnOnce() -> R) -> R {
with_stack(|stack| {
let mut new_scope = ServiceRegistry::new();
if let Some(parent) = stack.last() {
new_scope.merge_from(parent);
}
stack.push(new_scope);
});
struct PopGuard;
impl Drop for PopGuard {
fn drop(&mut self) {
with_stack(|stack| {
if stack.len() > 1 {
stack.pop();
}
});
}
}
let _guard = PopGuard;
f()
}
}
#[derive(Clone)]
struct Slot<T>(std::rc::Rc<std::cell::RefCell<T>>);
pub fn set_context<T: Clone + 'static>(value: T) {
match try_inject::<Slot<T>>() {
Some(slot) => *slot.0.borrow_mut() = value,
None => {
let _ = provide(Slot(std::rc::Rc::new(std::cell::RefCell::new(value))));
}
}
}
pub fn context<T: Clone + 'static>() -> Option<T> {
try_inject::<Slot<T>>().map(|slot| slot.0.borrow().clone())
}
#[cfg(test)]
mod context_tests {
use super::*;
#[test]
fn a_second_build_replaces_the_context_the_first_one_set() {
#[derive(Clone, PartialEq, Debug)]
struct Ctx(&'static str);
Scope::with(|| {
set_context(Ctx("first"));
set_context(Ctx("second"));
assert_eq!(context::<Ctx>(), Some(Ctx("second")));
});
}
#[test]
fn an_unset_context_is_absent() {
#[derive(Clone, PartialEq, Debug)]
struct Unset(u8);
Scope::with(|| assert_eq!(context::<Unset>(), None));
}
}