finga
7ad6f610d1
In order to keep the configuration globally available use `once_cell`. Also check for a valid email address in the part which is receiving the data from the api.
176 lines
4.4 KiB
Rust
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/{}/{}", env!("CARGO_BIN_NAME"), file);
|
|
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")
|
|
}
|
|
}
|