#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "async")]
use std::pin::Pin;
#[cfg(feature = "lifecycle")]
pub trait Lifecycle: crate::core::AutoBuilder {
#[must_use = "on_ready returns Result<(), Error>; ignoring it may hide initialization failures"]
fn on_ready(_kit: &crate::kit::Kit<crate::kit::Ready>) -> Result<(), Self::Error> {
Ok(())
}
fn on_shutdown(_cap: &Self::Capability) {}
}
#[cfg(all(feature = "lifecycle", feature = "async"))]
pub trait AsyncLifecycle: crate::core::AsyncAutoBuilder {
#[allow(
clippy::type_complexity,
reason = "Pin<Box<dyn Future + Send>> is the canonical dyn-compatible async dispatch type"
)]
#[must_use]
fn on_ready<'a>(
_kit: &'a crate::kit::AsyncKit<crate::kit::async_kit::Ready>,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'a>> {
Box::pin(async { Ok(()) })
}
#[allow(
clippy::type_complexity,
reason = "Pin<Box<dyn Future>> is the canonical dyn-compatible async dispatch type"
)]
fn on_shutdown<'a>(
_cap: &'a Self::Capability,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async {})
}
}
#[cfg(all(test, feature = "lifecycle"))]
mod tests {
use super::*;
use crate::core::{AutoBuilder, ModuleMeta};
use crate::kit::Kit;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
struct TestCap;
#[derive(Debug)]
struct TestError;
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "test error")
}
}
impl std::error::Error for TestError {}
struct TestModule;
impl ModuleMeta for TestModule {
const NAME: &'static str = "test-lifecycle";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AutoBuilder for TestModule {
type Capability = Arc<TestCap>;
type Error = TestError;
fn build(_kit: &Kit) -> Result<Arc<TestCap>, TestError> {
Ok(Arc::new(TestCap))
}
}
static SHUTDOWN_COUNTER: AtomicUsize = AtomicUsize::new(0);
impl Lifecycle for TestModule {
fn on_ready(_kit: &Kit<crate::kit::Ready>) -> Result<(), TestError> {
Ok(())
}
fn on_shutdown(_cap: &Arc<TestCap>) {
SHUTDOWN_COUNTER.fetch_add(1, Ordering::Relaxed);
}
}
#[test]
fn lifecycle_trait_has_default_on_ready() {
struct DefaultModule;
impl ModuleMeta for DefaultModule {
const NAME: &'static str = "default-lifecycle";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AutoBuilder for DefaultModule {
type Capability = Arc<TestCap>;
type Error = TestError;
fn build(_kit: &Kit) -> Result<Arc<TestCap>, TestError> {
Ok(Arc::new(TestCap))
}
}
impl Lifecycle for DefaultModule {}
let mut kit = Kit::new();
kit.register::<DefaultModule>().unwrap();
kit.register_lifecycle::<DefaultModule>();
let built = kit.build().unwrap();
drop(built);
}
#[test]
fn lifecycle_trait_has_default_on_shutdown() {
struct DefaultShutdownModule;
impl ModuleMeta for DefaultShutdownModule {
const NAME: &'static str = "default-shutdown";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AutoBuilder for DefaultShutdownModule {
type Capability = Arc<TestCap>;
type Error = TestError;
fn build(_kit: &Kit) -> Result<Arc<TestCap>, TestError> {
Ok(Arc::new(TestCap))
}
}
impl Lifecycle for DefaultShutdownModule {
fn on_ready(_kit: &Kit<crate::kit::Ready>) -> Result<(), TestError> {
Ok(())
}
}
let mut kit = Kit::new();
kit.register::<DefaultShutdownModule>().unwrap();
kit.register_lifecycle::<DefaultShutdownModule>();
let built = kit.build().unwrap();
built.shutdown(); }
#[test]
fn lifecycle_shutdown_counter_increments() {
let before = SHUTDOWN_COUNTER.load(Ordering::Relaxed);
let cap = Arc::new(TestCap);
TestModule::on_shutdown(&cap);
let after = SHUTDOWN_COUNTER.load(Ordering::Relaxed);
assert_eq!(after, before + 1, "shutdown counter should increment");
}
#[test]
fn lifecycle_test_module_full_kit_integration() {
let mut kit = Kit::new();
kit.register::<TestModule>().unwrap();
kit.register_lifecycle::<TestModule>();
let built = kit.build().unwrap();
built.shutdown();
}
#[test]
fn lifecycle_test_error_display() {
let e = TestError;
assert_eq!(format!("{e}"), "test error");
}
}
#[cfg(all(test, feature = "lifecycle", feature = "async"))]
mod async_tests {
use super::*;
use crate::core::{AsyncAutoBuilder, ModuleMeta};
use crate::kit::AsyncKit;
use crate::test_helpers::block_on;
use std::sync::Arc;
#[derive(Debug, Clone)]
struct AsyncTestCap;
#[derive(Debug)]
struct AsyncTestError;
impl std::fmt::Display for AsyncTestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "async test error")
}
}
impl std::error::Error for AsyncTestError {}
struct AsyncTestModule;
impl ModuleMeta for AsyncTestModule {
const NAME: &'static str = "async-lifecycle";
fn dependencies() -> &'static [(&'static str, std::any::TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for AsyncTestModule {
type Capability = Arc<AsyncTestCap>;
type Error = AsyncTestError;
fn build<'a>(
_kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Arc<AsyncTestCap>, AsyncTestError>> + Send + 'a>>
{
Box::pin(async move { Ok(Arc::new(AsyncTestCap)) })
}
}
impl AsyncLifecycle for AsyncTestModule {}
#[test]
fn async_lifecycle_default_on_ready_returns_ok() {
let kit = AsyncKit::new();
let built = block_on(kit.build()).expect("build should succeed");
let result = block_on(AsyncTestModule::on_ready(&built));
assert!(result.is_ok(), "default on_ready should return Ok");
}
#[test]
fn async_lifecycle_default_on_shutdown_completes() {
let cap = Arc::new(AsyncTestCap);
block_on(AsyncTestModule::on_shutdown(&cap));
}
#[test]
fn async_lifecycle_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AsyncTestCap>();
}
#[test]
fn async_lifecycle_test_module_full_kit_integration() {
let mut kit = AsyncKit::new();
kit.register::<AsyncTestModule>().unwrap();
kit.register_lifecycle::<AsyncTestModule>();
let built = block_on(kit.build()).unwrap();
built.shutdown();
}
#[test]
fn async_lifecycle_test_error_display() {
let e = AsyncTestError;
assert_eq!(format!("{e}"), "async test error");
}
}