use figment::{providers::Env, value::UncasedStr, Figment}; use serde::{Deserialize, Deserializer}; #[derive(Debug, Clone, Deserialize)] pub struct Config { // Database URL pub database_url: String, // Discord Credentials #[serde(deserialize_with = "deserialize_string_from_any")] pub discord_client_id: String, pub discord_client_secret: String, // GitHub Credentials #[serde(deserialize_with = "deserialize_string_from_any")] pub github_client_id: String, pub github_client_secret: String, // S3 Credentials pub s3_access_key: String, pub s3_secret_access_key: String, pub s3_bucket_name: String, pub s3_public_base_url: String, // Server Details #[serde(default = "default_port")] pub port: u16, #[serde(default = "default_host")] pub host: std::net::IpAddr, #[serde(default = "default_shutdown_timeout")] pub shutdown_timeout_seconds: u32, // Public base URL used for OAuth redirect URIs pub public_base_url: String, // JWT pub jwt_secret: String, } // Standard User-Agent: name/version (+site) pub const USER_AGENT: &str = concat!( env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"), " (+https://pacman.xevion.dev)" ); fn default_host() -> std::net::IpAddr { "0.0.0.0".parse().unwrap() } fn default_port() -> u16 { 3000 } fn default_shutdown_timeout() -> u32 { 5 } fn deserialize_string_from_any<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { use serde_json::Value; let value = Value::deserialize(deserializer)?; match value { Value::String(s) => Ok(s), Value::Number(n) => Ok(n.to_string()), _ => Err(serde::de::Error::custom("Expected string or number")), } } pub fn load_config() -> Config { Figment::new() .merge(Env::raw().map(|key| { if key == UncasedStr::new("RAILWAY_DEPLOYMENT_DRAINING_SECONDS") { "SHUTDOWN_TIMEOUT_SECONDS".into() } else { key.into() } })) .extract() .expect("Failed to load config") }