mvsep-api-tester 0.1.0

MVSep API tester - CLI tool for testing music separation API
Documentation
use anyhow::Result;
use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserConfigEntry {
    pub key: String,
    pub value: String,
    pub value_type: String,
    pub updated_at: String,
}

pub struct UserConfigDB {
    conn: Connection,
}

impl UserConfigDB {
    pub fn new(path: &str) -> Result<Self> {
        let conn = Connection::open(path)?;
        let db = Self { conn };
        db.init()?;
        Ok(db)
    }

    #[allow(clippy::should_implement_trait)]
    pub fn default() -> Result<Self> {
        let path = format!(
            "{}/user_config.db",
            std::env::var("HOME").unwrap_or_else(|_| ".".to_string())
        );
        Self::new(&path)
    }

    fn init(&self) -> Result<()> {
        self.conn.execute_batch(
            "
            CREATE TABLE IF NOT EXISTS user_config (
                key TEXT PRIMARY KEY NOT NULL,
                value TEXT NOT NULL DEFAULT '',
                value_type TEXT NOT NULL DEFAULT 'string',
                updated_at TEXT DEFAULT (datetime('now')),
                created_at TEXT DEFAULT (datetime('now'))
            );

            CREATE INDEX IF NOT EXISTS idx_user_config_key ON user_config(key);
            CREATE INDEX IF NOT EXISTS idx_user_config_type ON user_config(value_type);

            CREATE TABLE IF NOT EXISTS config_groups (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                description TEXT,
                created_at TEXT DEFAULT (datetime('now'))
            );
            ",
        )?;
        Ok(())
    }

    pub fn set_string(&self, key: &str, value: &str) -> Result<()> {
        self.conn.execute(
            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'string', datetime('now'))",
            params![key, value],
        )?;
        Ok(())
    }

    pub fn get_string(&self, key: &str) -> Result<Option<String>> {
        let mut stmt = self
            .conn
            .prepare("SELECT value FROM user_config WHERE key = ?1")?;
        let mut rows = stmt.query_map(params![key], |row| row.get(0))?;
        match rows.next() {
            Some(result) => Ok(Some(result?)),
            None => Ok(None),
        }
    }

    pub fn set_bool(&self, key: &str, value: bool) -> Result<()> {
        self.conn.execute(
            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'bool', datetime('now'))",
            params![key, if value { "true" } else { "false" }],
        )?;
        Ok(())
    }

    pub fn get_bool(&self, key: &str) -> Result<Option<bool>> {
        match self.get_string(key)? {
            Some(value) => Ok(Some(value == "true")),
            None => Ok(None),
        }
    }

    pub fn set_int(&self, key: &str, value: i64) -> Result<()> {
        self.conn.execute(
            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'int', datetime('now'))",
            params![key, value.to_string()],
        )?;
        Ok(())
    }

    pub fn get_int(&self, key: &str) -> Result<Option<i64>> {
        match self.get_string(key)? {
            Some(value) => match value.parse::<i64>() {
                Ok(v) => Ok(Some(v)),
                Err(_) => Ok(None),
            },
            None => Ok(None),
        }
    }

    pub fn set_float(&self, key: &str, value: f64) -> Result<()> {
        self.conn.execute(
            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'float', datetime('now'))",
            params![key, value.to_string()],
        )?;
        Ok(())
    }

    pub fn get_float(&self, key: &str) -> Result<Option<f64>> {
        match self.get_string(key)? {
            Some(value) => match value.parse::<f64>() {
                Ok(v) => Ok(Some(v)),
                Err(_) => Ok(None),
            },
            None => Ok(None),
        }
    }

    pub fn set_json<T: Serialize>(&self, key: &str, value: &T) -> Result<()> {
        let json_str = serde_json::to_string(value)?;
        self.conn.execute(
            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'json', datetime('now'))",
            params![key, json_str],
        )?;
        Ok(())
    }

    pub fn get_json<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Result<Option<T>> {
        match self.get_string(key)? {
            Some(value) => match serde_json::from_str(&value) {
                Ok(v) => Ok(Some(v)),
                Err(_) => Ok(None),
            },
            None => Ok(None),
        }
    }

    pub fn delete(&self, key: &str) -> Result<bool> {
        let rows_affected = self
            .conn
            .execute("DELETE FROM user_config WHERE key = ?1", params![key])?;
        Ok(rows_affected > 0)
    }

    pub fn exists(&self, key: &str) -> Result<bool> {
        let mut stmt = self
            .conn
            .prepare("SELECT COUNT(*) FROM user_config WHERE key = ?1")?;
        let count: i64 = stmt.query_row(params![key], |row| row.get(0))?;
        Ok(count > 0)
    }

    pub fn get_all_by_prefix(&self, prefix: &str) -> Result<Vec<UserConfigEntry>> {
        let mut stmt = self.conn.prepare(
            "SELECT key, value, value_type, updated_at FROM user_config WHERE key LIKE ?1 || '%' ORDER BY key"
        )?;
        let rows = stmt.query_map(params![prefix], |row| {
            Ok(UserConfigEntry {
                key: row.get(0)?,
                value: row.get(1)?,
                value_type: row.get(2)?,
                updated_at: row.get(3)?,
            })
        })?;
        rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
    }

    pub fn get_all_by_type(&self, value_type: &str) -> Result<Vec<UserConfigEntry>> {
        let mut stmt = self.conn.prepare(
            "SELECT key, value, value_type, updated_at FROM user_config WHERE value_type = ?1 ORDER BY key"
        )?;
        let rows = stmt.query_map(params![value_type], |row| {
            Ok(UserConfigEntry {
                key: row.get(0)?,
                value: row.get(1)?,
                value_type: row.get(2)?,
                updated_at: row.get(3)?,
            })
        })?;
        rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
    }

    pub fn get_all(&self) -> Result<Vec<UserConfigEntry>> {
        let mut stmt = self
            .conn
            .prepare("SELECT key, value, value_type, updated_at FROM user_config ORDER BY key")?;
        let rows = stmt.query_map([], |row| {
            Ok(UserConfigEntry {
                key: row.get(0)?,
                value: row.get(1)?,
                value_type: row.get(2)?,
                updated_at: row.get(3)?,
            })
        })?;
        rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
    }

    pub fn count(&self) -> Result<i64> {
        let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM user_config")?;
        let count: i64 = stmt.query_row([], |row| row.get(0))?;
        Ok(count)
    }

    pub fn clear_all(&self) -> Result<u64> {
        let affected = self.conn.execute("DELETE FROM user_config", [])?;
        Ok(affected as u64)
    }

    pub fn export_to_json(&self) -> Result<String> {
        let entries = self.get_all()?;
        let json = serde_json::to_string_pretty(&entries)?;
        Ok(json)
    }

    pub fn import_from_json(&self, json_str: &str) -> Result<u64> {
        let entries: Vec<UserConfigEntry> = serde_json::from_str(json_str)?;
        let mut count = 0u64;

        for entry in entries {
            self.conn.execute(
                "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, ?3, ?4)",
                params![entry.key, entry.value, entry.value_type, entry.updated_at],
            )?;
            count += 1;
        }

        Ok(count)
    }
}