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) <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-08-22 10:49:39 +01:00
co-authored by Claude Opus 5
parent bf7def5cf1
commit 8ff5046ff3
5 changed files with 9 additions and 42 deletions
+1 -1
View File
@@ -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<Self, ConfigError> {
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,
+5 -31
View File
@@ -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<Self, ConfigError> {
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<Self, ConfigError> {
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());
}
+1 -1
View File
@@ -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<DaemonConfig, ConfigError> {
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,
+1 -4
View File
@@ -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(),
};
+1 -5
View File
@@ -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<WebConfig, WebConfigError> {
let file = if path.exists() {
MaestroConfigFile::load(path)?
} else {
MaestroConfigFile::default()
};
let file = MaestroConfigFile::load(path)?;
resolve_with(file.web, env)
}