remindrs/src/config.rs
finga e59fa30b78
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Fix CI clippy error
Variables can be used directly in the `format!` string.
2023-02-06 17:05:05 +01:00

176 lines
4.4 KiB
Rust

use anyhow::{anyhow, bail, Error, Result};
use lettre::{message::Mailbox, Address};
use once_cell::sync::OnceCell;
use serde::Deserialize;
use std::{
env, fs,
net::{IpAddr, Ipv6Addr},
path::PathBuf,
};
use tracing::{error, info, trace, warn};
static CONFIG: OnceCell<Config> = OnceCell::new();
fn default_database_host() -> String {
"localhost".to_string()
}
const fn default_database_port() -> u16 {
5432
}
fn default_database_name() -> String {
env!("CARGO_BIN_NAME").to_string()
}
fn default_database_user() -> String {
env!("CARGO_BIN_NAME").to_string()
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Database {
/// Host of the database
#[serde(default = "default_database_host")]
pub host: String,
/// Port of the database
#[serde(default = "default_database_port")]
pub port: u16,
/// Name of the database
#[serde(default = "default_database_name")]
pub name: String,
/// Name of the user to connect to the database
#[serde(default = "default_database_user")]
pub user: String,
/// Password of the user to connect to the database
pub pass: String,
}
const fn default_server_address() -> IpAddr {
IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))
}
const fn default_server_port() -> u16 {
8080
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Server {
/// Listening address of the server
#[serde(default = "default_server_address")]
pub address: IpAddr,
/// Port of the database
#[serde(default = "default_server_port")]
pub port: u16,
}
impl Default for Server {
fn default() -> Self {
Self {
address: default_server_address(),
port: default_server_port(),
}
}
}
fn default_email_from() -> Mailbox {
Mailbox::new(
Some(env!("CARGO_BIN_NAME").to_string()),
Address::new(env!("CARGO_BIN_NAME"), "localhost").unwrap(),
)
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Email {
/// Address the notification emails are sent from
#[serde(default = "default_email_from")]
pub from: Mailbox,
}
impl Default for Email {
fn default() -> Self {
Self {
from: default_email_from(),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
/// Database configuration
pub database: Database,
/// Server configuration
#[serde(default)]
pub server: Server,
/// Email configuration
#[serde(default)]
pub email: Email,
}
impl Config {
fn load_file(file: &PathBuf) -> Result<Self> {
info!(?file, "loading configuration");
let config = toml::from_str(&fs::read_to_string(file)?)?;
trace!(?file, ?config, "loaded configuration");
Ok(config)
}
#[allow(clippy::cognitive_complexity)]
fn try_paths() -> Result<Self> {
let file = "config.toml";
match Self::load_file(&file.into()) {
Ok(config) => return Ok(config),
Err(error) => warn!(?file, "cannot load configuration: {:#}", Error::msg(error)),
}
let user_config =
xdg::BaseDirectories::with_prefix(env!("CARGO_BIN_NAME"))?.get_config_file(file);
match Self::load_file(&user_config) {
Ok(config) => return Ok(config),
Err(error) => {
warn!(file = ?user_config, "cannot load configuration: {:#}", Error::msg(error));
}
}
let global_config = format!("/etc/{}/{file}", env!("CARGO_BIN_NAME"));
match Self::load_file(&global_config.clone().into()) {
Ok(config) => return Ok(config),
Err(error) => {
warn!(file = ?global_config, "cannot load configuration: {:#}", Error::msg(error));
}
}
error!("no valid configuration file found");
bail!("no valid configuration file found");
}
pub fn load_config(file: Option<PathBuf>) -> Result<Self> {
if let Some(file) = file {
Ok(Self::load_file(&file)?)
} else {
Ok(Self::try_paths()?)
}
}
pub fn init(file: Option<PathBuf>) -> Result<()> {
CONFIG
.set(Self::load_config(file)?)
.map_err(|_| anyhow!("configuration already initialized"))?;
Ok(())
}
pub fn get() -> &'static Self {
CONFIG.get().expect("config is not initialized")
}
}