use anyhow::{Context, Result};
use tracing::{event, Level};
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
pub struct DiscordConfig {
pub discord_token: String,
pub database_url: String,
pub redis_url: String,
pub job_interval_min: u32,
pub shard_key: uuid::Uuid,
}
const ENV_PREFIX: &str = "FERCORD";
impl DiscordConfig {
#[allow(dead_code)]
#[tracing::instrument]
pub fn from_env() -> Result<Self> {
let builder =
config::Config::builder().add_source(config::Environment::with_prefix(ENV_PREFIX));
let config = builder
.build()
.with_context(|| "Error building Fercord configuration")?;
config
.try_deserialize::<DiscordConfig>()
.with_context(|| "Error deserializing configuration")
}
#[allow(dead_code)]
#[tracing::instrument]
pub fn from_env_and_file(path: &str) -> Result<Self> {
event!(Level::DEBUG, "Building configuration from environment and file {}", path);
let builder = config::Config::builder()
.add_source(config::File::with_name(path))
.add_source(config::Environment::with_prefix(ENV_PREFIX));
let config = builder
.build()
.with_context(|| "Error building Fercord configuration")?;
config
.try_deserialize::<DiscordConfig>()
.with_context(|| "Error deserializing configuration")
}
#[cfg(test)]
fn from_file(path: &str) -> Result<Self> {
let builder = config::Config::builder().add_source(config::File::with_name(path));
let config = builder
.build()
.with_context(|| "Error building Fercord configuration")?;
config
.try_deserialize::<DiscordConfig>()
.with_context(|| "Error deserializing configuration")
}
}
impl Default for DiscordConfig {
fn default() -> Self {
Self::from_env()
.with_context(|| "Error creating config from environment")
.unwrap()
}
}
#[cfg(test)]
mod tests {
use std::env;
use super::*;
const TEST_CONFIG_PATH: &str = "../.testdata/basic_config.toml";
#[test]
fn can_deserialize_toml() {
let expected = DiscordConfig {
discord_token: "111".into(),
database_url: "sqlite://:memory:".into(),
redis_url: "redis://localhost".into(),
job_interval_min: 1,
shard_key: uuid::uuid!("c69b7bb6-0ca4-40da-8bad-26d9d4d2fb50"),
};
let config = DiscordConfig::from_file(TEST_CONFIG_PATH).unwrap();
assert_eq!(expected, config);
}
#[test]
fn env_overwrites_file() {
env_setup();
let expected = DiscordConfig {
discord_token: "222".into(),
database_url: "sqlite://:memory:".into(),
redis_url: "redis://localhost".into(),
job_interval_min: 1,
shard_key: uuid::uuid!("c69b7bb6-0ca4-40da-8bad-26d9d4d2fb50"),
};
let config = DiscordConfig::from_env_and_file(TEST_CONFIG_PATH).unwrap();
assert_eq!(expected, config);
env_teardown();
}
fn env_setup() {
env::set_var(format!("{}_DISCORD_TOKEN", ENV_PREFIX), "222");
}
fn env_teardown() {
env::remove_var(format!("{}_DISCORD_TOKEN", ENV_PREFIX));
}
}