aiscript_runtime/config/
mod.rs

1use std::{env, fmt::Display, fs, ops::Deref, path::Path, sync::OnceLock};
2
3use auth::AuthConfig;
4use serde::Deserialize;
5
6use db::DatabaseConfig;
7pub use sso::{SsoConfig, get_sso_fields};
8
9mod auth;
10mod db;
11mod sso;
12#[cfg(test)]
13mod tests;
14
15static CONFIG: OnceLock<Config> = OnceLock::new();
16
17// Custom string type that handles environment variable substitution
18#[derive(Debug, Clone, Deserialize)]
19#[serde(from = "String")]
20pub struct EnvString(String);
21
22impl From<String> for EnvString {
23    fn from(s: String) -> Self {
24        if let Some(env_key) = s.strip_prefix('$') {
25            match env::var(env_key) {
26                Ok(val) => EnvString(val),
27                Err(_) => {
28                    // If env var is not found, use the original string
29                    // This allows for better error handling at runtime
30                    EnvString(s)
31                }
32            }
33        } else {
34            EnvString(s)
35        }
36    }
37}
38
39impl Display for EnvString {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "{}", self.0)
42    }
43}
44
45impl From<EnvString> for String {
46    fn from(s: EnvString) -> Self {
47        s.0
48    }
49}
50
51impl Deref for EnvString {
52    type Target = String;
53
54    fn deref(&self) -> &Self::Target {
55        &self.0
56    }
57}
58
59impl AsRef<str> for EnvString {
60    fn as_ref(&self) -> &str {
61        &self.0
62    }
63}
64
65#[derive(Debug, Deserialize, Default)]
66pub struct Config {
67    #[serde(default)]
68    pub database: DatabaseConfig,
69    #[serde(default)]
70    pub apidoc: ApiDocConfig,
71    #[serde(default)]
72    pub auth: AuthConfig,
73    #[serde(default)]
74    pub sso: SsoConfig,
75}
76
77#[derive(Debug, Deserialize, Default)]
78#[serde(rename_all = "lowercase")]
79pub enum ApiDocType {
80    Swagger,
81    #[default]
82    Redoc,
83}
84
85#[derive(Debug, Deserialize)]
86pub struct ApiDocConfig {
87    pub enabled: bool,
88    #[serde(rename = "type", default)]
89    pub doc_type: ApiDocType,
90    #[serde(default = "default_path")]
91    pub path: String,
92}
93
94fn default_path() -> String {
95    "/doc".to_string()
96}
97
98impl Default for ApiDocConfig {
99    fn default() -> Self {
100        Self {
101            enabled: true,
102            doc_type: ApiDocType::default(),
103            path: default_path(),
104        }
105    }
106}
107
108impl Config {
109    fn new(source: impl AsRef<Path>) -> Result<Self, Box<dyn std::error::Error>> {
110        let path = source.as_ref();
111        if path.exists() {
112            let content = fs::read_to_string(path)?;
113            Ok(toml::from_str(&content)?)
114        } else {
115            Ok(Config::default())
116        }
117    }
118
119    pub fn load(path: &str) -> &Config {
120        CONFIG.get_or_init(|| {
121            Config::new(path).unwrap_or_else(|e| {
122                eprintln!("Error loading config file: {}", e);
123                Config::default()
124            })
125        })
126    }
127
128    pub fn get() -> &'static Config {
129        CONFIG.get().expect("Config not initialized")
130    }
131}