From 8ff5046ff3f6c24fdff53385a4ac39a0d89c9790 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 10:49:39 +0100 Subject: [PATCH] refactor(maestro): one default-on-missing config loader load() and load_or_default() differed only in whether a missing file was an error. Nothing wants the error: every field has a default. Keeps load(), drops the MissingFile variant, whose message pointed at the `maestro daemon configure` subcommand the single-host refactor deleted. Co-Authored-By: Claude Opus 5 (1M context) --- crates/maestro-cli/src/config.rs | 2 +- crates/maestro-config/src/lib.rs | 36 ++++------------------------- crates/maestro-daemon/src/config.rs | 2 +- crates/maestro-tui/src/lib.rs | 5 +--- crates/maestro-web/src/config.rs | 6 +---- 5 files changed, 9 insertions(+), 42 deletions(-) diff --git a/crates/maestro-cli/src/config.rs b/crates/maestro-cli/src/config.rs index 4a112ec..7eaeba4 100644 --- a/crates/maestro-cli/src/config.rs +++ b/crates/maestro-cli/src/config.rs @@ -45,7 +45,7 @@ impl CliConfig { /// Load the subset of the unified file the CLI consumes. A missing file is /// fine — every field has a default. pub fn load(path: &Path) -> Result { - let file = MaestroConfigFile::load_or_default(path)?; + let file = MaestroConfigFile::load(path)?; Ok(Self { daemon_url: file.cli.daemon_url, daemon_bind_addr: file.daemon.bind_addr, diff --git a/crates/maestro-config/src/lib.rs b/crates/maestro-config/src/lib.rs index 4aed6eb..2e3c888 100644 --- a/crates/maestro-config/src/lib.rs +++ b/crates/maestro-config/src/lib.rs @@ -41,12 +41,6 @@ pub enum ConfigError { TomlEncode(#[from] toml::ser::Error), #[error("could not determine config directory (XDG_CONFIG_HOME / $HOME unset?)")] NoConfigDir, - #[error( - "maestro config not found at {}\n\ - Run `maestro daemon configure` to create it.", - path.display() - )] - MissingFile { path: PathBuf }, #[error("invalid {env} ({input:?}): {source}")] InvalidBindAddr { env: &'static str, @@ -314,27 +308,17 @@ fn validate_presets(presets: &[SpawnPreset]) -> Result<(), SpawnConfigError> { } impl MaestroConfigFile { - /// Strict load — error with [`ConfigError::MissingFile`] if absent. + /// Load the file, or return an empty config if it doesn't exist. Every + /// field has a default, so a missing file is never an error. pub fn load(path: &Path) -> Result { if !path.exists() { - return Err(ConfigError::MissingFile { - path: path.to_path_buf(), - }); + return Ok(Self::default()); } let bytes = std::fs::read(path)?; let s = std::str::from_utf8(&bytes).unwrap_or(""); Ok(toml::from_str(s)?) } - /// Load if present, else return an empty config. Use for callers that - /// want a default-on-missing read. - pub fn load_or_default(path: &Path) -> Result { - if !path.exists() { - return Ok(Self::default()); - } - Self::load(path) - } - /// Atomic write (tmp + rename). Creates parent dirs on demand. pub fn save(&self, path: &Path) -> Result<(), ConfigError> { if let Some(parent) = path.parent() { @@ -447,20 +431,10 @@ mod tests { } #[test] - fn missing_file_strict_load_errors_with_pointer_to_configure() { + fn missing_file_loads_as_empty_config() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("config.toml"); - let err = MaestroConfigFile::load(&path).unwrap_err(); - let msg = err.to_string(); - assert!(matches!(err, ConfigError::MissingFile { .. })); - assert!(msg.contains("maestro daemon configure"), "msg was: {msg}"); - } - - #[test] - fn missing_file_load_or_default_returns_empty() { - let dir = tempfile::tempdir().unwrap(); - let path = dir.path().join("config.toml"); - let cfg = MaestroConfigFile::load_or_default(&path).unwrap(); + let cfg = MaestroConfigFile::load(&path).unwrap(); assert_eq!(cfg, MaestroConfigFile::default()); } diff --git a/crates/maestro-daemon/src/config.rs b/crates/maestro-daemon/src/config.rs index a0068ad..6678479 100644 --- a/crates/maestro-daemon/src/config.rs +++ b/crates/maestro-daemon/src/config.rs @@ -111,7 +111,7 @@ impl EnvOverrides { /// Resolve the runtime configuration: env overrides on top of the unified /// file, then defaults. A missing file is not an error. pub fn load_config(path: &Path, env: EnvOverrides) -> Result { - let file = MaestroConfigFile::load_or_default(path)?; + let file = MaestroConfigFile::load(path)?; let daemon = file.daemon; // Resolve spawn presets up front so a bad `[spawn].presets` (empty/dup id, diff --git a/crates/maestro-tui/src/lib.rs b/crates/maestro-tui/src/lib.rs index 6f4a9c0..c0220bb 100644 --- a/crates/maestro-tui/src/lib.rs +++ b/crates/maestro-tui/src/lib.rs @@ -1,7 +1,4 @@ //! `maestro tui` — terminal UI client over the local daemon's `/api/*` surface. -//! -//! Design: `docs/superpowers/specs/2026-05-28-maestro-tui-design.md` -//! Implementation: `docs/superpowers/plans/2026-05-28-maestro-tui.md` #![deny(missing_debug_implementations)] @@ -74,7 +71,7 @@ pub async fn run(cfg: RunConfig) -> Result<(), TuiError> { maestro_config::resolve_config_path().map_err(|e| TuiError::Config(e.to_string())) }); let cfg_file = match cfg_path { - Ok(path) => maestro_config::MaestroConfigFile::load_or_default(&path) + Ok(path) => maestro_config::MaestroConfigFile::load(&path) .map_err(|e| TuiError::Config(e.to_string()))?, Err(_) => maestro_config::MaestroConfigFile::default(), }; diff --git a/crates/maestro-web/src/config.rs b/crates/maestro-web/src/config.rs index b7050fb..15be39c 100644 --- a/crates/maestro-web/src/config.rs +++ b/crates/maestro-web/src/config.rs @@ -59,11 +59,7 @@ impl EnvOverrides { /// Resolve the runtime configuration. Missing-file is fine: every field has /// a default and the binary is happy to run with no config.toml at all. pub fn load_config(path: &Path, env: EnvOverrides) -> Result { - let file = if path.exists() { - MaestroConfigFile::load(path)? - } else { - MaestroConfigFile::default() - }; + let file = MaestroConfigFile::load(path)?; resolve_with(file.web, env) }