1use thiserror::Error;
12
13#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum SanitizeError {
17 #[error("replacement store capacity exceeded: {current} mappings (limit: {limit})")]
18 CapacityExceeded { current: usize, limit: usize },
19
20 #[error("invalid seed length: expected 32 bytes, got {0}")]
21 InvalidSeedLength(usize),
22
23 #[error("I/O error: {0}")]
24 IoError(#[from] std::io::Error),
25
26 #[error("parse error ({format}): {message}")]
27 ParseError { format: String, message: String },
28
29 #[error("recursion depth exceeded: {0}")]
30 RecursionDepthExceeded(String),
31
32 #[error("input too large: {size} bytes (limit: {limit})")]
33 InputTooLarge { size: usize, limit: usize },
34
35 #[error("pattern compilation error: {0}")]
36 PatternCompileError(String),
37
38 #[error("invalid configuration: {0}")]
39 InvalidConfig(String),
40
41 #[error("secrets: empty password")]
42 SecretsEmptyPassword,
43
44 #[error("secrets: encrypted file too short (corrupt or truncated)")]
45 SecretsTooShort,
46
47 #[error("secrets: not a recognized encrypted secrets file (bad magic or unsupported version)")]
48 SecretsUnrecognizedFormat,
49
50 #[error("secrets: decryption failed — wrong password or corrupted file")]
51 SecretsDecryptFailed,
52
53 #[error("secrets: cipher error: {0}")]
54 SecretsCipherError(String),
55
56 #[error("secrets: {format} error: {message}")]
57 SecretsFormatError { format: String, message: String },
58
59 #[error("secrets: invalid UTF-8: {0}")]
60 SecretsInvalidUtf8(String),
61
62 #[error("secrets: no password provided — file appears encrypted but --encrypted-secrets was not specified")]
63 SecretsPasswordRequired,
64
65 #[error("archive error: {0}")]
66 ArchiveError(String),
67}
68
69pub type Result<T> = std::result::Result<T, SanitizeError>;
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn from_io_error_wraps_message() {
77 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
78 let err = SanitizeError::from(io_err);
79 assert!(matches!(err, SanitizeError::IoError(_)));
80 assert!(err.to_string().contains("file not found"));
81 }
82
83 #[test]
84 fn io_error_exposes_kind() {
85 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
86 if let SanitizeError::IoError(inner) = SanitizeError::from(io_err) {
87 assert_eq!(inner.kind(), std::io::ErrorKind::PermissionDenied);
88 } else {
89 panic!("expected IoError");
90 }
91 }
92
93 #[test]
94 fn display_variants_are_actionable() {
95 assert!(SanitizeError::CapacityExceeded {
96 current: 5,
97 limit: 3
98 }
99 .to_string()
100 .contains('5'));
101 assert!(SanitizeError::InputTooLarge {
102 size: 100,
103 limit: 50
104 }
105 .to_string()
106 .contains("100"));
107 assert!(SanitizeError::RecursionDepthExceeded("too deep".into())
108 .to_string()
109 .contains("too deep"));
110 assert!(SanitizeError::SecretsEmptyPassword
111 .to_string()
112 .contains("empty"));
113 assert!(SanitizeError::SecretsDecryptFailed
114 .to_string()
115 .contains("wrong password"));
116 }
117}