Skip to main content

cc_audit/scanner/
error.rs

1//! Scanner-specific error types.
2
3use std::path::PathBuf;
4use thiserror::Error;
5
6/// Error type for scanner operations.
7#[derive(Error, Debug)]
8pub enum ScanError {
9    #[error("File not found: {0}")]
10    FileNotFound(PathBuf),
11
12    #[error("Path is not a file: {0}")]
13    NotAFile(PathBuf),
14
15    #[error("Path is not a directory: {0}")]
16    NotADirectory(PathBuf),
17
18    #[error("Failed to read file: {path}")]
19    ReadError {
20        path: PathBuf,
21        #[source]
22        source: std::io::Error,
23    },
24
25    #[error("Failed to parse YAML in {path}")]
26    YamlParseError {
27        path: PathBuf,
28        #[source]
29        source: serde_yaml::Error,
30    },
31
32    #[error("Failed to parse JSON in {path}")]
33    JsonParseError {
34        path: PathBuf,
35        #[source]
36        source: serde_json::Error,
37    },
38
39    #[error("Invalid format in {path}: {message}")]
40    InvalidFormat { path: PathBuf, message: String },
41
42    #[error("Regex compilation error: {0}")]
43    RegexError(#[from] regex::Error),
44}
45
46impl ScanError {
47    /// Create a FileNotFound error from a path-like type.
48    pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
49        Self::FileNotFound(path.into())
50    }
51
52    /// Create a NotADirectory error from a path-like type.
53    pub fn not_a_directory(path: impl Into<PathBuf>) -> Self {
54        Self::NotADirectory(path.into())
55    }
56
57    /// Create a ReadError from a path and IO error.
58    pub fn read_error(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
59        Self::ReadError {
60            path: path.into(),
61            source,
62        }
63    }
64
65    /// Create a YamlParseError from a path and YAML error.
66    pub fn yaml_error(path: impl Into<PathBuf>, source: serde_yaml::Error) -> Self {
67        Self::YamlParseError {
68            path: path.into(),
69            source,
70        }
71    }
72
73    /// Create a JsonParseError from a path and JSON error.
74    pub fn json_error(path: impl Into<PathBuf>, source: serde_json::Error) -> Self {
75        Self::JsonParseError {
76            path: path.into(),
77            source,
78        }
79    }
80
81    /// Create an InvalidFormat error.
82    pub fn invalid_format(path: impl Into<PathBuf>, message: impl Into<String>) -> Self {
83        Self::InvalidFormat {
84            path: path.into(),
85            message: message.into(),
86        }
87    }
88}
89
90/// Result type alias for scanner operations.
91pub type ScanResult<T> = std::result::Result<T, ScanError>;
92
93/// Convert from ScanError to the legacy AuditError for backwards compatibility.
94impl From<ScanError> for crate::error::AuditError {
95    fn from(err: ScanError) -> Self {
96        match err {
97            ScanError::FileNotFound(path) => {
98                crate::error::AuditError::FileNotFound(path.display().to_string())
99            }
100            ScanError::NotAFile(path) => {
101                crate::error::AuditError::NotADirectory(path.display().to_string())
102            }
103            ScanError::NotADirectory(path) => {
104                crate::error::AuditError::NotADirectory(path.display().to_string())
105            }
106            ScanError::ReadError { path, source } => crate::error::AuditError::ReadError {
107                path: path.display().to_string(),
108                source,
109            },
110            ScanError::YamlParseError { path, source } => {
111                crate::error::AuditError::YamlParseError {
112                    path: path.display().to_string(),
113                    source,
114                }
115            }
116            ScanError::JsonParseError { path, .. } => crate::error::AuditError::ParseError {
117                path: path.display().to_string(),
118                message: "JSON parse error".to_string(),
119            },
120            ScanError::InvalidFormat { path, message } => crate::error::AuditError::ParseError {
121                path: path.display().to_string(),
122                message,
123            },
124            ScanError::RegexError(e) => crate::error::AuditError::RegexError(e),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use std::path::Path;
133
134    #[test]
135    fn test_file_not_found() {
136        let err = ScanError::file_not_found("/path/to/file");
137        assert!(err.to_string().contains("/path/to/file"));
138    }
139
140    #[test]
141    fn test_not_a_directory() {
142        let err = ScanError::not_a_directory(Path::new("/path/to/file"));
143        assert!(err.to_string().contains("/path/to/file"));
144    }
145
146    #[test]
147    fn test_read_error() {
148        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
149        let err = ScanError::read_error("/path/to/file", io_err);
150        assert!(err.to_string().contains("/path/to/file"));
151    }
152
153    #[test]
154    fn test_invalid_format() {
155        let err = ScanError::invalid_format("/path/to/file", "missing field");
156        assert!(err.to_string().contains("/path/to/file"));
157        assert!(err.to_string().contains("missing field"));
158    }
159
160    #[test]
161    fn test_conversion_to_audit_error() {
162        let scan_err = ScanError::file_not_found("/test/path");
163        let audit_err: crate::error::AuditError = scan_err.into();
164        assert!(audit_err.to_string().contains("/test/path"));
165    }
166}