use super::types::StopReason;
use super::HeadlessError;
const EXIT_OK: i32 = 0;
const EXIT_RUNTIME: i32 = 1;
const EXIT_MISUSE: i32 = 2;
const EXIT_TIER_DENIED: i32 = 3;
pub fn exit_code(
err: Option<&HeadlessError>,
stop_reason: StopReason,
response_empty: bool,
tier_denied: bool,
) -> i32 {
if let Some(e) = err {
return exit_code_for_error(e);
}
if tier_denied && response_empty && stop_reason == StopReason::Denied {
return EXIT_TIER_DENIED;
}
EXIT_OK
}
fn exit_code_for_error(err: &HeadlessError) -> i32 {
match err {
HeadlessError::InputInvalid(_) | HeadlessError::InputTooLarge(_) => EXIT_MISUSE,
HeadlessError::Io(_)
| HeadlessError::Storage(_)
| HeadlessError::Aborted
| HeadlessError::PassphraseUnavailable
| HeadlessError::Db(_) => EXIT_RUNTIME,
}
}
#[cfg(test)]
mod tests {
use super::{exit_code, EXIT_MISUSE, EXIT_OK, EXIT_RUNTIME, EXIT_TIER_DENIED};
use crate::headless::types::StopReason;
use crate::headless::HeadlessError;
use crate::vault::VaultError;
#[test]
fn test_exit_codes_taxonomy() {
assert_eq!(exit_code(None, StopReason::Done, false, false), EXIT_OK);
assert_eq!(
exit_code(
Some(&HeadlessError::InputInvalid("x".into())),
StopReason::Error,
true,
false
),
EXIT_MISUSE
);
assert_eq!(
exit_code(None, StopReason::Denied, true, true),
EXIT_TIER_DENIED
);
assert_eq!(exit_code(None, StopReason::Done, false, true), EXIT_OK);
}
#[test]
fn test_input_too_large_maps_to_misuse() {
let err = HeadlessError::InputTooLarge(10 * 1024 * 1024);
assert_eq!(
exit_code(Some(&err), StopReason::Error, true, false),
EXIT_MISUSE
);
}
#[test]
fn test_passphrase_unavailable_maps_to_runtime_error() {
let err = HeadlessError::PassphraseUnavailable;
assert_eq!(
exit_code(Some(&err), StopReason::Error, true, false),
EXIT_RUNTIME
);
}
#[test]
fn test_db_corrupt_maps_to_runtime_error() {
let err: HeadlessError = VaultError::DbCorrupt {
db_path: std::path::PathBuf::from("/tmp/.magi/.magi-rs-memory.db"),
detail: "data present without envelope".into(),
}
.into();
assert_eq!(
exit_code(Some(&err), StopReason::Error, true, false),
EXIT_RUNTIME
);
}
#[test]
fn test_wrong_passphrase_maps_to_runtime_error() {
let err: HeadlessError = VaultError::WrongPassphrase.into();
assert_eq!(
exit_code(Some(&err), StopReason::Error, true, false),
EXIT_RUNTIME
);
}
#[test]
fn test_io_and_storage_map_to_runtime_error() {
let io = HeadlessError::Io("disk full".into());
let storage = HeadlessError::Storage("locked".into());
assert_eq!(
exit_code(Some(&io), StopReason::Error, true, false),
EXIT_RUNTIME
);
assert_eq!(
exit_code(Some(&storage), StopReason::Error, true, false),
EXIT_RUNTIME
);
}
#[test]
fn test_aborted_maps_to_runtime_error() {
let err = HeadlessError::Aborted;
assert_eq!(
exit_code(Some(&err), StopReason::Error, true, false),
EXIT_RUNTIME
);
}
#[test]
fn test_error_precedence_wins_over_tier_denied_signal() {
let err = HeadlessError::InputInvalid("bad envelope".into());
assert_eq!(
exit_code(Some(&err), StopReason::Denied, true, true),
EXIT_MISUSE
);
}
#[test]
fn test_tier_denied_without_empty_response_is_success() {
assert_eq!(exit_code(None, StopReason::Denied, false, true), EXIT_OK);
}
}