use super::super::metadata::RelationshipMetadata;
use crate::error::{ModelError, ModelResult};
use std::fmt::Debug;
use std::marker::PhantomData;
#[derive(Debug, Clone, PartialEq)]
pub enum RelationshipLoadingState {
NotLoaded,
Loading,
Loaded,
Failed(String),
}
pub trait RelationshipContainer<T>: Debug + Clone + Send + Sync {
fn is_loaded(&self) -> bool;
fn loading_state(&self) -> &RelationshipLoadingState;
fn get(&self) -> Option<&T>;
fn get_mut(&mut self) -> Option<&mut T>;
fn set_loaded(&mut self, data: T);
fn set_loading(&mut self);
fn set_failed(&mut self, error: String);
fn reset(&mut self);
fn take(&mut self) -> Option<T>;
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypeSafeRelationship<T> {
metadata: RelationshipMetadata,
loading_state: RelationshipLoadingState,
data: Option<T>,
eager_load: bool,
_phantom: PhantomData<T>,
}
impl<T> TypeSafeRelationship<T>
where
T: Clone + Debug + Send + Sync,
{
pub fn new(metadata: RelationshipMetadata) -> Self {
let eager_load = metadata.eager_load;
Self {
metadata,
loading_state: RelationshipLoadingState::NotLoaded,
data: None,
eager_load,
_phantom: PhantomData,
}
}
pub fn metadata(&self) -> &RelationshipMetadata {
&self.metadata
}
pub fn name(&self) -> &str {
&self.metadata.name
}
pub fn is_eager(&self) -> bool {
self.eager_load
}
pub fn set_eager(&mut self, eager: bool) {
self.eager_load = eager;
}
pub fn get_typed(&self) -> Option<&T> {
if matches!(self.loading_state, RelationshipLoadingState::Loaded) {
self.data.as_ref()
} else {
None
}
}
pub fn get_typed_mut(&mut self) -> Option<&mut T> {
if matches!(self.loading_state, RelationshipLoadingState::Loaded) {
self.data.as_mut()
} else {
None
}
}
pub fn unwrap(&self) -> &T {
self.get_typed().expect("Relationship not loaded")
}
pub fn try_get(&self) -> ModelResult<&T> {
match &self.loading_state {
RelationshipLoadingState::Loaded => self.data.as_ref().ok_or_else(|| {
ModelError::Configuration("Relationship marked as loaded but no data".to_string())
}),
RelationshipLoadingState::NotLoaded => Err(ModelError::Configuration(format!(
"Relationship '{}' not loaded",
self.name()
))),
RelationshipLoadingState::Loading => Err(ModelError::Configuration(format!(
"Relationship '{}' is currently loading",
self.name()
))),
RelationshipLoadingState::Failed(error) => Err(ModelError::Configuration(format!(
"Relationship '{}' failed to load: {}",
self.name(),
error
))),
}
}
pub fn map<U, F>(self, f: F) -> TypeSafeRelationship<U>
where
U: Clone + Debug + Send + Sync,
F: FnOnce(T) -> U,
{
let mut new_rel = TypeSafeRelationship::new(self.metadata);
new_rel.loading_state = self.loading_state.clone();
new_rel.eager_load = self.eager_load;
if let Some(data) = self.data {
new_rel.data = Some(f(data));
}
new_rel
}
pub fn if_loaded<F>(&self, f: F) -> Option<()>
where
F: FnOnce(&T),
{
if let Some(data) = self.get_typed() {
f(data);
Some(())
} else {
None
}
}
}
impl<T> RelationshipContainer<T> for TypeSafeRelationship<T>
where
T: Clone + Debug + Send + Sync,
{
fn is_loaded(&self) -> bool {
matches!(self.loading_state, RelationshipLoadingState::Loaded)
}
fn loading_state(&self) -> &RelationshipLoadingState {
&self.loading_state
}
fn get(&self) -> Option<&T> {
self.get_typed()
}
fn get_mut(&mut self) -> Option<&mut T> {
self.get_typed_mut()
}
fn set_loaded(&mut self, data: T) {
self.data = Some(data);
self.loading_state = RelationshipLoadingState::Loaded;
}
fn set_loading(&mut self) {
self.loading_state = RelationshipLoadingState::Loading;
}
fn set_failed(&mut self, error: String) {
self.loading_state = RelationshipLoadingState::Failed(error);
self.data = None;
}
fn reset(&mut self) {
self.loading_state = RelationshipLoadingState::NotLoaded;
self.data = None;
}
fn take(&mut self) -> Option<T> {
if matches!(self.loading_state, RelationshipLoadingState::Loaded) {
self.reset();
self.data.take()
} else {
None
}
}
}
impl<T> Default for TypeSafeRelationship<T>
where
T: Clone + Debug + Send + Sync,
{
fn default() -> Self {
Self {
metadata: RelationshipMetadata::default(),
loading_state: RelationshipLoadingState::NotLoaded,
data: None,
eager_load: false,
_phantom: PhantomData,
}
}
}