Skip to content

Map wizard errors to exit codes

Match on the three variants, and keep the catch-all

use rtb_tui::{Wizard, WizardError};
use std::process::ExitCode;

async fn run() -> ExitCode {
    match wizard().run().await {
        Ok(config) => {
            write_config(&config);
            ExitCode::SUCCESS
        }
        Err(WizardError::Cancelled) => {
            eprintln!("cancelled — nothing was written");
            ExitCode::from(1)
        }
        Err(WizardError::Interrupted) => ExitCode::from(130),
        Err(err) => {
            eprintln!("{err}");
            ExitCode::from(2)
        }
    }
}

WizardError is #[non_exhaustive], so the final arm is required by the compiler and not just good manners. Make it do something sensible — a new variant in a later release will land there.

Say nothing extra on Ctrl+C

WizardError::Interrupted means the user pressed Ctrl+C. They know what they did, and a shell convention already covers it: exit 128 + SIGINT, which is 130. Printing "interrupted" on top of the ^C the terminal already echoed is noise.

Cancelled is different. That is Esc on the first step, or a step returning StepOutcome::Back from there — a deliberate "no thanks" rather than a signal. A one-line acknowledgement that says whether anything was written is worth printing, because the user cannot otherwise tell.

Keeping the two apart is the whole reason they are separate variants.

Report a step failure with the step name

WizardError::Step { step, message } carries the name the failing step returned from WizardStep::name(), and its Display output already includes both:

wizard step `select-branch` failed: Invalid configuration: no options

That name is the only handle you get on which step broke. If your step names are "step1" and "step2", that is what the error will say, so name them after what they ask for.

The original InquireError is not kept — only its message, already stringified. There is no source() chain to walk and no way to match on the underlying variant from outside. If a step needs to distinguish one inquire failure from another, it has to do that inside prompt before returning.

Do not treat cancellation as a failure to log

A cancelled wizard is a normal outcome. Logging it at error level, or reporting it to telemetry as a crash, produces a dashboard full of users who changed their minds. Both Cancelled and Interrupted belong at info level if they belong anywhere.

Next