saas-rs-sdk 0.6.4

The SaaS RS SDK
use crate::models::model_from_message;
use crate::storage::Error;
use crate::storage::config_store::{ConfigStore, FindOptions};
use pbbson::prost::Message;
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, str::FromStr, sync::Arc};
use tonic::Status;

#[derive(Clone)]
pub struct ConfigDao<B: Clone + Debug + FromStr + ToString> {
    config_store: Arc<dyn ConfigStore<B>>,
}

impl<B: Clone + Debug + FromStr + ToString> ConfigDao<B> {
    pub fn new(config_store: Arc<dyn ConfigStore<B>>) -> Self {
        Self { config_store }
    }

    pub fn adapter(&self) -> Arc<dyn ConfigStore<B>> {
        self.config_store.clone()
    }

    pub async fn create<T: Message + Serialize + for<'a> Deserialize<'a> + Clone + Default>(
        &self,
        bucket: B,
        model: &T,
    ) -> Result<T, Status> {
        self.config_store
            .create(bucket.clone(), model_from_message(model)?)
            .await
            .map_err(|e| {
                log::error!(bucket:?, err:? = e; "Failed creating record");
                to_status(e)
            })?
            .try_into()
            .map_err(|e| {
                log::error!(bucket:?, err:? = e; "Failed converting created model back into a message");
                Status::internal(e.to_string())
            })
    }

    pub async fn delete(&self, bucket: B, id: &str, by_account_id: Option<String>) -> Result<(), Status> {
        self.config_store
            .delete(bucket.clone(), id, by_account_id)
            .await
            .map_err(|e| {
                log::error!(bucket:?, err:? = e; "Failed deleting record");
                e
            })?;
        Ok(())
    }

    pub async fn find<T: Message + Serialize + for<'a> Deserialize<'a> + Clone + Default>(
        &self,
        bucket: B,
        id: &str,
    ) -> Result<T, Status> {
        self.config_store
            .find(bucket.clone(), id)
            .await
            .map_err(|e| {
                match e {
                    Error::NotFound(_) => {}
                    _ => {
                        log::error!(bucket:?, id, err:? = e; "Failed finding record");
                    }
                }
                to_status(e)
            })?
            .try_into()
            .map_err(|e| {
                log::error!(bucket:?, err:? = e; "Failed converting found model into a message");
                Status::internal(e.to_string())
            })
    }

    pub async fn find_many<T: Message + Serialize + for<'a> Deserialize<'a> + Clone + Default>(
        &self,
        bucket: B,
        options: FindOptions,
    ) -> Result<Vec<T>, Status> {
        let filter = options.filter.clone();
        self.config_store
            .find_many(bucket.clone(), options)
            .await
            .map_err(|e| {
                log::error!(bucket:?, filter:?, err:? = e; "Failed finding records");
                to_status(e)
            })?
            .into_iter()
            .map(|model| {
                model.try_into().map_err(|e| {
                    log::error!(bucket:?, filter:?, err:? = e; "Failed converting found model into a message");
                    to_status(e.into())
                })
            })
            .collect()
    }

    pub async fn update<T: Message + Serialize + for<'a> Deserialize<'a> + Clone + Default>(
        &self,
        bucket: B,
        model: &T,
    ) -> Result<T, Status> {
        self.config_store
            .update(bucket.clone(), model_from_message(model)?)
            .await
            .map_err(|e| {
                log::error!(bucket:?, err:? = e; "Failed updating record");
                to_status(e)
            })?
            .try_into()
            .map_err(|e| {
                log::error!(bucket:?, err:? = e; "Failed converting updated model back into a message");
                Status::internal(e.to_string())
            })
    }
}

fn to_status(e: Error) -> Status {
    match e {
        Error::AlreadyExists(e) => Status::already_exists(e.to_string()),
        Error::FailedPrecondition(e) => Status::failed_precondition(e.to_string()),
        Error::Internal(e) => Status::internal(e.to_string()),
        Error::InvalidArgument(e) => Status::invalid_argument(e.to_string()),
        Error::MigrationFailed(e) => Status::internal(e.to_string()),
        Error::NotFound(e) => Status::not_found(e.to_string()),
        Error::Unavailable(e) => Status::unavailable(e.to_string()),
        Error::Unimplemented(e) => Status::unimplemented(e.to_string()),
    }
}

#[cfg(test)]
mod tests {
    use crate::storage::config_store::ConfigDao;
    use crate::storage::config_store::adapters::memory::{MemoryConfigStore, Options};
    use std::collections::HashMap;
    use std::sync::Arc;
    use strum_macros::{AsRefStr, Display, EnumIter, EnumString};

    #[test]
    fn can_clone() {
        #[derive(AsRefStr, Clone, Debug, Display, EnumIter, EnumString)]
        enum Bucket {
            None,
        }
        let opts: Options<Bucket> = Options {
            app_name: None,
            belongs_tos_by_bucket: HashMap::new(),
            has_manys_by_bucket: HashMap::new(),
        };
        let config_store = Arc::new(MemoryConfigStore::new(opts));
        let config_dao = ConfigDao::new(config_store);
        let _ = config_dao.clone();
    }
}