From 52310761f67220c9c075cd847205825a720035ee Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 21 Feb 2026 23:38:32 +0900 Subject: feat(console): add structured error handling, verbosity, and suggestions Implement Phase 7.2 error handling & UX infrastructure: - Add exit_code module with MozartError, bail()/bail_silent() helpers, and Composer-compatible exit code constants (0-5, 100) - Redesign Console struct with Verbosity enum (Quiet/Normal/Verbose/ VeryVerbose/Debug), ANSI auto-detection via IsTerminal, and verbosity-gated output methods (info/verbose/debug/error) - Thread Console through all 33 command execute() signatures - Replace all std::process::exit() calls with structured MozartError returns handled in main() - Migrate eprintln\! status messages to console.info() for quiet-mode suppression - Add suggest module with Levenshtein distance and "Did you mean?" formatting for future package name suggestions Co-Authored-By: Claude Opus 4.6 --- crates/mozart/src/commands/validate.rs | 44 +++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) (limited to 'crates/mozart/src/commands/validate.rs') diff --git a/crates/mozart/src/commands/validate.rs b/crates/mozart/src/commands/validate.rs index 7a01761..1dec3fe 100644 --- a/crates/mozart/src/commands/validate.rs +++ b/crates/mozart/src/commands/validate.rs @@ -67,7 +67,11 @@ impl ValidationResult { // ─── Entry point ───────────────────────────────────────────────────────────── -pub fn execute(args: &ValidateArgs, cli: &super::Cli) -> anyhow::Result<()> { +pub fn execute( + args: &ValidateArgs, + cli: &super::Cli, + console: &crate::console::Console, +) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), None => std::env::current_dir()?, @@ -79,24 +83,28 @@ pub fn execute(args: &ValidateArgs, cli: &super::Cli) -> anyhow::Result<()> { None => working_dir.join("composer.json"), }; + // Validate-specific exit codes (matching Composer's behavior): + // 3 = file not found or not readable + // 2 = JSON parse error + const VALIDATE_FILE_ERROR: i32 = 3; + const VALIDATE_JSON_ERROR: i32 = 2; + // Check file exists if !file.exists() { - eprintln!( - "{}", - crate::console::error(&format!("{} not found.", file.display())) - ); - std::process::exit(3); + return Err(crate::exit_code::bail( + VALIDATE_FILE_ERROR, + format!("{} not found.", file.display()), + )); } // Read file content let content = match std::fs::read_to_string(&file) { Ok(c) => c, Err(_) => { - eprintln!( - "{}", - crate::console::error(&format!("{} is not readable.", file.display())) - ); - std::process::exit(3); + return Err(crate::exit_code::bail( + VALIDATE_FILE_ERROR, + format!("{} is not readable.", file.display()), + )); } }; @@ -104,12 +112,10 @@ pub fn execute(args: &ValidateArgs, cli: &super::Cli) -> anyhow::Result<()> { let json_value: serde_json::Value = match serde_json::from_str(&content) { Ok(v) => v, Err(e) => { - eprintln!( - "{}", - crate::console::error(&format!("{} does not contain valid JSON", file.display())) - ); - eprintln!("{e}"); - std::process::exit(2); + return Err(crate::exit_code::bail( + VALIDATE_JSON_ERROR, + format!("{} does not contain valid JSON: {e}", file.display()), + )); } }; @@ -130,7 +136,7 @@ pub fn execute(args: &ValidateArgs, cli: &super::Cli) -> anyhow::Result<()> { // Stub for --with-dependencies if args.with_dependencies { - eprintln!("The --with-dependencies option is not yet implemented"); + console.info("The --with-dependencies option is not yet implemented"); } let exit_code = compute_exit_code( @@ -141,7 +147,7 @@ pub fn execute(args: &ValidateArgs, cli: &super::Cli) -> anyhow::Result<()> { args.strict, ); if exit_code != 0 { - std::process::exit(exit_code); + return Err(crate::exit_code::bail_silent(exit_code)); } Ok(()) -- cgit v1.3.1