botcat-capoo 1.0.3

A NapCat based QQ bot implemented in Rust
Documentation
use std::str::FromStr;

use config::{Config, File};
use serde::{Deserialize, Serialize};

use crate::error::Error;

#[derive(PartialEq)]
pub enum AppEnv {
    Development,
    Test,
    Production,
}

impl AppEnv {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Development => "dev",
            Self::Test => "test",
            Self::Production => "prod",
        }
    }

    pub fn is_development(&self) -> bool {
        *self == Self::Development
    }

    pub fn is_test(&self) -> bool {
        *self == Self::Test
    }

    pub fn is_production(&self) -> bool {
        *self == Self::Production
    }
}

impl FromStr for AppEnv {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "development" | "dev" | "develop" => Ok(Self::Development),
            "test" => Ok(Self::Test),
            "production" | "prod" | "product" => Ok(Self::Production),
            _ => Err(Self::Err::Other {
                desc: format!("Invalid environment: {s}"),
            }),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BotcatCapooConfig {
    pub host: String,
    pub port: u16,
    pub owner: u64,
    pub id: u64,
    pub database: DatabaseConfig,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NapcatConfig {
    pub api_url: String,
    pub port: u16,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum DatabaseType {
    Postgres,
    Sqlite,
    MySql,
}

impl FromStr for DatabaseType {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "postgres" | "postgresql" | "psql" | "pgsql" | "pg" => Ok(Self::Postgres),
            "sqlite" | "sqlite3" => Ok(Self::Sqlite),
            "mysql" => Ok(Self::MySql),
            _ => Err(Self::Err::Other {
                desc: format!("Invalid database type: {s}"),
            }),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DatabaseConfig {
    pub db_type: String,
    pub host: Option<String>,
    pub port: Option<u16>,
    pub username: Option<String>,
    pub password: Option<String>,
    pub database: String,
    pub max_connections: Option<u32>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SecretConfig {
    pub http_client_token: String,
    pub http_server_token: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AppConfig {
    pub botcat_capoo: BotcatCapooConfig,
    pub napcat: NapcatConfig,
    pub secret: SecretConfig,
}

impl AppConfig {
    pub fn init() -> Result<AppConfig, Error> {
        let env = std::env::var("BOTCAT_CAPOO_ENV").unwrap_or_else(|_| "dev".to_string());
        let env = AppEnv::from_str(&env)
            .unwrap_or(AppEnv::Development)
            .as_str();

        let settings = Config::builder()
            .add_source(File::with_name(&format!("config/{env}")))
            .add_source(File::with_name(&format!("secret/{env}")))
            .build()?;
        settings.try_deserialize::<AppConfig>().map_err(Error::from)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[ignore = "Requires secret files which are not included in the repository"]
    fn test_config_initialization() {
        let config = AppConfig::init();
        assert!(config.is_ok());
    }
}