Skip to main content

cc_audit/error/
mod.rs

1//! Error types for cc-audit.
2//!
3//! This module provides a unified error handling system with:
4//! - `CcAuditError`: The new unified error type with full context preservation
5//! - `AuditError`: Legacy error type for backwards compatibility
6//! - Context types for better error messages
7
8mod audit;
9mod context;
10
11pub use audit::CcAuditError;
12pub use context::{IoOperation, ParseFormat};
13
14use crate::hooks::HookError;
15use crate::malware_db::MalwareDbError;
16use thiserror::Error;
17
18/// Legacy error type for backwards compatibility.
19///
20/// New code should prefer using `CcAuditError` for better error context.
21#[derive(Error, Debug)]
22pub enum AuditError {
23    #[error("File not found: {0}")]
24    FileNotFound(String),
25
26    #[error("Failed to read file: {path}")]
27    ReadError {
28        path: String,
29        #[source]
30        source: std::io::Error,
31    },
32
33    #[error("Failed to parse YAML frontmatter: {path}")]
34    YamlParseError {
35        path: String,
36        #[source]
37        source: serde_yaml::Error,
38    },
39
40    #[error("Invalid SKILL.md format: {0}")]
41    InvalidSkillFormat(String),
42
43    #[error("Regex compilation error: {0}")]
44    RegexError(#[from] regex::Error),
45
46    #[error("Path is not a directory: {0}")]
47    NotADirectory(String),
48
49    #[error("JSON serialization error: {0}")]
50    JsonError(#[from] serde_json::Error),
51
52    #[error("Failed to parse file: {path} - {message}")]
53    ParseError { path: String, message: String },
54
55    #[error("Hook operation failed: {0}")]
56    Hook(#[from] HookError),
57
58    #[error("Malware database error: {0}")]
59    MalwareDb(#[from] MalwareDbError),
60
61    #[error("File watch error: {0}")]
62    Watch(#[from] notify::Error),
63
64    #[error("Configuration error: {0}")]
65    Config(String),
66}
67
68/// Result type alias for operations using the legacy AuditError.
69pub type Result<T> = std::result::Result<T, AuditError>;
70
71/// Result type alias for operations using the new CcAuditError.
72pub type CcResult<T> = std::result::Result<T, CcAuditError>;
73
74/// Convert from CcAuditError to AuditError for backwards compatibility.
75impl From<CcAuditError> for AuditError {
76    fn from(err: CcAuditError) -> Self {
77        match err {
78            CcAuditError::Io { path, source, .. } => AuditError::ReadError {
79                path: path.display().to_string(),
80                source,
81            },
82            CcAuditError::Parse { path, .. } => AuditError::ParseError {
83                path: path.display().to_string(),
84                message: "parse error".to_string(),
85            },
86            CcAuditError::FileNotFound(path) => {
87                AuditError::FileNotFound(path.display().to_string())
88            }
89            CcAuditError::NotADirectory(path) => {
90                AuditError::NotADirectory(path.display().to_string())
91            }
92            CcAuditError::NotAFile(path) => AuditError::NotADirectory(path.display().to_string()),
93            CcAuditError::InvalidFormat { path, message } => AuditError::ParseError {
94                path: path.display().to_string(),
95                message,
96            },
97            CcAuditError::Regex(e) => AuditError::RegexError(e),
98            CcAuditError::Hook(e) => AuditError::Hook(e),
99            CcAuditError::MalwareDb(e) => AuditError::MalwareDb(e),
100            CcAuditError::Watch(e) => AuditError::Watch(e),
101            CcAuditError::Config(s) => AuditError::Config(s),
102            CcAuditError::YamlParse { path, source } => AuditError::YamlParseError { path, source },
103            CcAuditError::InvalidSkillFormat(s) => AuditError::InvalidSkillFormat(s),
104            CcAuditError::Json(e) => AuditError::JsonError(e),
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_error_display_file_not_found() {
115        let err = AuditError::FileNotFound("/path/to/file".to_string());
116        assert_eq!(err.to_string(), "File not found: /path/to/file");
117    }
118
119    #[test]
120    fn test_error_display_read_error() {
121        let err = AuditError::ReadError {
122            path: "/path/to/file".to_string(),
123            source: std::io::Error::new(std::io::ErrorKind::NotFound, "not found"),
124        };
125        assert_eq!(err.to_string(), "Failed to read file: /path/to/file");
126    }
127
128    #[test]
129    fn test_error_display_invalid_skill_format() {
130        let err = AuditError::InvalidSkillFormat("missing frontmatter".to_string());
131        assert_eq!(
132            err.to_string(),
133            "Invalid SKILL.md format: missing frontmatter"
134        );
135    }
136
137    #[test]
138    fn test_error_display_not_a_directory() {
139        let err = AuditError::NotADirectory("/path/to/file".to_string());
140        assert_eq!(err.to_string(), "Path is not a directory: /path/to/file");
141    }
142
143    #[test]
144    fn test_error_display_parse_error() {
145        let err = AuditError::ParseError {
146            path: "/path/to/file".to_string(),
147            message: "invalid JSON".to_string(),
148        };
149        assert_eq!(
150            err.to_string(),
151            "Failed to parse file: /path/to/file - invalid JSON"
152        );
153    }
154
155    #[test]
156    fn test_error_from_hook_error() {
157        let hook_error = HookError::NotAGitRepository;
158        let err: AuditError = hook_error.into();
159        assert!(err.to_string().contains("Hook operation failed"));
160    }
161
162    #[test]
163    fn test_error_from_malware_db_error() {
164        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
165        let malware_error = MalwareDbError::ReadFile(io_error);
166        let err: AuditError = malware_error.into();
167        assert!(err.to_string().contains("Malware database error"));
168    }
169
170    #[test]
171    fn test_error_display_config() {
172        let err = AuditError::Config("invalid value".to_string());
173        assert_eq!(err.to_string(), "Configuration error: invalid value");
174    }
175
176    #[test]
177    fn test_cc_audit_error_to_audit_error() {
178        let cc_err = CcAuditError::FileNotFound(std::path::PathBuf::from("/test/path"));
179        let audit_err: AuditError = cc_err.into();
180        assert!(audit_err.to_string().contains("/test/path"));
181    }
182}