datasources 0.1.25

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use crate::connection::Connection;
use crate::converters::{MetaDataRowTypeConverter, SchemaObject, TypeConverter};
use crate::crud_repository::CRUDRepository;
use crate::crud_repository_options::CRUDRepositoryOptions;
use crate::enrichment_payload::{
    cell_from_row, cell_from_struct, payload_from_row, payload_from_struct, set_cell_on_row,
    set_cell_on_struct, set_one_on_struct, set_payload_on_row, set_payload_on_struct,
    set_typed_list_on_struct, typed_list_from_struct,
};
use crate::readonly_repository::ReadonlyRepository;
use crate::readonly_repository_mapping_options::ReadonlyRepositoryMappingOptions;
use crate::repository::{ICRUDRepository, IReadonlyRepository};
use crate::result::Result;
use crate::row::Row;
use crate::value::Value;
use facet::{Def, Peek};
use std::sync::Arc;

pub trait Entity: Clone + Default + Send + Sync + 'static {
    fn object_converter() -> Box<dyn TypeConverter<Self, Row> + Send + Sync>;

    fn is_dictionary() -> bool {
        false
    }

    fn payload(&self, name: &str) -> Vec<Row> {
        let _ = name;
        Vec::new()
    }

    fn set_payload(&mut self, name: &str, rows: Vec<Row>) -> Result<()> {
        let _ = (name, rows);
        Ok(())
    }

    fn related<R: Entity>(&self, name: &str) -> Vec<R> {
        self.payload(name)
            .into_iter()
            .filter_map(|row| R::object_converter().convert(&row).ok())
            .collect()
    }

    fn set_related<R: Entity>(&mut self, name: &str, items: Vec<R>) -> Result<()> {
        let rows = items
            .iter()
            .map(|item| R::object_converter().reverse_convert(item))
            .collect::<Result<Vec<_>>>()?;
        self.set_payload(name, rows)
    }

    fn set_one<R: Entity>(&mut self, name: &str, item: R) -> Result<()> {
        self.set_related(name, vec![item])
    }

    fn cell(&self, name: &str) -> Option<Value> {
        let _ = name;
        None
    }

    fn set_cell(&mut self, name: &str, value: Value) -> Result<()> {
        let _ = (name, value);
        Ok(())
    }

    fn has(&self, name: &str) -> bool {
        self.cell(name).is_some()
    }

    fn schema_list<R: SchemaObject>(&self, name: &str) -> Vec<R> {
        self.payload(name)
            .into_iter()
            .filter_map(|row| R::object_converter().convert(&row).ok())
            .collect()
    }

    fn set_schema_list<R: SchemaObject>(&mut self, name: &str, items: Vec<R>) -> Result<()> {
        let rows = items
            .iter()
            .map(|item| R::object_converter().reverse_convert(item))
            .collect::<Result<Vec<_>>>()?;
        self.set_payload(name, rows)
    }

    fn set_schema_one<R: SchemaObject>(&mut self, name: &str, item: R) -> Result<()> {
        self.set_schema_list(name, vec![item])
    }

    fn collect_list<P: Entity>(parent: &P, name: &str) -> Vec<Self>
    where
        Self: Sized,
    {
        parent
            .payload(name)
            .into_iter()
            .filter_map(|row| Self::object_converter().convert(&row).ok())
            .collect()
    }

    fn install_list<P: Entity>(parent: &mut P, name: &str, items: Vec<Self>) -> Result<()>
    where
        Self: Sized,
    {
        let rows = items
            .iter()
            .map(|item| Self::object_converter().reverse_convert(item))
            .collect::<Result<Vec<_>>>()?;
        parent.set_payload(name, rows)
    }

    #[doc(hidden)]
    fn create_repository(
        connection: Arc<dyn Connection>,
        options: CRUDRepositoryOptions,
        from_row: impl FnOnce(
            Arc<dyn Connection>,
            CRUDRepositoryOptions,
        ) -> Arc<dyn ICRUDRepository<Row>>,
    ) -> Arc<dyn ICRUDRepository<Self>>
    where
        Self: Sized;

    #[doc(hidden)]
    fn create_readonly_repository(
        connection: Arc<dyn Connection>,
        options: ReadonlyRepositoryMappingOptions,
        from_row: impl FnOnce(
            Arc<dyn Connection>,
            ReadonlyRepositoryMappingOptions,
        ) -> Arc<dyn IReadonlyRepository<Row>>,
    ) -> Arc<dyn IReadonlyRepository<Self>>
    where
        Self: Sized;
}

impl Entity for Row {
    fn object_converter() -> Box<dyn TypeConverter<Row, Row> + Send + Sync> {
        Box::new(MetaDataRowTypeConverter::<Row>::new())
    }

    fn is_dictionary() -> bool {
        true
    }

    fn payload(&self, name: &str) -> Vec<Row> {
        payload_from_row(self, name)
    }

    fn set_payload(&mut self, name: &str, rows: Vec<Row>) -> Result<()> {
        set_payload_on_row(self, name, rows);
        Ok(())
    }

    fn cell(&self, name: &str) -> Option<Value> {
        cell_from_row(self, name)
    }

    fn set_cell(&mut self, name: &str, value: Value) -> Result<()> {
        set_cell_on_row(self, name, value);
        Ok(())
    }

    fn has(&self, name: &str) -> bool {
        self.contains_key(name)
    }

    fn create_repository(
        connection: Arc<dyn Connection>,
        options: CRUDRepositoryOptions,
        from_row: impl FnOnce(
            Arc<dyn Connection>,
            CRUDRepositoryOptions,
        ) -> Arc<dyn ICRUDRepository<Row>>,
    ) -> Arc<dyn ICRUDRepository<Self>> {
        from_row(connection, options)
    }

    fn create_readonly_repository(
        connection: Arc<dyn Connection>,
        options: ReadonlyRepositoryMappingOptions,
        from_row: impl FnOnce(
            Arc<dyn Connection>,
            ReadonlyRepositoryMappingOptions,
        ) -> Arc<dyn IReadonlyRepository<Row>>,
    ) -> Arc<dyn IReadonlyRepository<Self>> {
        from_row(connection, options)
    }
}

impl<T: SchemaObject> Entity for T {
    fn object_converter() -> Box<dyn TypeConverter<T, Row> + Send + Sync> {
        let mut converter = MetaDataRowTypeConverter::<T>::new();
        converter.should_coerce_id = true;
        Box::new(converter)
    }

    fn payload(&self, name: &str) -> Vec<Row> {
        payload_from_struct(self, name)
    }

    fn set_payload(&mut self, name: &str, rows: Vec<Row>) -> Result<()> {
        *self = set_payload_on_struct(self, name, rows)?;
        Ok(())
    }

    fn cell(&self, name: &str) -> Option<Value> {
        cell_from_struct(self, name)
    }

    fn set_cell(&mut self, name: &str, value: Value) -> Result<()> {
        *self = set_cell_on_struct(self, name, value)?;
        Ok(())
    }

    fn has(&self, name: &str) -> bool {
        Peek::new(self).into_struct().ok().and_then(|st| st.field_by_name(name).ok()).is_some()
    }

    fn related<R: Entity>(&self, name: &str) -> Vec<R> {
        R::collect_list(self, name)
    }

    fn set_related<R: Entity>(&mut self, name: &str, items: Vec<R>) -> Result<()> {
        R::install_list(self, name, items)
    }

    fn schema_list<R: SchemaObject>(&self, name: &str) -> Vec<R> {
        if list_field(self, name) {
            return typed_list_from_struct(self, name);
        }
        payload_from_struct(self, name)
            .into_iter()
            .filter_map(|row| R::object_converter().convert(&row).ok())
            .collect()
    }

    fn set_schema_list<R: SchemaObject>(&mut self, name: &str, items: Vec<R>) -> Result<()> {
        if list_field(self, name) {
            *self = set_typed_list_on_struct(self, name, items)?;
            return Ok(());
        }
        let rows = items
            .iter()
            .map(|item| R::object_converter().reverse_convert(item))
            .collect::<Result<Vec<_>>>()?;
        *self = set_payload_on_struct(self, name, rows)?;
        Ok(())
    }

    fn set_schema_one<R: SchemaObject>(&mut self, name: &str, item: R) -> Result<()> {
        *self = set_one_on_struct(self, name, item)?;
        Ok(())
    }

    fn collect_list<P: Entity>(parent: &P, name: &str) -> Vec<Self> {
        parent.schema_list::<T>(name)
    }

    fn install_list<P: Entity>(parent: &mut P, name: &str, items: Vec<Self>) -> Result<()> {
        parent.set_schema_list(name, items)
    }

    fn create_repository(
        connection: Arc<dyn Connection>,
        options: CRUDRepositoryOptions,
        _from_row: impl FnOnce(
            Arc<dyn Connection>,
            CRUDRepositoryOptions,
        ) -> Arc<dyn ICRUDRepository<Row>>,
    ) -> Arc<dyn ICRUDRepository<Self>> {
        Arc::new(CRUDRepository::<T>::new(connection, options).expect("repository"))
    }

    fn create_readonly_repository(
        connection: Arc<dyn Connection>,
        options: ReadonlyRepositoryMappingOptions,
        _from_row: impl FnOnce(
            Arc<dyn Connection>,
            ReadonlyRepositoryMappingOptions,
        ) -> Arc<dyn IReadonlyRepository<Row>>,
    ) -> Arc<dyn IReadonlyRepository<Self>> {
        Arc::new(ReadonlyRepository::<T>::new(connection, options).expect("repository"))
    }
}

pub fn assign_related<T: Entity, TRelated: Entity>(
    parent: &mut T,
    name: &str,
    items: Vec<TRelated>,
) -> Result<()> {
    parent.set_related(name, items)
}

pub fn assign_one<T: Entity, TRelated: Entity>(parent: &mut T, name: &str, item: TRelated) -> Result<()> {
    parent.set_one(name, item)
}

fn list_field<T: SchemaObject>(item: &T, name: &str) -> bool {
    Peek::new(item)
        .into_struct()
        .ok()
        .and_then(|st| st.field_by_name(name).ok())
        .is_some_and(|field| matches!(field.shape().def, Def::List(_)))
}