use std::sync::Arc;
use async_trait::async_trait;
use continuo::Registry;
use continuo::{Bootable, Component, ComponentOrder, Finalizable, Result};
use std::sync::{
Mutex,
atomic::{AtomicBool, Ordering},
};
struct Settings {
value: usize,
}
trait Value: Send + Sync {
fn value(&self) -> usize;
}
impl Value for Settings {
fn value(&self) -> usize {
self.value
}
}
#[test]
fn ordinary_values_and_interfaces_need_no_lifecycle_traits() {
let registry = Registry::<()>::new();
let settings = Arc::new(Settings { value: 7 });
registry.insert(settings.clone()).unwrap();
assert!(Arc::ptr_eq(&settings, ®istry.resolve::<Settings>().unwrap()));
assert_eq!(
registry.with_typed::<Settings, _>(|value| value.value),
Some(7)
);
registry.bind_dyn::<dyn Value, Settings>(|value| value).unwrap();
assert_eq!(registry.resolve_dyn::<dyn Value>().unwrap().value(), 7);
assert!(registry.components().is_empty());
assert!(registry.insert(Arc::new(Settings { value: 9 })).is_err());
assert!(Arc::ptr_eq(&settings, ®istry.resolve::<Settings>().unwrap()));
}
#[derive(Default)]
struct PreparationState {
registry: Registry<Self>,
steps: Mutex<Vec<&'static str>>,
}
struct PreparedService {
value: usize,
}
struct PreparedDependency;
struct Constructor {
fail: bool,
}
#[async_trait]
impl Component<PreparationState> for Constructor {
type Deps = ();
fn name(&self) -> &str {
"constructor"
}
fn as_bootable(&self) -> Option<&dyn Bootable<PreparationState>> {
Some(self)
}
}
#[async_trait]
impl Bootable<PreparationState> for Constructor {
async fn boot(
&self,
_deps: (),
state: &PreparationState,
) -> Result<()> {
if self.fail {
return Err(continuo::Error::msg("construction rejected"));
}
state.steps.lock().unwrap().push("construct");
state.registry.insert(Arc::new(PreparedService { value: 7 }))?;
state.registry.insert(Arc::new(PreparedDependency))?;
state.registry.register(Arc::new(LateDependency))?;
Ok(())
}
}
struct LateDependency;
#[async_trait]
impl Component<PreparationState> for LateDependency {
type Deps = ();
fn name(&self) -> &str {
"late-dependency"
}
fn as_bootable(&self) -> Option<&dyn Bootable<PreparationState>> {
Some(self)
}
}
#[async_trait]
impl Bootable<PreparationState> for LateDependency {
async fn boot(
&self,
_deps: (),
state: &PreparationState,
) -> Result<()> {
state.steps.lock().unwrap().push("dependency");
Ok(())
}
}
struct Consumer;
#[async_trait]
impl Component<PreparationState> for Consumer {
type Deps = ();
fn name(&self) -> &str {
"consumer"
}
fn order(&self) -> ComponentOrder {
ComponentOrder::new().after::<Constructor>().after::<LateDependency>()
}
fn as_bootable(&self) -> Option<&dyn Bootable<PreparationState>> {
Some(self)
}
}
#[async_trait]
impl Bootable<PreparationState> for Consumer {
async fn boot(
&self,
_deps: (),
state: &PreparationState,
) -> Result<()> {
assert_eq!(
state.registry.resolve::<PreparedService>().unwrap().value,
7
);
state.steps.lock().unwrap().push("consumer");
Ok(())
}
}
#[tokio::test]
async fn construction_publishes_complete_di_values_and_replans_dependencies() {
let state = PreparationState::default();
state.registry.register(Arc::new(Consumer)).unwrap();
state.registry.register(Arc::new(Constructor { fail: false })).unwrap();
assert!(state.registry.resolve::<PreparedService>().is_none());
state.registry.boot_all(&state).await.unwrap();
assert_eq!(
*state.steps.lock().unwrap(),
["construct", "dependency", "consumer"]
);
assert!(state.registry.resolve::<PreparedDependency>().is_some());
}
#[tokio::test]
async fn rejected_construction_publishes_no_empty_service() {
let state = PreparationState::default();
state.registry.register(Arc::new(Constructor { fail: true })).unwrap();
assert!(state.registry.boot_all(&state).await.is_err());
assert!(state.registry.resolve::<PreparedService>().is_none());
assert!(state.registry.resolve::<PreparedDependency>().is_none());
}
struct Resource(AtomicBool);
#[async_trait]
impl Finalizable<()> for Resource {
async fn finalize(
&self,
_: &(),
) -> Result<()> {
self.0.store(true, Ordering::Relaxed);
Ok(())
}
}
struct Cleanup(Arc<Resource>);
#[async_trait]
impl Component<()> for Cleanup {
type Deps = ();
fn name(&self) -> &str {
"cleanup"
}
fn as_finalizable(&self) -> Option<&dyn Finalizable<()>> {
Some(self.0.as_ref())
}
}
#[tokio::test]
async fn finalization_needs_neither_a_runnable_nor_di() {
let registry = Registry::new();
let resource = Arc::new(Resource(AtomicBool::new(false)));
registry.register(Arc::new(Cleanup(resource.clone()))).unwrap();
assert!(registry.resolve::<Resource>().is_none());
assert!(
registry
.components()
.iter()
.all(|entry| entry.clone().runnables().is_empty())
);
let outcome = registry.finalize_all(&()).await.unwrap();
assert!(outcome.is_complete());
assert_eq!(outcome.finalized_count(), 1);
assert!(resource.0.load(Ordering::Relaxed));
}