pub(crate) const SUCCESS: i32 = 0;
pub(crate) const RUNTIME_ERROR: i32 = 1;
pub(crate) const USAGE_ERROR: i32 = 2;
pub(crate) const HARD_SIGINT: i32 = 130;
#[derive(Debug)]
pub(crate) struct UsageErrorTag;
impl std::fmt::Display for UsageErrorTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("usage error")
}
}
impl std::error::Error for UsageErrorTag {}
pub(crate) fn usage_error(message: impl Into<String>) -> anyhow::Error {
anyhow::Error::new(UsageErrorTag).context(message.into())
}
pub(crate) fn exit_code_for_anyhow(err: &anyhow::Error) -> i32 {
if err
.chain()
.any(<dyn std::error::Error + 'static>::is::<UsageErrorTag>)
{
USAGE_ERROR
} else {
RUNTIME_ERROR
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn usage_error_tag_routes_to_exit_2() {
let err = usage_error("missing --yes for destructive admin command");
assert_eq!(exit_code_for_anyhow(&err), USAGE_ERROR);
}
#[test]
fn untagged_error_routes_to_exit_1() {
let err: anyhow::Error = anyhow::anyhow!("connection refused: tcp transport failed");
assert_eq!(exit_code_for_anyhow(&err), RUNTIME_ERROR);
}
#[test]
fn usage_error_tag_preserved_through_context_chain() {
let err = usage_error("invalid --from value")
.context("parsing --from <VALUE>")
.context("loading listener");
assert_eq!(exit_code_for_anyhow(&err), USAGE_ERROR);
}
}