use std::fmt;
pub const OK: u8 = 0;
pub const ERROR: u8 = 1;
pub const USAGE: u8 = 2;
pub const BUSY: u8 = 3;
pub const UNAVAILABLE: u8 = 4;
pub const INTERRUPTED: u8 = 130;
#[derive(Debug)]
pub struct CliExit {
pub code: u8,
pub message: String,
}
impl CliExit {
pub fn with_code(code: u8, message: impl Into<String>) -> anyhow::Error {
anyhow::Error::new(Self {
code,
message: message.into(),
})
}
pub fn usage(message: impl Into<String>) -> anyhow::Error {
Self::with_code(USAGE, message)
}
pub fn busy(message: impl Into<String>) -> anyhow::Error {
Self::with_code(BUSY, message)
}
pub fn unavailable(message: impl Into<String>) -> anyhow::Error {
Self::with_code(UNAVAILABLE, message)
}
pub fn interrupted(message: impl Into<String>) -> anyhow::Error {
Self::with_code(INTERRUPTED, message)
}
}
impl fmt::Display for CliExit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for CliExit {}
pub fn exit_code_for(error: &anyhow::Error) -> u8 {
for cause in error.chain() {
if let Some(exit) = cause.downcast_ref::<CliExit>() {
return exit.code;
}
if let Some(store) = cause.downcast_ref::<crate::store::StoreError>()
&& store.is_lock_contention()
{
return BUSY;
}
}
ERROR
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_find_the_code_through_context_layers() {
let error = CliExit::busy("held").context("open store").context("scan");
assert_eq!(exit_code_for(&error), BUSY);
}
#[test]
fn should_default_plain_errors_to_one() {
assert_eq!(exit_code_for(&anyhow::anyhow!("boom")), ERROR);
}
#[test]
fn should_map_each_named_constructor_to_its_code() {
assert_eq!(exit_code_for(&CliExit::usage("u")), USAGE);
assert_eq!(exit_code_for(&CliExit::unavailable("d")), UNAVAILABLE);
assert_eq!(exit_code_for(&CliExit::interrupted("i")), INTERRUPTED);
}
}