Skip to main content

murk_cli/
error.rs

1//! Unified error type for the murk library.
2
3use crate::crypto::CryptoError;
4use crate::github::GitHubError;
5use crate::vault::VaultError;
6
7/// Top-level error type for murk operations.
8#[derive(Debug)]
9pub enum MurkError {
10    /// Vault file I/O or parsing.
11    Vault(VaultError),
12    /// Cryptographic operation (encrypt/decrypt/key parse).
13    Crypto(CryptoError),
14    /// Integrity check failed (MAC mismatch, tampering).
15    Integrity(String),
16    /// Key resolution or environment configuration.
17    Key(String),
18    /// Recipient management (authorize, revoke).
19    Recipient(String),
20    /// Secret management (add, remove, describe).
21    Secret(String),
22    /// Recipient group management (create, add/remove member, assign).
23    Group(String),
24    /// Agent grant management (grant, revoke, list).
25    Grant(String),
26    /// Agent access policy violation or management error.
27    Policy(String),
28    /// GitHub key fetch.
29    GitHub(GitHubError),
30    /// General I/O.
31    Io(std::io::Error),
32}
33
34impl std::fmt::Display for MurkError {
35    #[allow(clippy::match_same_arms)]
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            MurkError::Vault(e) => write!(f, "{e}"),
39            MurkError::Crypto(e) => write!(f, "{e}"),
40            MurkError::Integrity(msg) => write!(f, "integrity check failed: {msg}"),
41            MurkError::Key(msg) => write!(f, "{msg}"),
42            MurkError::Recipient(msg) => write!(f, "{msg}"),
43            MurkError::Secret(msg) => write!(f, "{msg}"),
44            MurkError::Group(msg) => write!(f, "{msg}"),
45            MurkError::Grant(msg) => write!(f, "{msg}"),
46            MurkError::Policy(msg) => write!(f, "{msg}"),
47            MurkError::GitHub(e) => write!(f, "{e}"),
48            MurkError::Io(e) => write!(f, "I/O error: {e}"),
49        }
50    }
51}
52
53impl std::error::Error for MurkError {
54    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55        match self {
56            MurkError::Io(e) => Some(e),
57            _ => None,
58        }
59    }
60}
61
62impl From<VaultError> for MurkError {
63    fn from(e: VaultError) -> Self {
64        MurkError::Vault(e)
65    }
66}
67
68impl From<CryptoError> for MurkError {
69    fn from(e: CryptoError) -> Self {
70        MurkError::Crypto(e)
71    }
72}
73
74impl From<GitHubError> for MurkError {
75    fn from(e: GitHubError) -> Self {
76        MurkError::GitHub(e)
77    }
78}
79
80impl From<std::io::Error> for MurkError {
81    fn from(e: std::io::Error) -> Self {
82        MurkError::Io(e)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn display_integrity() {
92        let e = MurkError::Integrity("mac mismatch".into());
93        assert_eq!(e.to_string(), "integrity check failed: mac mismatch");
94    }
95
96    #[test]
97    fn display_key() {
98        let e = MurkError::Key("MURK_KEY not set".into());
99        assert_eq!(e.to_string(), "MURK_KEY not set");
100    }
101
102    #[test]
103    fn display_recipient() {
104        let e = MurkError::Recipient("not found".into());
105        assert_eq!(e.to_string(), "not found");
106    }
107
108    #[test]
109    fn display_secret() {
110        let e = MurkError::Secret("invalid".into());
111        assert_eq!(e.to_string(), "invalid");
112    }
113
114    #[test]
115    fn display_io() {
116        let e = MurkError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "gone"));
117        assert!(e.to_string().contains("I/O error"));
118    }
119
120    #[test]
121    fn from_vault_error() {
122        let ve = VaultError::Parse("bad json".into());
123        let e: MurkError = ve.into();
124        assert!(e.to_string().contains("bad json"));
125    }
126
127    #[test]
128    fn from_crypto_error() {
129        let ce = CryptoError::Decrypt("failed".into());
130        let e: MurkError = ce.into();
131        assert!(e.to_string().contains("failed"));
132    }
133
134    #[test]
135    fn from_io_error() {
136        let io = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
137        let e: MurkError = io.into();
138        assert!(e.to_string().contains("denied"));
139    }
140
141    #[test]
142    fn error_source_io() {
143        let e = MurkError::Io(std::io::Error::other("test"));
144        assert!(std::error::Error::source(&e).is_some());
145    }
146
147    #[test]
148    fn error_source_non_io() {
149        let e = MurkError::Key("test".into());
150        assert!(std::error::Error::source(&e).is_none());
151    }
152}