use async_trait::async_trait;
use std::sync::Arc;
use tokio::sync::OnceCell;
#[async_trait]
pub trait Resource: Send + Sync + Sized {
type Config: Clone + Send + Sync;
type Error: std::error::Error + Send + Sync + 'static;
async fn initialize(config: Self::Config) -> Result<Self, Self::Error>;
async fn is_healthy(&self) -> bool {
true
}
async fn cleanup(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
pub struct LazyResource<R: Resource> {
inner: Arc<OnceCell<R>>,
config: R::Config,
}
impl<R: Resource> LazyResource<R> {
pub fn new(config: R::Config) -> Self {
Self {
inner: Arc::new(OnceCell::new()),
config,
}
}
pub async fn get(&self) -> Result<&R, R::Error> {
self.inner
.get_or_try_init(|| R::initialize(self.config.clone()))
.await
}
pub fn is_initialized(&self) -> bool {
self.inner.get().is_some()
}
pub async fn is_healthy(&self) -> Option<bool> {
match self.inner.get() {
Some(resource) => Some(resource.is_healthy().await),
None => None,
}
}
}
impl<R: Resource> Clone for LazyResource<R> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
config: self.config.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
struct TestResource {
value: i32,
#[allow(dead_code)]
init_count: Arc<AtomicU32>,
}
#[derive(Clone)]
struct TestConfig {
value: i32,
init_count: Arc<AtomicU32>,
}
#[async_trait]
impl Resource for TestResource {
type Config = TestConfig;
type Error = std::io::Error;
async fn initialize(config: Self::Config) -> Result<Self, Self::Error> {
config.init_count.fetch_add(1, Ordering::SeqCst);
Ok(Self {
value: config.value,
init_count: config.init_count,
})
}
async fn is_healthy(&self) -> bool {
self.value > 0
}
async fn cleanup(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
#[tokio::test]
async fn test_lazy_resource_initialization() {
let init_count = Arc::new(AtomicU32::new(0));
let resource = LazyResource::<TestResource>::new(TestConfig {
value: 42,
init_count: init_count.clone(),
});
assert!(!resource.is_initialized());
assert_eq!(init_count.load(Ordering::SeqCst), 0);
let instance = resource.get().await.unwrap();
assert_eq!(instance.value, 42);
assert!(resource.is_initialized());
assert_eq!(init_count.load(Ordering::SeqCst), 1);
let instance2 = resource.get().await.unwrap();
assert_eq!(instance2.value, 42);
assert_eq!(init_count.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_lazy_resource_cloning() {
let init_count = Arc::new(AtomicU32::new(0));
let resource = LazyResource::<TestResource>::new(TestConfig {
value: 42,
init_count: init_count.clone(),
});
let cloned = resource.clone();
assert!(!cloned.is_initialized());
let instance = resource.get().await.unwrap();
assert_eq!(instance.value, 42);
assert_eq!(init_count.load(Ordering::SeqCst), 1);
assert!(cloned.is_initialized());
let cloned_instance = cloned.get().await.unwrap();
assert_eq!(cloned_instance.value, 42);
assert_eq!(init_count.load(Ordering::SeqCst), 1); }
#[tokio::test]
async fn test_resource_health_check() {
let init_count = Arc::new(AtomicU32::new(0));
let resource = LazyResource::<TestResource>::new(TestConfig {
value: 100,
init_count,
});
assert_eq!(resource.is_healthy().await, None);
let _instance = resource.get().await.unwrap();
assert_eq!(resource.is_healthy().await, Some(true));
}
#[tokio::test]
async fn test_resource_health_check_unhealthy() {
let init_count = Arc::new(AtomicU32::new(0));
let resource = LazyResource::<TestResource>::new(TestConfig {
value: -1, init_count,
});
let _instance = resource.get().await.unwrap();
assert_eq!(resource.is_healthy().await, Some(false));
}
}