konservejo: provide better feedback for config validation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I1c4e94780890b1ea440ee5b72959d0d96a6a6964
This commit is contained in:
raf 2026-03-17 16:26:28 +03:00
commit 687a935064
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -64,11 +64,101 @@ async fn main() -> Result<()> {
}
},
#[allow(clippy::print_stdout)]
Commands::ValidateConfig => {
info!("Validating configuration from {}", cli.config);
config::Config::from_file(&cli.config)
use std::io::Write;
println!("Validating configuration from: {}", cli.config);
let config = config::Config::from_file(&cli.config)
.map_err(|e| eyre!("Configuration is invalid: {}", e))?;
info!("Configuration is valid");
println!("\n[OK] Configuration structure is valid\n");
println!("Service: {}", config.service.name);
println!(" Concurrency limit: {}", config.service.concurrency_limit);
println!(" State DB: {}", config.service.state_db_path);
println!(" Temp dir: {}", config.service.temp_dir);
println!(
" Retry: {} attempts, {}ms initial backoff",
config.service.retry.max_retries,
config.service.retry.initial_backoff_ms
);
println!("\nSources ({}):", config.sources.len());
for (i, source) in config.sources.iter().enumerate() {
match source {
config::SourceConfig::Forgejo(cfg) => {
println!(" [{}] {} -> {}", i + 1, cfg.id, cfg.api_url);
if !cfg.scope.organizations.is_empty() {
println!(
" Organizations: {}",
cfg.scope.organizations.join(", ")
);
}
if !cfg.scope.exclude_repos.is_empty() {
println!(
" Excluded: {}",
cfg.scope.exclude_repos.join(", ")
);
}
},
}
}
println!("\nSinks ({}):", config.sinks.len());
for (i, sink) in config.sinks.iter().enumerate() {
match sink {
config::SinkConfig::Filesystem(cfg) => {
println!(" [{}] {} -> {}", i + 1, cfg.id, cfg.path);
println!(" Verify on write: {}", cfg.verify_on_write);
if let Some(days) = cfg.retention_days {
println!(" Retention: {days} days");
}
},
config::SinkConfig::S3(_) => {
println!(" [{}] S3 (NOT IMPLEMENTED - will error)", i + 1);
},
}
}
print!("\nChecking paths... ");
std::io::stdout()
.flush()
.map_err(|e| eyre!("Failed to flush stdout: {e}"))?;
let state_dir = std::path::Path::new(&config.service.state_db_path)
.parent()
.ok_or_else(|| eyre!("Invalid state_db_path (no parent directory)"))?;
if !state_dir.exists() {
bail!(
"State directory does not exist: {}. Create it first.",
state_dir.display()
);
}
let temp_dir = std::path::Path::new(&config.service.temp_dir);
if !temp_dir.exists() {
bail!(
"Temp directory does not exist: {}. Create it first.",
temp_dir.display()
);
}
for sink in &config.sinks {
if let config::SinkConfig::Filesystem(cfg) = sink {
let path = std::path::Path::new(&cfg.path);
if !path.exists() {
bail!(
"Sink directory does not exist: {}. Create it first.",
path.display()
);
}
}
}
println!("[OK]");
println!("\n[OK] Configuration is valid and paths are accessible");
Ok(())
},