ForgeFlux/src/settings/mod.rs

163 lines
5.2 KiB
Rust

use std::path::Path;
use std::{env, fs};
use config::builder::DefaultState;
use config::{Config, ConfigBuilder, ConfigError, File};
use serde::Deserialize;
use url::Url;
pub mod database;
pub mod forges;
pub mod server;
use database::{DBType, Database};
use forges::Forges;
use server::Server;
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
pub struct Settings {
pub debug: bool,
pub log: String,
pub source_code: String,
pub allow_registration: bool,
pub database: Database,
pub forges: Forges,
pub server: Server,
// pub smtp: Option<Smtp>,
}
impl Settings {
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
for (parameter, env_var_name) in [
("debug", "FORGEFLUX_debug"),
("source_code", "FORGEFLUX_source_code"),
("allow_registration", "FORGEFLUX_allow_registration"),
]
.iter()
{
if let Ok(val) = env::var(env_var_name) {
log::debug!("Overriding [{parameter}] with environment variable {env_var_name}");
s = s.set_override(parameter, val).unwrap();
}
}
s = Database::env_override(s);
s = Forges::env_override(s);
Server::env_override(s)
}
pub fn new() -> Result<Self, ConfigError> {
let mut s = Config::builder();
const CURRENT_DIR: &str = "./config/default.toml";
const ETC: &str = "/etc/forgeflux/config.toml";
// Will be overridden after config is parsed and loaded into Settings by
// Settings::set_database_type.
// This parameter is not ergonomic for users, but it is required and can be programatically
// inferred. But we need a default value for config lib to parse successfully, since it is
// DBType and not Option<DBType>
s = s
.set_default("database.database_type", DBType::Postgres.to_string())
.expect("unable to set database.database_type default config");
s = s
.set_default("log", "INFO")
.expect("unable to set log default config");
if let Ok(path) = env::var("FORGEFLUX_CONFIG") {
let absolute_path = Path::new(&path).canonicalize().unwrap();
log::info!(
"Loading config file from {}",
absolute_path.to_str().unwrap()
);
s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
} else if Path::new(CURRENT_DIR).exists() {
let absolute_path = fs::canonicalize(CURRENT_DIR).unwrap();
log::info!(
"Loading config file from {}",
absolute_path.to_str().unwrap()
);
// merging default config from file
s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
} else if Path::new(ETC).exists() {
log::info!("{}", format!("Loading config file from {}", ETC));
s = s.add_source(File::with_name(ETC));
} else {
log::warn!("Configuration file not found");
}
s = Self::env_override(s);
let mut settings = s.build()?.try_deserialize::<Settings>()?;
settings.check_url();
settings.database.set_database_type();
Ok(settings)
}
fn check_url(&self) {
Url::parse(&self.source_code).expect("Please enter a URL for source_code in settings");
}
}
#[cfg(test)]
pub mod tests {
use url::Url;
use super::Settings;
use crate::db::create_database::CreateDatabase;
use crate::db::delete_database::DeleteDatabase;
use crate::db::migrate::RunMigrations;
use crate::db::sqlx_postgres::Postgres;
use crate::utils::get_random;
#[macro_export]
macro_rules! env_helper {
($init_settings:ident, $env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
println!("Setting env var {} to {} for test", $env, $val);
env::set_var($env, $val);
{
let new_settings = crate::settings::Settings::new().unwrap();
assert_eq!(new_settings.$($param).+, $val_typed, "should match");
assert_ne!(new_settings.$($param).+, $init_settings.$($param).+);
}
env::remove_var($env);
};
($init_settings:ident, $env:expr, $val:expr, $($param:ident).+) => {
env_helper!($init_settings, $env, $val.to_string(), $val, $($param).+);
};
}
pub async fn get_settings() -> Settings {
let mut settings = Settings::new().unwrap();
let mut db_url = Url::parse(&settings.database.url).unwrap();
db_url.set_path(&get_random(12));
settings.database.url = db_url.to_string();
crate::db::sqlx_postgres::PostgresDatabase
.create_database(&db_url)
.await;
let db = Postgres::new(
sqlx::postgres::PgPool::connect(&settings.database.url)
.await
.unwrap(),
);
db.migrate().await;
settings
}
impl Settings {
pub async fn drop_db(&self) {
crate::db::sqlx_postgres::PostgresDatabase
.delete_database(&Url::parse(&self.database.url).unwrap())
.await;
}
}
}