datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use futures_util::TryStreamExt;
use crate::delete_nested::DeleteNested;
use crate::enrichment::Enrichment;
use crate::repository::ICRUDRepository;
use crate::result::Result;
use crate::row::Row;
use crate::{Operator, Where};
use async_trait::async_trait;
use std::sync::Arc;

#[async_trait]
pub trait DeleteEnrichmentStrategy: Send + Sync {
    async fn delete_children(
        &self,
        writer: Arc<dyn ICRUDRepository>,
        enrichment: &Enrichment,
        parent: &Row,
        delete_nested: &dyn DeleteNested,
    ) -> Result<()>;

    async fn delete_linked(
        &self,
        writer: Arc<dyn ICRUDRepository>,
        enrichment: &Enrichment,
        parent: &Row,
    ) -> Result<()>;
}

pub(crate) async fn load(
    writer: &dyn ICRUDRepository,
    enrichment: &Enrichment,
    parent: &Row,
) -> Result<Vec<Row>> {
    let Some(key) = parent.get(&enrichment.source_property).cloned() else {
        return Ok(Vec::new());
    };
    writer
        .read(
            Some(&Where::new(Operator::equals(enrichment.related_key.clone(), key))),
            None, None
        )
        .try_collect::<Vec<_>>().await
}

pub(crate) fn children_where(
    writer: &dyn ICRUDRepository,
    enrichment: &Enrichment,
    parent: &Row,
) -> Option<Where> {
    if !writer.id_names().is_empty() {
        return None;
    }
    parent
        .get(&enrichment.source_property)
        .cloned()
        .map(|key| Where::new(Operator::equals(enrichment.related_key.clone(), key)))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::crud_repository::CRUDRepository;
    use crate::default_write_enrichment_strategy::DefaultWriteEnrichmentStrategy;
    use crate::repository::IReadonlyRepository;
    use crate::value::Value;
    use std::sync::Arc;

    struct SkipDelete;

    #[async_trait]
    impl DeleteEnrichmentStrategy for SkipDelete {
        async fn delete_children(
            &self,
            _writer: Arc<dyn ICRUDRepository>,
            _enrichment: &Enrichment,
            _parent: &Row,
            _delete_nested: &dyn DeleteNested,
        ) -> Result<()> {
            Ok(())
        }

        async fn delete_linked(
            &self,
            _writer: Arc<dyn ICRUDRepository>,
            _enrichment: &Enrichment,
            _parent: &Row,
        ) -> Result<()> {
            Ok(())
        }
    }

    #[tokio::test]
    async fn delete_strategy_can_skip_children() {
        let write = Arc::new(DefaultWriteEnrichmentStrategy::new(
            Some(Arc::new(SkipDelete)),
            None,
            None,
            false,
        ));
        let children = Arc::new(CRUDRepository::memory(vec![]));
        let mut parents = CRUDRepository::memory_with(vec![], Some(write));
        let mut parent = Row::new();
        let created = parents.create(&mut parent).await.unwrap();
        let mut child = Row::new();
        child.insert("parent_id".into(), created.get("id").unwrap().clone());
        child.insert("label".into(), Value::from("a"));
        children.create(&mut child).await.unwrap();
        parents.add_enriched(
            Enrichment::new("children", "id", "parent_id", None, crate::EnrichmentPropertyType::List),
            children.clone(),
            None,
        );
        parents.delete(&created, None).await.unwrap();
        let left = children.read(None, None, None).try_collect::<Vec<_>>().await.unwrap();
        assert_eq!(left.len(), 1);
        assert_eq!(left[0].get("label"), Some(&Value::from("a")));
        assert!(parents.read(None, None, None).try_collect::<Vec<_>>().await.unwrap().is_empty());
    }
}