use std::path::PathBuf; use serde::Deserialize; /// Enable secure cookies by default only in release mode /// (Secure cookies can't be set over HTTP. Rough assumption: development is done over HTTP) const DEFAULT_COOKIES_SECURE: bool = cfg!(not(debug_assertions)); #[derive(Debug, Clone, Deserialize)] pub struct AuthConfig { pub open_signup: bool, pub cookies_secure: bool, } /// Build a connection URI from parts fn format_uri( scheme: &str, username: &Option, password: &Option, host: &str, port: &Option, path: &Option, ) -> String { let mut url = format!("{scheme}://"); if let Some(username) = username { url.push_str(username); if let Some(password) = password { url.push_str(&format!(":{password}")); } url.push('@'); } url.push_str(host); if let Some(port) = port { url.push_str(&format!(":{port}")); } if let Some(path) = path { url.push_str(&format!("/{path}")); } url } #[derive(Debug, Clone, Deserialize)] pub struct DatabaseConfig { #[serde(flatten)] connection: DatabaseConnectionConfig, } impl DatabaseConfig { /// Get the configured database connection URI pub fn connection_uri(&self) -> String { self.connection.as_uri() } } #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] enum DatabaseConnectionConfig { FromUrl { url: String, }, FromParts { host: String, port: Option, database: Option, username: Option, password: Option, }, } impl DatabaseConnectionConfig { /// Convert this configuration into the Postgres connection URI pub fn as_uri(&self) -> String { match self { Self::FromUrl { url } => url.clone(), Self::FromParts { host, port, database, username, password, } => format_uri("postgres", username, password, host, port, database), } } } #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] enum KeyValStoreConnectionConfig { FromUrl { url: String, }, FromParts { scheme: Option, host: String, port: Option, database: Option, username: Option, password: Option, }, } impl KeyValStoreConnectionConfig { /// Convert this configuration into the Redis connection URI pub fn as_uri(&self) -> String { match self { Self::FromUrl { url } => url.clone(), Self::FromParts { scheme, host, port, database, username, password, } => format_uri( scheme.as_deref().unwrap_or("redis"), username, password, host, port, database, ), } } } #[derive(Debug, Clone, Deserialize)] pub struct KeyValStoreConfig { #[serde(flatten)] connection: KeyValStoreConnectionConfig, } impl KeyValStoreConfig { /// Get the configured database connection URI pub fn connection_uri(&self) -> String { self.connection.as_uri() } } #[derive(Debug, Clone, Deserialize)] pub struct ServerConfig { pub public_path: PathBuf, } #[derive(Debug, Clone, Deserialize)] /// Top-level application configuration pub struct Config { pub auth: AuthConfig, pub database: DatabaseConfig, pub key_val_store: KeyValStoreConfig, pub server: ServerConfig, } /// Parse configuration from the expected files and environment variables pub fn load_config() -> Result { use config::{Environment, File}; let pkg_name = env!("CARGO_PKG_NAME"); config::Config::builder() .set_default("server.port", 8080)? .set_default("auth.open_signup", false)? .set_default("auth.cookies_secure", DEFAULT_COOKIES_SECURE)? .set_default("server.public_path", default_public_dir())? .add_source(File::with_name(&format!("/etc/{pkg_name}/config")).required(false)) .add_source(File::with_name(&format!("/etc/{pkg_name}")).required(false)) .add_source(File::with_name("config").required(false)) .add_source(Environment::with_prefix(pkg_name).separator("_")) .build()? .try_deserialize() } /// Provide a sane default for the public path, using the same sources as Dioxus does internally. /// Checks the `DIOXUS_PUBLIC_PATH` environment variable, then tries relative to the path of this /// executable fn default_public_dir() -> Option { std::env::var("DIOXUS_PUBLIC_PATH").ok().or_else(|| { std::env::current_exe().ok().and_then(|path| { path.parent() .expect("current executable path must have a parent") .join("public") .into_os_string() .into_string() .ok() }) }) }