//! Runtime application configuration values. use anyhow::{Context as _, Result}; use axum::extract::FromRef; use config::{Config, Environment}; use dotenvy::dotenv; use serde::Deserialize; use tracing::info; use url::Url; use crate::app::App; /// Runtime application configuration values, typically read from environment /// variables. /// /// This may be used with the [`axum::extract::State`] extractor. #[derive(Clone, Debug, Deserialize)] pub(crate) struct Settings { /// Prefix under which to nest all routes. If specified, include leading /// slash but no trailing slash, for example "/app". For default behavior, /// leave as empty string. #[serde(default)] pub(crate) root_path: String, /// postgresql:// URL for Phonograph's application database. pub(crate) database_url: Url, #[serde(default = "default_app_db_max_connections")] pub(crate) app_db_max_connections: u32, /// When set to 1, embedded SQLx migrations will be run on startup. #[serde(default)] pub(crate) run_database_migrations: u8, /// Address for server to bind to #[serde(default = "default_host")] pub(crate) host: String, /// Port for server to bind to #[serde(default = "default_port")] pub(crate) port: u16, /// Host visible to end users, for example "https://phono.dev" pub(crate) frontend_host: String, pub(crate) auth: AuthSettings, } fn default_app_db_max_connections() -> u32 { 16 } fn default_port() -> u16 { 8080 } fn default_host() -> String { "127.0.0.1".to_owned() } /// OAuth2 and session cookie settings. #[derive(Clone, Debug, Deserialize)] pub(crate) struct AuthSettings { pub(crate) client_id: String, pub(crate) client_secret: String, pub(crate) auth_url: String, pub(crate) token_url: String, pub(crate) userinfo_url: String, pub(crate) logout_url: Option, #[serde(default = "default_cookie_name")] pub(crate) cookie_name: String, } fn default_cookie_name() -> String { "PHONO_SESSION".to_string() } impl Settings { /// Load environment variables into a [`Settings`] struct. pub(crate) fn load() -> Result { let s = Config::builder() .add_source(Environment::default().separator("__")) .build() .context("config error")?; s.try_deserialize().context("deserialize error") } } impl FromRef for Settings { fn from_ref(state: &App) -> Self { state.settings.clone() } } /// Attempt to load environment variables from .env file. pub(crate) fn load_dotenv() -> Result<()> { dotenv() .map(|pathbuf| { info!( "using env file {0}", pathbuf .to_str() .ok_or(anyhow::anyhow!("pathbuf is not valid unicode"))? ); Ok(()) }) .or_else(|err| { if err.not_found() { info!("no env file loaded"); Ok(Ok(())) } else { Err(err) } })? }