diff --git a/server/svix-server/Cargo.toml b/server/svix-server/Cargo.toml index 992f6d0d2..e1e3f466b 100644 --- a/server/svix-server/Cargo.toml +++ b/server/svix-server/Cargo.toml @@ -73,12 +73,12 @@ omniqueue = { git = "https://github.com/svix/omniqueue-rs", rev = "75e5a9510ad33 # Switch to hyper-http-proxy when upgrading hyper to 1.0. hyper-proxy = { version = "=0.9.1", default-features = false, features = ["openssl-tls"] } hex = "0.4.3" +anyhow = "1.0.56" [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = { version = "0.5", optional = true } [dev-dependencies] -anyhow = "1.0.56" assert_matches = "1.5.0" # NOTE: Purposely not the latest version such as not to mess up the `hyper` fork patch axum-server = { version = "0.5", features = ["tls-openssl"] } diff --git a/server/svix-server/src/cfg.rs b/server/svix-server/src/cfg.rs index d34435d3f..f76c5be32 100644 --- a/server/svix-server/src/cfg.rs +++ b/server/svix-server/src/cfg.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, collections::HashMap, fmt, net::SocketAddr, sync::Arc, time::Duration}; +use anyhow::{bail, Context}; use figment::{ providers::{Env, Format, Toml}, Figment, @@ -533,7 +534,20 @@ impl From for omniqueue::backends::redis::SentinelConfig { } } -pub fn load() -> Result> { +/// Try to extract a [`ConfigurationInner`] from the provided [`Figment`]. Any error message should +/// indicate the missing required field(s). +fn try_extract(figment: Figment) -> anyhow::Result { + // Explicitly override error if `jwt_secret` is not set, as the default error does not mention + // the field name due it coming from an inlined field `ConfigurationInner::jwt_signing_config` + // See: + if !figment.contains("jwt_secret") { + bail!("missing field `jwt_secret`"); + } + + Ok(figment.extract()?) +} + +pub fn load() -> anyhow::Result> { if let Ok(db_url) = std::env::var("DATABASE_URL") { // If we have DATABASE_URL set, we should potentially use it. const DB_DSN: &str = "SVIX_DB_DSN"; @@ -542,14 +556,16 @@ pub fn load() -> Result> { } } - let config: ConfigurationInner = Figment::new() + let merged = Figment::new() .merge(Toml::string(DEFAULTS)) .merge(Toml::file("config.toml")) - .merge(Env::prefixed("SVIX_")) - .extract() - .expect("Error loading configuration"); + .merge(Env::prefixed("SVIX_")); + + let config = try_extract(merged).context("failed to extract configuration")?; - config.validate().expect("Error validating configuration"); + config + .validate() + .context("failed to validate configuration")?; Ok(Arc::from(config)) } @@ -562,7 +578,7 @@ mod tests { Figment, }; - use super::{load, CacheBackend, CacheType, QueueBackend, QueueType}; + use super::{load, try_extract, CacheBackend, CacheType, QueueBackend, QueueType}; use crate::core::security::{JWTAlgorithm, JwtSigningConfig}; #[test] @@ -582,6 +598,16 @@ mod tests { assert_eq!(cfg.cache_backend(), CacheBackend::Redis("test_b")); } + #[test] + fn test_try_extract_missing_jwt_secret() { + let defaults = Figment::new(); + + let actual = try_extract(defaults); + + let err = actual.unwrap_err(); + assert_eq!(err.to_string(), "missing field `jwt_secret`"); + } + #[test] fn test_jwt_signing_fallback() { let raw_config = r#" diff --git a/server/svix-server/src/main.rs b/server/svix-server/src/main.rs index 3ae00b6ae..6d2af5475 100644 --- a/server/svix-server/src/main.rs +++ b/server/svix-server/src/main.rs @@ -4,6 +4,7 @@ #![warn(clippy::all)] #![forbid(unsafe_code)] +use anyhow::bail; use clap::{Parser, Subcommand}; use dotenvy::dotenv; use svix_server::{ @@ -108,11 +109,11 @@ fn org_id_parser(s: &str) -> Result { } #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { dotenv().ok(); let args = Args::parse(); - let cfg = cfg::load().expect("Error loading configuration"); + let cfg = cfg::load()?; let (tracing_subscriber, _guard) = setup_tracing(&cfg, /* for_test = */ false); tracing_subscriber.init(); @@ -136,8 +137,7 @@ async fn main() { } if let Err(e) = futures::future::try_join_all(wait_for).await { - tracing::error!("{e}"); - return; + bail!(e); } } @@ -201,4 +201,5 @@ async fn main() { }; opentelemetry::global::shutdown_tracer_provider(); + Ok(()) }