kabu_core_config/
config_loader.rs1use async_trait::async_trait;
2use dotenvy::dotenv;
3use regex::{Captures, Regex};
4use serde::de::DeserializeOwned;
5use std::{env, fs};
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum LoadConfigError {
10 #[error("IO error: {0}")]
11 IoError(#[from] std::io::Error),
12 #[error("TOML error: {0}")]
13 TomlError(#[from] toml::de::Error),
14 #[error("Error loading config: {0}")]
15 ConfigError(String),
16}
17
18#[async_trait]
19pub trait ConfigLoader {
20 type SectionType;
21
22 async fn load_from_file(file_name: String) -> Result<Self::SectionType, LoadConfigError>;
23}
24
25pub trait ConfigLoaderSync {
26 type SectionType;
27
28 fn load_from_file_sync(file_name: String) -> Result<Self::SectionType, LoadConfigError>;
29}
30
31pub async fn load_from_file<T: DeserializeOwned>(file_name: String) -> Result<T, LoadConfigError> {
32 dotenv().ok();
33 let contents = tokio::fs::read_to_string(file_name).await?;
34 let contents = expand_vars(&contents);
35 let config: T = toml::from_str(&contents)?;
36 Ok(config)
37}
38
39pub fn load_from_file_sync<T: DeserializeOwned>(file_name: String) -> Result<T, LoadConfigError> {
40 dotenv().ok();
41 let contents = fs::read_to_string(file_name)?;
42 let contents = expand_vars(&contents);
43 let config: T = toml::from_str(&contents)?;
44 Ok(config)
45}
46
47fn expand_vars(raw_config: &str) -> String {
48 let re = Regex::new(r"\$\{([a-zA-Z_][0-9a-zA-Z_]*)\}").unwrap();
50 re.replace_all(raw_config, |caps: &Captures| match env::var(&caps[1]) {
51 Ok(val) => val,
52 Err(_) => caps[0].to_string(),
53 })
54 .to_string()
55}