use std::net::SocketAddr;
use std::path::PathBuf;
use serde::Deserialize;
const CONFIG_FILE_NAME: &str = "mail4agent.toml";
const DEFAULT_BIND: &str = "127.0.0.1:18301";
const DB_PATH_ENV: &str = "MAIL4AGENT_DB_PATH";
const BIND_ENV: &str = "MAIL4AGENT_BIND";
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Config {
pub bind: SocketAddr,
pub db_path: Option<PathBuf>,
}
impl Default for Config {
fn default() -> Self {
Self {
bind: DEFAULT_BIND
.parse()
.expect("DEFAULT_BIND is a valid socket address literal"),
db_path: None,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("read {0}: {1}")]
Read(PathBuf, std::io::Error),
#[error("parse {0}: {1}")]
Parse(PathBuf, Box<toml::de::Error>),
#[error(
"no home/data directory available to place the default mailbox database -- \
set {DB_PATH_ENV} explicitly"
)]
NoDataDir,
}
impl Config {
pub fn load() -> Result<Self, ConfigError> {
let path = PathBuf::from(CONFIG_FILE_NAME);
let mut cfg = if !path.exists() {
Self::default()
} else {
let raw = std::fs::read_to_string(&path).map_err(|err| ConfigError::Read(path.clone(), err))?;
toml::from_str(&raw).map_err(|err| ConfigError::Parse(path, Box::new(err)))?
};
if let Ok(bind) = std::env::var(BIND_ENV) {
cfg.bind = bind.parse().map_err(|err| {
ConfigError::Read(
PathBuf::from(BIND_ENV),
std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{err}")),
)
})?;
}
Ok(cfg)
}
pub fn resolve_db_path(&self) -> Result<PathBuf, ConfigError> {
if let Ok(from_env) = std::env::var(DB_PATH_ENV) {
return Ok(PathBuf::from(from_env));
}
if let Some(path) = &self.db_path {
return Ok(path.clone());
}
let dirs = directories::ProjectDirs::from("", "", "mail4agent").ok_or(ConfigError::NoDataDir)?;
Ok(dirs.data_dir().join("mail4agent.sqlite"))
}
}