boxcap 0.1.0

Common project structure for creating rust projects
Documentation
//! The database service provides all interactions between the dispatcher and a remote or local database
//!
//!
use super::Message;
use super::Service;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use std::error::Error;
use std::io;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
use std::time::Duration;
use surrealdb::engine::remote::ws::{Client, Ws};
use surrealdb::sql::Thing;
use surrealdb::Surreal;

/// represents a databases return type
#[derive(Debug, Deserialize)]
pub struct Record {
    id: Thing,
}

/// This is anything that can be an entry to be writable to the database
pub trait Entry {}

/// Denotes the acceptable types of databases
pub enum DatabaseType {
    /// An in memory database
    MEMORY,
    /// A local db attached to the filesystem
    LOCAL,
    /// A remote db accessed through a url
    REMOTE,
}

/// Acts as a command type that other services can use to send command to the database
/// These commands can include read or write commands
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum Command<'a> {
    /// A select command (read)
    SELECT(&'a str),
    /// A create command (write)
    CREATE(&'a str),
    /// A simple query (whatever)
    QUERY(&'a str),
}

impl<'a> Command<'a> {
    /// converts the command to json
    pub fn to_json(&self) -> String {
        serde_json::to_string(self).expect(&format!("failed to convert {:?} to json", self))
    }
}

/// Database is a type of service that can interact with a local or remote database
/// The database service is responsible for managing connections to the database, both internal and external
pub struct Database {
    /// The name of the database, this value will be used to send messages to the db
    pub name: String,
    rx: Option<Receiver<Message>>,
    tx: Option<Sender<Message>>,
    db: Surreal<Client>,
}

impl Database {
    /// Initializes a new db instance
    pub async fn new(
        namespace: &str,
        database_name: &str,
        db_type: DatabaseType,
    ) -> Result<Self, Box<dyn Error>> {
        // Create database connection
        let db = match db_type {
            DatabaseType::MEMORY => {
                // Handle in-memory database creation if supported
                return Err(Box::new(io::Error::new(
                    io::ErrorKind::Other,
                    "Cannot support memory databases yet",
                )));
            }
            DatabaseType::LOCAL => {
                // Create / connect to new local db
                let client = match Surreal::new::<Ws>("127.0.0.1:8000").await {
                    Ok(c) => c,
                    Err(e) => return Err(Box::new(e)), // db is not running
                };
                client.use_ns(namespace).use_db(database_name).await?;
                client
            }
            DatabaseType::REMOTE => {
                // Handle remote database connection if supported
                return Err(Box::new(io::Error::new(
                    io::ErrorKind::Other,
                    "Cannot support remote databases yet",
                )));
            }
        };

        Ok(Database {
            name: database_name.to_string(),
            rx: None,
            tx: None,
            db: db,
        })
    }
}

impl Service for Database {
    fn run(&self) -> Result<(), Box<dyn Error>> {
        loop {
            if let Some(rx) = &self.rx {
                if let Some(message) = rx.try_recv().ok() {
                    // message processing
                    if let Ok(command) = serde_json::from_str::<Command>(&message.content) {
                        match command {
                            Command::CREATE(what) => {
                                // format of what is table:entry
                            }
                            Command::SELECT(what) => {}
                            Command::QUERY(what) => {}
                        }
                    }
                }
            }
        }
    }

    fn send(&self, msg: Message) -> Result<(), Box<dyn Error>> {
        if let Some(tx) = &self.tx {
            tx.send(msg);
            return Ok(());
        }
        Ok(())
    }

    fn identify(&self) -> String {
        self.name.to_string()
    }

    /// sets the comm pair for the service
    fn set_comm_pair(&mut self, tx: Sender<Message>, rx: Receiver<Message>) -> () {
        self.tx = Some(tx);
        self.rx = Some(rx);
    }
}

/// The testing suite for the library can be seen here
#[cfg(test)]
mod tests {
    #[allow(dead_code)]
    use super::*;
    use std::{fs, path::Path};
    #[allow(dead_code)]
    /// Cleans log results for the tests

    fn clean() -> Result<(), Box<dyn std::error::Error>> {
        // Define the path to the logs directory
        let log_dir_path = Path::new("logs/tests");

        // Check if the directory exists
        if log_dir_path.exists() {
            // Remove the directory and its contents
            fs::remove_dir_all(log_dir_path)?;
            println!("Successfully removed {}", log_dir_path.display());
        } else {
            println!("Directory {} does not exist.", log_dir_path.display());
        }

        Ok(())
    }

    #[tokio::test]
    async fn test_db() {
        let db_type = DatabaseType::LOCAL;
        let db = Database::new("test", "test", db_type).await;

        if let Err(e) = db {
            panic!("test_db: {}", e)
        }
    }

    #[test]
    fn test_command() {
        let c = Command::CREATE("test");
        let cstr = c.to_json();

        println!("{}", cstr);

        let w: Command = serde_json::from_str(&cstr).expect("unable to deserialize");
        assert_eq!(w, Command::CREATE("test"));
    }
}