use std::sync::OnceLock;
pub struct LazyService<T> {
inner: OnceLock<T>,
factory: Box<dyn Fn() -> T + Send + Sync>,
}
impl<T> LazyService<T> {
pub fn new<F>(factory: F) -> Self
where
F: Fn() -> T + Send + Sync + 'static,
{
Self {
inner: OnceLock::new(),
factory: Box::new(factory),
}
}
pub fn get(&self) -> &T {
self.inner.get_or_init(|| (self.factory)())
}
pub fn is_initialized(&self) -> bool {
self.inner.get().is_some()
}
pub fn try_get(&self) -> Option<&T> {
self.inner.get()
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for LazyService<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.try_get() {
Some(service) => f.debug_tuple("LazyService").field(service).finish(),
None => f
.debug_struct("LazyService")
.field("status", &"uninitialized")
.finish(),
}
}
}
impl<T: Clone> Clone for LazyService<T> {
fn clone(&self) -> Self {
match self.try_get() {
Some(value) => Self {
inner: OnceLock::from(value.clone()),
factory: Box::new(|| unreachable!("Already initialized")),
},
None => Self {
inner: OnceLock::new(),
factory: Box::new(|| unreachable!("Cannot clone uninitialized LazyService")),
},
}
}
}
pub trait LazyClientTrait: Send + Sync {
fn service_type() -> &'static str;
fn is_available(&self) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
struct TestService {
value: String,
}
#[test]
fn test_lazy_service_initialization() {
let lazy = LazyService::new(|| TestService {
value: "test".to_string(),
});
assert!(!lazy.is_initialized());
assert!(lazy.try_get().is_none());
let service = lazy.get();
assert_eq!(service.value, "test");
assert!(lazy.is_initialized());
let service2 = lazy.get();
assert!(std::ptr::eq(service, service2));
}
#[test]
fn test_lazy_service_clone_initialized() {
let lazy = LazyService::new(|| TestService {
value: "original".to_string(),
});
let _ = lazy.get();
let cloned = lazy.clone();
assert!(cloned.is_initialized());
assert_eq!(cloned.get().value, "original");
}
#[test]
fn test_lazy_service_debug() {
let lazy: LazyService<TestService> = LazyService::new(|| TestService {
value: "debug_test".to_string(),
});
let debug_uninit = format!("{:?}", lazy);
assert!(debug_uninit.contains("uninitialized"));
let _ = lazy.get();
let debug_init = format!("{:?}", lazy);
assert!(debug_init.contains("debug_test"));
}
}