#![allow(dead_code)]
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DoctorExitCode {
Healthy = 0,
FindingsPresent = 1,
FixPartial = 2,
FixFailedRolledBack = 3,
RefusedUnsafe = 4,
ConcurrencyLost = 5,
OnlineRequired = 6,
UsageError = 64,
NoInput = 66,
CannotCreateOutput = 73,
IoError = 74,
}
impl DoctorExitCode {
#[must_use]
pub const fn as_i32(self) -> i32 {
self as i32
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Healthy => "healthy",
Self::FindingsPresent => "findings_present",
Self::FixPartial => "fix_partial",
Self::FixFailedRolledBack => "fix_failed_rolled_back",
Self::RefusedUnsafe => "refused_unsafe",
Self::ConcurrencyLost => "concurrency_lost",
Self::OnlineRequired => "online_required",
Self::UsageError => "usage_error",
Self::NoInput => "no_input",
Self::CannotCreateOutput => "cannot_create_output",
Self::IoError => "io_error",
}
}
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::Healthy => "every check passed",
Self::FindingsPresent => "findings present; --repair not requested",
Self::FixPartial => "--repair ran; some fixers failed",
Self::FixFailedRolledBack => "--repair attempted, rolled back from backup",
Self::RefusedUnsafe => "refused: precondition gate failed",
Self::ConcurrencyLost => "refused: workspace lock unavailable",
Self::OnlineRequired => "refused: --online flag required",
Self::UsageError => "CLI usage error",
Self::NoInput => "required input missing",
Self::CannotCreateOutput => "could not create output artifact",
Self::IoError => "I/O error during non-mutating operation",
}
}
#[must_use]
pub fn all() -> &'static [Self] {
&[
Self::Healthy,
Self::FindingsPresent,
Self::FixPartial,
Self::FixFailedRolledBack,
Self::RefusedUnsafe,
Self::ConcurrencyLost,
Self::OnlineRequired,
Self::UsageError,
Self::NoInput,
Self::CannotCreateOutput,
Self::IoError,
]
}
}
impl From<DoctorExitCode> for i32 {
fn from(code: DoctorExitCode) -> Self {
code.as_i32()
}
}
pub fn exit_with(code: DoctorExitCode) -> ! {
crate::shutdown::exit_process(code.as_i32())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exit_codes_match_contract_values() {
assert_eq!(DoctorExitCode::Healthy.as_i32(), 0);
assert_eq!(DoctorExitCode::FindingsPresent.as_i32(), 1);
assert_eq!(DoctorExitCode::FixPartial.as_i32(), 2);
assert_eq!(DoctorExitCode::FixFailedRolledBack.as_i32(), 3);
assert_eq!(DoctorExitCode::RefusedUnsafe.as_i32(), 4);
assert_eq!(DoctorExitCode::ConcurrencyLost.as_i32(), 5);
assert_eq!(DoctorExitCode::OnlineRequired.as_i32(), 6);
assert_eq!(DoctorExitCode::UsageError.as_i32(), 64);
assert_eq!(DoctorExitCode::NoInput.as_i32(), 66);
assert_eq!(DoctorExitCode::CannotCreateOutput.as_i32(), 73);
assert_eq!(DoctorExitCode::IoError.as_i32(), 74);
}
#[test]
fn as_str_round_trips_via_all() {
for code in DoctorExitCode::all() {
let name = code.as_str();
assert!(!name.is_empty());
assert!(
name.chars().all(|c| c.is_ascii_lowercase() || c == '_'),
"name `{name}` must be snake_case"
);
}
}
#[test]
fn from_doctor_exit_code_into_i32() {
let n: i32 = DoctorExitCode::RefusedUnsafe.into();
assert_eq!(n, 4);
}
#[test]
fn description_is_non_empty_for_every_variant() {
for code in DoctorExitCode::all() {
assert!(!code.description().is_empty());
}
}
}