Skip to main content

sanitize_engine/
error.rs

1//! Unified error types for the sanitization engine.
2//!
3//! All fallible operations in the crate return [`Result<T>`], which is an
4//! alias for `std::result::Result<T, SanitizeError>`.
5//!
6//! Errors are categorised by subsystem (`IoError`, `SecretsError`,
7//! `ArchiveError`, …) so callers can match on the variant to decide
8//! whether to retry, skip, or abort. The [`thiserror`] derive keeps
9//! display messages actionable and grep-friendly.
10
11use thiserror::Error;
12
13/// All errors that can occur within the sanitization engine.
14#[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}