Front-load config in QuireCi::new
Load and parse config at construction time instead of on every call.
QuireCi now stores GlobalConfig and exposes it via config(). Missing
config.fnl falls back to defaults (port 3000, no sentry). server.rs
no longer deals with Result for config access.

Assisted-by: Owl Alpha via pi
change skwpoksvrluszyxqxuxysmtzypyrtkuo
commit eae6b89fa7686f61b6bf1629157a4826c1b21c10
author Alpha Chen <alpha@kejadlen.dev>
date
parent xvlkwotq
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index 891c29a..f5adf44 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -298,7 +298,7 @@ fn main() -> Result<()> {
     match cli.command {
         Commands::Validate => validate(workspace),
         Commands::Serve => {
-            let quire = quire::QuireCi::new(cli.base_dir);
+            let quire = quire::QuireCi::new(cli.base_dir)?;
             let rt = tokio::runtime::Builder::new_multi_thread()
                 .enable_all()
                 .build()
diff --git a/quire-ci/src/quire.rs b/quire-ci/src/quire.rs
index 18149f8..4b33fa4 100644
--- a/quire-ci/src/quire.rs
+++ b/quire-ci/src/quire.rs
@@ -1,11 +1,11 @@
 use std::path::PathBuf;
 
-use miette::{Result, ensure};
+use miette::{IntoDiagnostic, Result};
 
 use quire_core::fennel::Fennel;
 
 /// Parsed global configuration (`<base-dir>/config.fnl`).
-#[derive(serde::Deserialize, Debug)]
+#[derive(serde::Deserialize, Debug, Clone)]
 #[serde(rename_all = "kebab-case")]
 pub struct GlobalConfig {
     #[serde(default)]
@@ -19,38 +19,43 @@ fn default_port() -> u16 {
     3000
 }
 
-#[derive(serde::Deserialize, Debug)]
+#[derive(serde::Deserialize, Debug, Clone)]
 pub struct SentryConfig {
     pub dsn: quire_core::secret::SecretString,
 }
 
 /// Application runtime context.
 ///
-/// Carries configuration and provides resolved paths.
+/// Loads config at construction time so callers don't have to thread
+/// Results around.
 #[derive(Clone)]
 pub struct QuireCi {
-    base_dir: PathBuf,
+    config: GlobalConfig,
 }
 
 impl QuireCi {
-    pub fn new(base_dir: PathBuf) -> Self {
-        Self { base_dir }
+    pub fn new(base_dir: PathBuf) -> Result<Self> {
+        let config_path = base_dir.join("config.fnl");
+        let config = if config_path.exists() {
+            let fennel = Fennel::new().into_diagnostic()?;
+            fennel.load_file(&config_path).into_diagnostic()?
+        } else {
+            GlobalConfig::default()
+        };
+        Ok(Self { config })
     }
 
-    pub fn config_path(&self) -> PathBuf {
-        self.base_dir.join("config.fnl")
+    pub fn config(&self) -> &GlobalConfig {
+        &self.config
     }
+}
 
-    /// Load and parse the global Fennel config file.
-    pub fn global_config(&self) -> Result<GlobalConfig> {
-        let config_path = self.config_path();
-        ensure!(
-            config_path.exists(),
-            "config not found: {}",
-            config_path.display()
-        );
-        let fennel = Fennel::new()?;
-        Ok(fennel.load_file(&config_path)?)
+impl Default for GlobalConfig {
+    fn default() -> Self {
+        Self {
+            sentry: None,
+            port: default_port(),
+        }
     }
 }
 
@@ -64,9 +69,8 @@ mod tests {
         let config_path = dir.path().join("config.fnl");
         fs_err::write(&config_path, "{}").expect("write");
 
-        let q = QuireCi::new(dir.path().to_path_buf());
-        let config = q.global_config().expect("global_config should load");
-        assert_eq!(config.port, 3000);
+        let q = QuireCi::new(dir.path().to_path_buf()).expect("should load");
+        assert_eq!(q.config().port, 3000);
     }
 
     #[test]
@@ -75,20 +79,17 @@ mod tests {
         let config_path = dir.path().join("config.fnl");
         fs_err::write(&config_path, r#"{:port 4000}"#).expect("write");
 
-        let q = QuireCi::new(dir.path().to_path_buf());
-        let config = q.global_config().expect("global_config should load");
-        assert_eq!(config.port, 4000);
+        let q = QuireCi::new(dir.path().to_path_buf()).expect("should load");
+        assert_eq!(q.config().port, 4000);
     }
 
     #[test]
-    fn global_config_missing_file_errors() {
+    fn global_config_missing_file_uses_defaults() {
         let dir = tempfile::tempdir().expect("tempdir");
-        let q = QuireCi::new(dir.path().to_path_buf());
-        let err = q.global_config().unwrap_err();
-        assert!(
-            err.to_string().contains("config not found"),
-            "expected config not found error, got {err:?}"
-        );
+
+        let q = QuireCi::new(dir.path().to_path_buf()).expect("should load");
+        assert_eq!(q.config().port, 3000);
+        assert!(q.config().sentry.is_none());
     }
 
     #[test]
@@ -101,9 +102,12 @@ mod tests {
         )
         .expect("write");
 
-        let q = QuireCi::new(dir.path().to_path_buf());
-        let config = q.global_config().expect("global_config should load");
-        let sentry = config.sentry.expect("sentry should be present");
+        let q = QuireCi::new(dir.path().to_path_buf()).expect("should load");
+        let sentry = q
+            .config()
+            .sentry
+            .as_ref()
+            .expect("sentry should be present");
         assert_eq!(sentry.dsn.reveal().unwrap(), "https://key@sentry.io/123");
     }
 }
diff --git a/quire-ci/src/server.rs b/quire-ci/src/server.rs
index 94677eb..266774c 100644
--- a/quire-ci/src/server.rs
+++ b/quire-ci/src/server.rs
@@ -19,21 +19,8 @@ async fn index() -> String {
 }
 
 /// Initialize Sentry if the global config provides a DSN.
-///
-/// Returns the guard if initialized, or None if Sentry is not configured.
-/// Logs a warning on failure but does not abort.
 fn init_sentry(quire: &QuireCi) -> Option<ClientInitGuard> {
-    let config = quire
-        .global_config()
-        .inspect_err(|e| {
-            tracing::warn!(
-                error = %e,
-                "failed to load global config, skipping Sentry init"
-            );
-        })
-        .ok()?;
-
-    let sentry_config = config.sentry.as_ref()?;
+    let sentry_config = quire.config().sentry.as_ref()?;
     let dsn = sentry_config
         .dsn
         .reveal()
@@ -52,13 +39,7 @@ fn init_sentry(quire: &QuireCi) -> Option<ClientInitGuard> {
 }
 
 pub async fn run(quire: QuireCi) -> Result<()> {
-    let config = quire
-        .global_config()
-        .inspect(|c| tracing::info!(port = c.port, "loaded config"))
-        .inspect_err(|e| tracing::warn!(error = %e, "proceeding with defaults"))
-        .ok();
-
-    let port = config.as_ref().map(|c| c.port).unwrap_or(3000);
+    let port = quire.config().port;
 
     let _sentry = init_sentry(&quire);
     let miette_layer = telemetry::MietteLayer::new();