use async_trait::async_trait;
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use tracing::warn;
use crate::proxy::ProxyResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadStrategy {
Eager,
Lazy,
Select,
Joined,
}
#[async_trait]
pub trait LazyLoadable: Send + Sync {
type Data: Clone + Send + Sync;
fn is_loaded(&self) -> bool;
async fn load(&mut self) -> ProxyResult<()>;
async fn get(&mut self) -> ProxyResult<Self::Data>;
fn get_if_loaded(&self) -> Option<Self::Data>;
}
pub struct LazyLoaded<T, F>
where
T: Clone + Send + Sync,
F: Fn() -> futures::future::BoxFuture<'static, ProxyResult<T>> + Send + Sync,
{
data: Arc<RwLock<Option<T>>>,
loader: Arc<F>,
}
impl<T, F> LazyLoaded<T, F>
where
T: Clone + Send + Sync,
F: Fn() -> futures::future::BoxFuture<'static, ProxyResult<T>> + Send + Sync,
{
pub fn new(loader: F) -> Self {
Self {
data: Arc::new(RwLock::new(None)),
loader: Arc::new(loader),
}
}
pub fn preloaded(data: T, loader: F) -> Self {
Self {
data: Arc::new(RwLock::new(Some(data))),
loader: Arc::new(loader),
}
}
fn read_lock(&self) -> RwLockReadGuard<'_, Option<T>> {
self.data.read().unwrap_or_else(|e| {
warn!("RwLock poisoned on read, recovering with inner value");
e.into_inner()
})
}
fn write_lock(&self) -> RwLockWriteGuard<'_, Option<T>> {
self.data.write().unwrap_or_else(|e| {
warn!("RwLock poisoned on write, recovering with inner value");
e.into_inner()
})
}
pub fn is_loaded(&self) -> bool {
self.read_lock().is_some()
}
pub async fn load(&self) -> ProxyResult<()> {
if self.is_loaded() {
return Ok(());
}
let data = (self.loader)().await?;
let mut guard = self.write_lock();
*guard = Some(data);
Ok(())
}
pub async fn get(&self) -> ProxyResult<T> {
self.load().await?;
let cached = {
let guard = self.read_lock();
guard.as_ref().cloned()
};
match cached {
Some(data) => Ok(data),
None => {
let data = (self.loader)().await?;
let mut write_guard = self.write_lock();
*write_guard = Some(data.clone());
Ok(data)
}
}
}
pub fn get_if_loaded(&self) -> Option<T> {
self.read_lock().as_ref().cloned()
}
pub fn reset(&self) {
let mut guard = self.write_lock();
*guard = None;
}
}
#[derive(Debug, Clone)]
pub struct EagerLoadConfig {
pub max_depth: usize,
pub relationships: Vec<String>,
}
impl EagerLoadConfig {
pub fn new() -> Self {
Self {
max_depth: 2,
relationships: Vec::new(),
}
}
pub fn with_relationship(mut self, relationship: &str) -> Self {
self.relationships.push(relationship.to_string());
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
}
impl Default for EagerLoadConfig {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
pub trait EagerLoadable: Send + Sync {
async fn eager_load(&mut self, config: &EagerLoadConfig) -> ProxyResult<()>;
fn is_relationship_loaded(&self, name: &str) -> bool;
}
pub struct RelationshipCache {
cache: Arc<RwLock<std::collections::HashMap<String, Box<dyn std::any::Any + Send + Sync>>>>,
}
impl RelationshipCache {
pub fn new() -> Self {
Self {
cache: Arc::new(RwLock::new(std::collections::HashMap::new())),
}
}
fn read_lock(
&self,
) -> RwLockReadGuard<'_, std::collections::HashMap<String, Box<dyn std::any::Any + Send + Sync>>>
{
self.cache.read().unwrap_or_else(|e| {
warn!("RelationshipCache RwLock poisoned on read, recovering with inner value");
e.into_inner()
})
}
fn write_lock(
&self,
) -> RwLockWriteGuard<'_, std::collections::HashMap<String, Box<dyn std::any::Any + Send + Sync>>>
{
self.cache.write().unwrap_or_else(|e| {
warn!("RelationshipCache RwLock poisoned on write, recovering with inner value");
e.into_inner()
})
}
pub fn contains(&self, key: &str) -> bool {
self.read_lock().contains_key(key)
}
pub fn get<T>(&self, key: &str) -> Option<T>
where
T: 'static + Clone,
{
let cache = self.read_lock();
cache.get(key).and_then(|v| v.downcast_ref::<T>().cloned())
}
pub fn set<T: 'static + Send + Sync>(&self, key: String, value: T) {
let mut cache = self.write_lock();
cache.insert(key, Box::new(value));
}
pub fn remove(&self, key: &str) -> bool {
let mut cache = self.write_lock();
cache.remove(key).is_some()
}
pub fn clear(&self) {
let mut cache = self.write_lock();
cache.clear();
}
}
impl Default for RelationshipCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_lazy_loaded() {
let lazy = LazyLoaded::new(|| Box::pin(async { Ok(vec![1, 2, 3]) }));
assert!(!lazy.is_loaded());
lazy.load().await.unwrap();
assert!(lazy.is_loaded());
let data = lazy.get_if_loaded().unwrap();
assert_eq!(data, vec![1, 2, 3]);
}
#[tokio::test]
async fn test_lazy_loaded_preloaded() {
let lazy = LazyLoaded::preloaded(vec![1, 2, 3], || Box::pin(async { Ok(vec![4, 5, 6]) }));
assert!(lazy.is_loaded());
let data = lazy.get_if_loaded().unwrap();
assert_eq!(data, vec![1, 2, 3]);
}
#[test]
fn test_eager_load_config() {
let config = EagerLoadConfig::new()
.with_relationship("posts")
.with_relationship("comments")
.max_depth(5);
assert_eq!(config.max_depth, 5);
assert_eq!(config.relationships, vec!["posts", "comments"]);
}
#[tokio::test]
async fn test_lazy_loaded_get_returns_owned_clone() {
let lazy = LazyLoaded::new(|| Box::pin(async { Ok(vec![10, 20, 30]) }));
let data = lazy.get().await.unwrap();
assert_eq!(data, vec![10, 20, 30]);
assert!(lazy.is_loaded());
}
#[tokio::test]
async fn test_lazy_loaded_get_if_loaded_returns_none_when_not_loaded() {
let lazy = LazyLoaded::new(|| Box::pin(async { Ok(42) }));
let result = lazy.get_if_loaded();
assert_eq!(result, None);
}
#[tokio::test]
async fn test_lazy_loaded_get_if_loaded_returns_cloned_value() {
let lazy = LazyLoaded::preloaded(String::from("hello"), || {
Box::pin(async { Ok(String::from("world")) })
});
let result = lazy.get_if_loaded();
assert_eq!(result, Some(String::from("hello")));
}
#[tokio::test]
async fn test_lazy_loaded_reset_forces_reload() {
let lazy = LazyLoaded::preloaded(vec![1], || Box::pin(async { Ok(vec![2]) }));
assert!(lazy.is_loaded());
lazy.reset();
assert!(!lazy.is_loaded());
assert_eq!(lazy.get_if_loaded(), None);
let data = lazy.get().await.unwrap();
assert_eq!(data, vec![2]);
}
#[test]
fn test_relationship_cache() {
let cache = RelationshipCache::new();
assert!(!cache.contains("key1"));
cache.set("key1".to_string(), vec![1, 2, 3]);
assert!(cache.contains("key1"));
let value: Vec<i32> = cache.get("key1").unwrap();
assert_eq!(value, vec![1, 2, 3]);
assert!(cache.remove("key1"));
assert!(!cache.contains("key1"));
}
}