HomeDisk/database/src/sqlite.rs

287 lines
8.0 KiB
Rust
Raw Normal View History

use std::str::FromStr;
use futures_util::TryStreamExt;
2022-07-12 19:59:11 +00:00
use sqlx::{
sqlite::{SqliteConnectOptions, SqliteQueryResult},
ConnectOptions, Executor, Row, SqlitePool,
};
use tracing::{debug, log::LevelFilter};
2022-06-23 09:52:48 +00:00
use super::{Error, Result, User};
2022-06-08 19:16:12 +00:00
/// SQL Database
#[derive(Debug, Clone)]
pub struct Database {
2022-06-08 19:16:12 +00:00
/// SQLite Connection Pool
pub conn: SqlitePool,
}
impl Database {
2022-06-08 22:02:20 +00:00
/// Open a SQLite database
2022-06-18 11:39:15 +00:00
/// ```no_run
2022-06-23 09:52:48 +00:00
/// # async fn foo() -> homedisk_database::Result<()> {
/// use homedisk_database::Database;
///
/// // open database in memory
/// Database::open("sqlite::memory:").await?;
2022-06-08 19:16:12 +00:00
///
/// // open database from file
/// Database::open("path/to/database.db").await?;
2022-06-18 11:39:15 +00:00
///
/// # Ok(()) }
/// ```
2022-06-23 09:52:48 +00:00
pub async fn open(path: &str) -> Result<Self> {
2022-06-07 20:36:26 +00:00
debug!("Opening SQLite database");
// sqlite connection options
2022-07-12 19:59:11 +00:00
let mut options = SqliteConnectOptions::from_str(path).map_err(Error::OpenDatabase)?;
// set log level to Debug
options.log_statements(LevelFilter::Debug);
2022-06-14 20:37:42 +00:00
// create a database pool
let conn = SqlitePool::connect_with(options)
2022-07-12 19:59:11 +00:00
.await
.map_err(Error::ConnectDatabase)?;
2022-06-14 20:37:42 +00:00
// return `Database`
Ok(Self { conn })
}
/// Create all required tabled for HomeDisk
/// ```
2022-06-23 09:52:48 +00:00
/// # async fn foo() -> homedisk_database::Result<()> {
/// # let db = homedisk_database::Database::open("sqlite::memory:").await?;
/// db.create_tables().await?;
///
/// # Ok(()) }
/// ```
2022-06-23 09:52:48 +00:00
pub async fn create_tables(&self) -> Result<SqliteQueryResult> {
let query = sqlx::query(include_str!("../../tables.sql"));
2022-07-12 19:59:11 +00:00
self.conn.execute(query).await.map_err(Error::Execute)
}
2022-06-08 22:02:20 +00:00
/// Create a new User
/// ```
2022-06-23 09:52:48 +00:00
/// # async fn foo() -> homedisk_database::Result<()> {
/// # let db = homedisk_database::Database::open("sqlite::memory:").await?;
/// # db.create_tables().await?;
/// use homedisk_database::User;
2022-06-14 20:37:42 +00:00
///
/// // create `User` type
/// let user = User::new("username", "password");
2022-06-08 19:16:12 +00:00
///
/// // create a user in database
/// db.create_user(&user).await?;
2022-06-18 11:39:15 +00:00
///
/// # Ok(()) }
/// ```
2022-06-23 09:52:48 +00:00
pub async fn create_user(&self, user: &User) -> Result<SqliteQueryResult> {
2022-06-07 20:36:26 +00:00
debug!("Creating user - {}", user.username);
2022-06-16 12:14:40 +00:00
// insert user to a database
let query = sqlx::query("INSERT INTO user (id, username, password) VALUES (?, ?, ?)")
2022-04-19 13:14:17 +00:00
.bind(&user.id)
.bind(&user.username)
.bind(&user.password);
2022-06-14 20:37:42 +00:00
// execute query and return output
2022-07-12 19:59:11 +00:00
self.conn.execute(query).await.map_err(Error::Execute)
}
2022-06-08 17:08:06 +00:00
/// Search for a user
2022-06-14 20:37:42 +00:00
/// ```
2022-06-23 09:52:48 +00:00
/// # async fn foo() -> homedisk_database::Result<()> {
/// # let db = homedisk_database::Database::open("sqlite::memory:").await?;
/// # db.create_tables().await?;
/// use homedisk_database::User;
2022-06-14 20:37:42 +00:00
///
/// // create `User` type
/// let user = User::new("username", "password");
2022-05-01 16:47:26 +00:00
///
/// # db.create_user(&user).await?;
/// db.find_user(&user).await?;
2022-06-18 11:39:15 +00:00
///
/// # Ok(()) }
2022-05-01 16:47:26 +00:00
/// ```
2022-06-23 09:52:48 +00:00
pub async fn find_user(&self, user: &User) -> Result<User> {
2022-06-18 11:39:15 +00:00
debug!("Searching for a user - {}", user.username);
2022-06-08 19:16:12 +00:00
// create query request to database
let query =
sqlx::query_as::<_, User>("SELECT * FROM user WHERE username = ? AND password = ?")
2022-06-18 11:39:15 +00:00
.bind(&user.username)
.bind(&user.password);
2022-06-08 19:16:12 +00:00
// fetch query
let mut stream = self.conn.fetch(query);
2022-06-08 19:16:12 +00:00
// get rows from query
2022-07-12 19:59:11 +00:00
let row = stream
.try_next()
.await
.map_err(Error::Execute)?
.ok_or(Error::UserNotFound)?;
2022-06-08 19:16:12 +00:00
// get `id` row
2022-07-12 19:59:11 +00:00
let id = row.try_get("id").map_err(Error::GetRow)?;
2022-06-08 19:16:12 +00:00
// get `username` row
2022-07-12 19:59:11 +00:00
let username = row.try_get("username").map_err(Error::GetRow)?;
2022-06-08 19:16:12 +00:00
// get `password` row
2022-07-12 19:59:11 +00:00
let password = row.try_get("password").map_err(Error::GetRow)?;
2022-06-14 20:37:42 +00:00
// return `User`
Ok(User {
id,
username,
password,
})
}
2022-06-08 17:08:06 +00:00
/// Search for a user by UUID
2022-06-14 20:37:42 +00:00
/// ```
2022-06-23 09:52:48 +00:00
/// # async fn foo() -> homedisk_database::Result<()> {
/// # let db = homedisk_database::Database::open("sqlite::memory:").await?;
/// # db.create_tables().await?;
/// use homedisk_database::User;
2022-06-14 20:37:42 +00:00
///
/// // create `User` type
/// let user = User::new("username", "password");
2022-05-01 16:47:26 +00:00
///
/// # db.create_user(&user).await?;
/// db.find_user_by_id(&user.id).await?;
2022-06-18 11:39:15 +00:00
///
/// # Ok(()) }
2022-05-01 16:47:26 +00:00
/// ```
2022-06-23 09:52:48 +00:00
pub async fn find_user_by_id(&self, id: &str) -> Result<User> {
2022-06-07 20:36:26 +00:00
debug!("Searching for a user by UUID - {}", id);
2022-05-01 16:47:26 +00:00
2022-06-08 19:16:12 +00:00
// create query request to database
let query = sqlx::query_as::<_, User>("SELECT * FROM user WHERE id = ?").bind(id);
2022-06-08 19:16:12 +00:00
// fetch query
let mut stream = self.conn.fetch(query);
2022-06-08 19:16:12 +00:00
// get rows from query
2022-07-12 19:59:11 +00:00
let row = stream
.try_next()
.await
.map_err(Error::Execute)?
.ok_or(Error::UserNotFound)?;
2022-06-08 19:16:12 +00:00
// get `id` row
2022-07-12 19:59:11 +00:00
let id = row.try_get("id").map_err(Error::GetRow)?;
2022-06-08 19:16:12 +00:00
// get `username` row
2022-07-12 19:59:11 +00:00
let username = row.try_get("username").map_err(Error::GetRow)?;
2022-06-08 19:16:12 +00:00
// get `password` row
2022-07-12 19:59:11 +00:00
let password = row.try_get("password").map_err(Error::GetRow)?;
2022-06-14 20:37:42 +00:00
// return `User`
Ok(User {
id,
username,
password,
})
}
}
#[cfg(test)]
mod tests {
use crate::{Database, User};
2022-06-18 11:39:15 +00:00
const USERNAME: &str = "medzik";
const PASSWORD: &str = "SuperSecretPassword123!";
/// Utils to open database in tests
async fn open_db() -> Database {
Database::open("sqlite::memory:").await.expect("open db")
}
2022-06-08 17:08:06 +00:00
/// Utils to create a new user in tests
async fn new_user(db: &Database) {
// create tables
db.create_tables().await.expect("create tables");
// create new user
2022-06-18 11:39:15 +00:00
let user = User::new(USERNAME, PASSWORD);
db.create_user(&user).await.expect("create user");
}
2022-06-18 11:39:15 +00:00
/// Test a create user
#[tokio::test]
async fn create_user() {
let db = open_db().await;
new_user(&db).await;
}
// Test a search for a user
#[tokio::test]
async fn find_user() {
let db = open_db().await;
new_user(&db).await;
let user = User::new(USERNAME, PASSWORD);
let user = db.find_user(&user).await.unwrap();
assert_eq!(user.username, USERNAME)
}
// Test a search for a user by id
#[tokio::test]
async fn find_user_by_id() {
let db = open_db().await;
new_user(&db).await;
let user = User::new(USERNAME, PASSWORD);
let user = db.find_user_by_id(&user.id).await.unwrap();
assert_eq!(user.username, USERNAME)
}
2022-06-14 20:37:42 +00:00
/// Test a search for a user with an invalid password to see if the user is returned (it shouldn't be)
#[tokio::test]
async fn find_user_wrong_password() {
let db = open_db().await;
new_user(&db).await;
2022-06-18 11:39:15 +00:00
let user = User::new(USERNAME, "wrong password 123!");
2022-06-18 11:40:01 +00:00
let err = db.find_user(&user).await.unwrap_err();
assert_eq!(err.to_string(), "user not found")
}
2022-07-12 19:59:11 +00:00
/// Test a search for a user who doesn't exist
#[tokio::test]
async fn find_user_wrong_username() {
let db = open_db().await;
new_user(&db).await;
2022-06-18 11:39:15 +00:00
let user = User::new("not_exists_user", PASSWORD);
2022-06-18 11:40:01 +00:00
let err = db.find_user(&user).await.unwrap_err();
assert_eq!(err.to_string(), "user not found")
}
2022-07-12 19:59:11 +00:00
/// Test a search for a user by UUID who doesn't exist
#[tokio::test]
async fn find_user_wrong_id() {
let db = open_db().await;
new_user(&db).await;
2022-06-18 11:39:15 +00:00
let other_user = User::new("not_exists_user", "my secret passphrase");
2022-06-14 20:37:42 +00:00
let err = db.find_user_by_id(&other_user.id).await.unwrap_err();
assert_eq!(err.to_string(), "user not found")
}
}