use std::net::SocketAddr;
use std::path::PathBuf;
use crate::error::ApiError;
use crate::keys::KeyStore;
pub const DEFAULT_BIND: &str = "127.0.0.1:8080";
pub const DEFAULT_DATA_DIR: &str = "./data";
pub const DEFAULT_KEYS_FILE: &str = "./api_keys";
pub const DEFAULT_METERING_LOG: &str = "./metering.log";
#[derive(Debug, Clone)]
pub struct Config {
pub data_dir: PathBuf,
pub keys_file: PathBuf,
pub metering_log: PathBuf,
pub bind: SocketAddr,
}
impl Config {
pub fn from_env() -> Result<Self, ApiError> {
let data_dir = env_path("CODETOPO_DATA_DIR", DEFAULT_DATA_DIR);
let keys_file = env_path("CODETOPO_KEYS_FILE", DEFAULT_KEYS_FILE);
let metering_log = env_path("CODETOPO_METERING_LOG", DEFAULT_METERING_LOG);
let bind = env_string("CODETOPO_BIND", DEFAULT_BIND);
let bind: SocketAddr = bind.parse().map_err(|_| {
ApiError::Internal(format!(
"CODETOPO_BIND must be a 'host:port' socket address, got '{bind}'"
))
})?;
Ok(Config {
data_dir,
keys_file,
metering_log,
bind,
})
}
pub fn index_db_path(&self, index_id: &str) -> PathBuf {
self.data_dir.join(format!("{index_id}.db"))
}
pub async fn prepare(&self) -> Result<KeyStore, ApiError> {
tokio::fs::create_dir_all(&self.data_dir)
.await
.map_err(|err| {
ApiError::Internal(format!(
"cannot create data dir {}: {err}",
self.data_dir.display()
))
})?;
KeyStore::load(&self.keys_file)
}
}
impl Default for Config {
fn default() -> Self {
Config {
data_dir: PathBuf::from(DEFAULT_DATA_DIR),
keys_file: PathBuf::from(DEFAULT_KEYS_FILE),
metering_log: PathBuf::from(DEFAULT_METERING_LOG),
bind: DEFAULT_BIND.parse().expect("default bind address is valid"),
}
}
}
fn env_string(key: &str, default: &str) -> String {
match std::env::var(key) {
Ok(value) if !value.trim().is_empty() => value,
_ => default.to_string(),
}
}
fn env_path(key: &str, default: &str) -> PathBuf {
PathBuf::from(env_string(key, default))
}