black_bagg/
error.rs

1use thiserror::Error;
2
3#[derive(Debug, Error)]
4#[allow(dead_code)]
5pub enum Error {
6    #[error(transparent)]
7    Anyhow(#[from] anyhow::Error),
8
9    #[error("I/O error: {0}")]
10    Io(#[from] std::io::Error),
11
12    #[error("serialization error: {0}")]
13    Serde(&'static str),
14
15    #[error("invalid input: {0}")]
16    Invalid(&'static str),
17
18    #[error("{0}")]
19    Message(String),
20
21    #[error(transparent)]
22    Csv(#[from] csv::Error),
23
24    #[error(transparent)]
25    Persist(#[from] tempfile::PersistError),
26
27    #[error(transparent)]
28    Hex(#[from] hex::FromHexError),
29
30    #[error(transparent)]
31    SystemTime(#[from] std::time::SystemTimeError),
32
33    #[error(transparent)]
34    FromUtf8(#[from] std::string::FromUtf8Error),
35}
36
37#[allow(dead_code)]
38pub type Result<T> = std::result::Result<T, Error>;
39
40#[macro_export]
41macro_rules! bail {
42    ($($arg:tt)*) => { return Err($crate::error::Error::Message(format!($($arg)*))) };
43}
44
45/// Classify errors into stable, scriptable exit codes.
46/// Ranges:
47/// 0 OK; 2 input; 3 integrity; 4 unlock; 5 I/O; 6 concurrency; 7 policy; 10 internal
48pub fn exit_code(err: &Error) -> i32 {
49    match err {
50        Error::Io(_) | Error::Csv(_) | Error::Persist(_) | Error::SystemTime(_) => 5,
51        Error::Hex(_) | Error::FromUtf8(_) | Error::Serde(_) | Error::Invalid(_) => 2,
52        Error::Anyhow(e) => classify_message(&format!("{}", e)),
53        Error::Message(msg) => classify_message(msg),
54    }
55}
56
57fn classify_message(msg: &str) -> i32 {
58    let lower = msg.to_ascii_lowercase();
59    if lower.contains("integrity")
60        || lower.contains("tamper")
61        || lower.contains("mismatch")
62        || lower.contains("aead_auth_fail")
63        || lower.contains("header_mac_mismatch")
64    {
65        return 3;
66    }
67    if lower.contains("unlock") || lower.contains("failed to unlock vault") {
68        return 4;
69    }
70    if lower.contains("resource temporarily unavailable") || lower.contains("lock file") || lower.contains("locked") {
71        return 6;
72    }
73    if lower.contains("passphrase_policy_violation")
74        || lower.contains("requires")
75        || lower.contains("unsafe-stdout")
76        || lower.contains("mlock support not compiled")
77        || lower.contains("mlock failed")
78        || lower.contains("refusing")
79        || lower.contains("denied")
80        || lower.contains("tty")
81    {
82        return 7;
83    }
84    if lower.contains("invalid") || lower.contains("too ") || lower.contains("must be") || lower.contains("missing") {
85        return 2;
86    }
87    10
88}