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: decryption failed — wrong password or corrupted file")]
48 SecretsDecryptFailed,
49
50 #[error("secrets: cipher error: {0}")]
51 SecretsCipherError(String),
52
53 #[error("secrets: {format} error: {message}")]
54 SecretsFormatError { format: String, message: String },
55
56 #[error("secrets: invalid UTF-8: {0}")]
57 SecretsInvalidUtf8(String),
58
59 #[error("secrets: no password provided — file appears encrypted but --encrypted-secrets was not specified")]
60 SecretsPasswordRequired,
61
62 #[error("archive error: {0}")]
63 ArchiveError(String),
64}
65
66pub type Result<T> = std::result::Result<T, SanitizeError>;
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn from_io_error_wraps_message() {
74 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
75 let err = SanitizeError::from(io_err);
76 assert!(matches!(err, SanitizeError::IoError(_)));
77 assert!(err.to_string().contains("file not found"));
78 }
79
80 #[test]
81 fn io_error_exposes_kind() {
82 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
83 if let SanitizeError::IoError(inner) = SanitizeError::from(io_err) {
84 assert_eq!(inner.kind(), std::io::ErrorKind::PermissionDenied);
85 } else {
86 panic!("expected IoError");
87 }
88 }
89
90 #[test]
91 fn display_variants_are_actionable() {
92 assert!(SanitizeError::CapacityExceeded {
93 current: 5,
94 limit: 3
95 }
96 .to_string()
97 .contains('5'));
98 assert!(SanitizeError::InputTooLarge {
99 size: 100,
100 limit: 50
101 }
102 .to_string()
103 .contains("100"));
104 assert!(SanitizeError::RecursionDepthExceeded("too deep".into())
105 .to_string()
106 .contains("too deep"));
107 assert!(SanitizeError::SecretsEmptyPassword
108 .to_string()
109 .contains("empty"));
110 assert!(SanitizeError::SecretsDecryptFailed
111 .to_string()
112 .contains("wrong password"));
113 }
114}