tailwag_orm 0.1.0

An ORM for building Postgres schemas and data access with a Manager pattern, backed by sqlx.
Documentation
use std::{
    any::{Any, TypeId},
    collections::HashMap,
    sync::Arc,
};

use sqlx::Postgres;

use crate::{
    data_manager::{GetTableDefinition, PostgresDataProvider},
    queries::Insertable,
};

use super::table::DatabaseTableDefinition;

#[derive(Default)]
pub struct DataSystemBuilder {
    resources: HashMap<TypeId, Arc<Box<dyn Any + Send + Sync>>>,
}

impl DataSystemBuilder {
    pub fn add_resource<T: GetTableDefinition + Send + Sync + 'static>(&mut self) {
        self.resources
            .insert(TypeId::of::<T>(), Arc::new(Box::new(T::get_table_definition())));
    }
    pub fn with_resource<T: GetTableDefinition + Send + Sync + 'static>(mut self) -> Self {
        self.add_resource::<T>();
        self
    }
    pub fn get<T: GetTableDefinition + Clone + Send + Sync + 'static>(
        &self
    ) -> Option<DatabaseTableDefinition<T>> {
        self.resources.get(&TypeId::of::<T>()).map(|t| {
            let boxed = t.downcast_ref::<DatabaseTableDefinition<T>>().expect(
                "Invalid type stored in DataSystem.resources - this should not be possible.
                The type exists in the map but failed to downcast.",
            );
            boxed.clone()
        })
    }

    pub fn build(self) -> UnconnectedDataSystem {
        UnconnectedDataSystem {
            resources: Arc::new(self.resources),
        }
    }
}

pub struct UnconnectedDataSystem {
    resources: Arc<HashMap<TypeId, Arc<Box<dyn Any + Send + Sync>>>>,
}
impl UnconnectedDataSystem {
    pub async fn connect(
        &self,
        pool: sqlx::Pool<Postgres>,
    ) -> DataSystem {
        DataSystem {
            resources: self.resources.clone(),
            pool,
        }
    }
}

#[derive(Clone)]
pub struct DataSystem {
    resources: Arc<HashMap<TypeId, Arc<Box<dyn Any + Send + Sync>>>>,
    pool: sqlx::Pool<Postgres>,
}

impl DataSystem {
    pub fn builder() -> DataSystemBuilder {
        DataSystemBuilder::default()
    }
}

impl DataSystem {
    pub fn get<T: Clone + Insertable + Send + Sync + 'static>(
        &self
    ) -> Option<PostgresDataProvider<T>> {
        self.resources.get(&TypeId::of::<T>()).map(|t| {
            let boxed = t.downcast_ref::<DatabaseTableDefinition<T>>().expect(
                "Invalid type stored in DataSystem.resources - this should not be possible.
                The type exists in the map but failed to downcast.",
            );
            PostgresDataProvider::new(boxed.clone(), self.pool.clone())
        })
    }
}

impl<T> TryFrom<&DataSystem> for PostgresDataProvider<T>
where
    T: Insertable + Clone + Send + Sync + 'static,
{
    type Error = String;

    fn try_from(parent: &DataSystem) -> Result<Self, Self::Error> {
        let Some(val) = parent.get::<T>() else {
            return Err("Unable to fetch PostgresDataProvider form DataSystem".to_string());
        };
        Ok(val)
    }
}