1use std::path::PathBuf;
4use thiserror::Error;
5
6use super::context::{IoOperation, ParseFormat};
7
8#[derive(Error, Debug)]
10pub enum CcAuditError {
11 #[error("Failed to {operation} {path}: {source}")]
13 Io {
14 path: PathBuf,
15 operation: IoOperation,
16 #[source]
17 source: std::io::Error,
18 },
19
20 #[error("Failed to parse {format} in {path}")]
22 Parse {
23 path: PathBuf,
24 format: ParseFormat,
25 #[source]
26 source: Box<dyn std::error::Error + Send + Sync>,
27 },
28
29 #[error("File not found: {0}")]
31 FileNotFound(PathBuf),
32
33 #[error("Path is not a directory: {0}")]
35 NotADirectory(PathBuf),
36
37 #[error("Path is not a file: {0}")]
39 NotAFile(PathBuf),
40
41 #[error("Invalid format in {path}: {message}")]
43 InvalidFormat { path: PathBuf, message: String },
44
45 #[error("Regex error: {0}")]
47 Regex(#[from] regex::Error),
48
49 #[error("Hook error: {0}")]
51 Hook(#[from] crate::hooks::HookError),
52
53 #[error("Malware database error: {0}")]
55 MalwareDb(#[from] crate::malware_db::MalwareDbError),
56
57 #[error("Watch error: {0}")]
59 Watch(#[from] notify::Error),
60
61 #[error("Configuration error: {0}")]
63 Config(String),
64
65 #[error("YAML parse error in {path}: {source}")]
67 YamlParse {
68 path: String,
69 #[source]
70 source: serde_yaml::Error,
71 },
72
73 #[error("Invalid SKILL.md format: {0}")]
75 InvalidSkillFormat(String),
76
77 #[error("JSON error: {0}")]
79 Json(#[from] serde_json::Error),
80}
81
82impl CcAuditError {
83 pub fn read_error(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
85 Self::Io {
86 path: path.into(),
87 operation: IoOperation::Read,
88 source,
89 }
90 }
91
92 pub fn write_error(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
94 Self::Io {
95 path: path.into(),
96 operation: IoOperation::Write,
97 source,
98 }
99 }
100
101 pub fn json_parse_error(path: impl Into<PathBuf>, source: serde_json::Error) -> Self {
103 Self::Parse {
104 path: path.into(),
105 format: ParseFormat::Json,
106 source: Box::new(source),
107 }
108 }
109
110 pub fn yaml_parse_error(path: impl Into<PathBuf>, source: serde_yaml::Error) -> Self {
112 Self::Parse {
113 path: path.into(),
114 format: ParseFormat::Yaml,
115 source: Box::new(source),
116 }
117 }
118
119 pub fn toml_parse_error(path: impl Into<PathBuf>, source: toml::de::Error) -> Self {
121 Self::Parse {
122 path: path.into(),
123 format: ParseFormat::Toml,
124 source: Box::new(source),
125 }
126 }
127
128 pub fn root_cause(&self) -> &dyn std::error::Error {
130 let mut current: &dyn std::error::Error = self;
131 while let Some(source) = current.source() {
132 current = source;
133 }
134 current
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use std::io;
142
143 #[test]
144 fn test_read_error() {
145 let err = CcAuditError::read_error(
146 "/path/to/file",
147 io::Error::new(io::ErrorKind::NotFound, "not found"),
148 );
149 assert!(err.to_string().contains("/path/to/file"));
150 assert!(err.to_string().contains("read"));
151 }
152
153 #[test]
154 fn test_write_error() {
155 let err = CcAuditError::write_error(
156 "/path/to/file",
157 io::Error::new(io::ErrorKind::PermissionDenied, "denied"),
158 );
159 assert!(err.to_string().contains("/path/to/file"));
160 assert!(err.to_string().contains("write"));
161 }
162
163 #[test]
164 fn test_file_not_found() {
165 let err = CcAuditError::FileNotFound(PathBuf::from("/missing/file"));
166 assert!(err.to_string().contains("/missing/file"));
167 }
168
169 #[test]
170 fn test_root_cause() {
171 let io_err = io::Error::new(io::ErrorKind::NotFound, "root cause");
172 let err = CcAuditError::read_error("/path", io_err);
173 let root = err.root_cause();
174 assert!(root.to_string().contains("root cause"));
175 }
176}