sqlx-repo 0.1.2

repository pattern on top of sqlx
Documentation
use std::ops::Deref;

use anyhow::Result;
use sqlx_repo::prelude::*;
use tempfile::NamedTempFile;

#[repo(Send + Sync + std::fmt::Debug)]
impl Repo for DatabaseRepository {
    async fn create_tables_and_insert_data(&self) -> Result<()> {
        let query = query!(
            "
            create table foo(id int primary key);
            create table bar(id int primary key, foreign key (id) REFERENCES foo(id) on delete cascade);
            insert into foo values(1), (2), (3);
            insert into bar values(1), (2), (3);
        "
        );
        sqlx::query(query).execute(&self.pool).await?;
        Ok(())
    }

    async fn delete_all_foo(&self) -> Result<()> {
        let query = query!("delete from foo");
        sqlx::query(query).execute(&self.pool).await?;
        Ok(())
    }

    async fn select_all_bar(&self) -> Result<Vec<i64>> {
        let query = query!("select * from bar");
        Ok(sqlx::query(query)
            .fetch_all(&self.pool)
            .await?
            .into_iter()
            .map(|row| row.get(0))
            .collect())
    }
}

#[tokio::test]
async fn test_database_creation_defaults() {
    let url = "sqlite::memory:";
    let repo = <dyn Repo>::new(url).await.unwrap();
    repo.create_tables_and_insert_data().await.unwrap();
    assert_eq!(vec![1, 2, 3], repo.select_all_bar().await.unwrap(),);
    repo.delete_all_foo().await.unwrap();
    assert_eq!(Vec::<i64>::new(), repo.select_all_bar().await.unwrap(),)
}

#[tokio::test]
async fn test_database_creation_foreign_key_not_enforced() {
    let url = "sqlite::memory:?foreign_keys=false";
    let repo = <dyn Repo>::new(url).await.unwrap();
    repo.create_tables_and_insert_data().await.unwrap();
    assert_eq!(vec![1, 2, 3], repo.select_all_bar().await.unwrap(),);
    repo.delete_all_foo().await.unwrap();
    assert_eq!(vec![1, 2, 3], repo.select_all_bar().await.unwrap(),)
}

struct TmpFile(NamedTempFile);

impl Deref for TmpFile {
    type Target = NamedTempFile;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Drop for TmpFile {
    fn drop(&mut self) {
        std::fs::remove_file(self.path()).ok();
    }
}

#[tokio::test]
async fn test_database_creation_pre_existing_file() {
    let file = TmpFile(tempfile::NamedTempFile::new().unwrap());
    let url = format!("sqlite://{}", file.path().to_str().unwrap());
    assert!(<dyn Repo>::new(&url).await.is_ok());
}

#[tokio::test]
async fn test_database_creation_disallow_creation() {
    let file = TmpFile(tempfile::NamedTempFile::new().unwrap());
    let url = format!(
        "sqlite://{}/?create_if_missing=false",
        file.path().to_str().unwrap()
    );
    drop(file);
    let res = <dyn Repo>::new(&url).await;
    assert!(res.is_err());
    assert_eq!(
        "error returned from database: (code: 14) unable to open database file",
        res.unwrap_err().to_string()
    )
}

#[tokio::test]
async fn test_database_default_creation() {
    let file = TmpFile(tempfile::NamedTempFile::new().unwrap());
    let url = format!("sqlite://{}", file.path().to_str().unwrap());
    drop(file);
    assert!(<dyn Repo>::new(&url).await.is_ok());
}