degenbot_cli/render.rs
1//! stdout/stderr rendering of typed console results (ADR-051 D2/Q1).
2//!
3//! Execution returns typed values (cli-core's `CommandReport`) and typed
4//! failures (`CliError`); rendering is the facade's job. Reports render as
5//! human lines on stdout - the same body text the ported click handlers echoed
6//! (`CommandReport::render_lines`). Failures render as one line on stderr and
7//! carry cli-core's single `CliError -> ExitCode` mapping.
8
9use std::io::Write as _;
10
11use degenbot_cli_core::{CliError, CommandOutcome, ExitCode};
12
13/// Render a completed run: report lines to stdout, the typed error to stderr,
14/// and the outcome's exit code.
15#[must_use]
16pub fn outcome(outcome: &CommandOutcome) -> i32 {
17 let mut stdout = std::io::stdout().lock();
18 if let Some(report) = outcome.report() {
19 for line in report.render_lines() {
20 let _ = writeln!(stdout, "{line}");
21 }
22 }
23 let _ = stdout.flush();
24 if let Some(error) = outcome.error() {
25 write_error(error);
26 }
27 outcome.exit_code.code()
28}
29
30/// Render a typed failure that never reached execution (an argv/resolution
31/// refusal) and return its exit code.
32#[must_use]
33pub fn error(error: &CliError) -> i32 {
34 write_error(error);
35 ExitCode::from(error).code()
36}
37
38/// The operator-facing stderr line for `error`.
39///
40/// The fleet boot refusal renders its named fail-fast line; every other arm
41/// renders [`CliError::message`] verbatim. No in-message `[tag]` prefix — the
42/// console area is derived from the closed domain target (ADR-043 §7).
43fn write_error(error: &CliError) {
44 let mut stderr = std::io::stderr().lock();
45 match error {
46 CliError::BootRefused(message) => {
47 let _ = writeln!(stderr, "REFUSED — {message}");
48 }
49 other => {
50 let _ = writeln!(stderr, "{}", other.message());
51 }
52 }
53}