1#[derive(Debug, thiserror::Error)]
11pub enum IronpostError {
12 #[error("config error: {0}")]
14 Config(#[from] ConfigError),
15
16 #[error("pipeline error: {0}")]
18 Pipeline(#[from] PipelineError),
19
20 #[error("detection error: {0}")]
22 Detection(#[from] DetectionError),
23
24 #[error("parse error: {0}")]
26 Parse(#[from] ParseError),
27
28 #[error("storage error: {0}")]
30 Storage(#[from] StorageError),
31
32 #[error("container error: {0}")]
34 Container(#[from] ContainerError),
35
36 #[error("sbom error: {0}")]
38 Sbom(#[from] SbomError),
39
40 #[error("plugin error: {0}")]
42 Plugin(#[from] PluginError),
43
44 #[error("io error: {0}")]
46 Io(#[from] std::io::Error),
47}
48
49#[derive(Debug, thiserror::Error)]
51pub enum ConfigError {
52 #[error("config file not found: {path}")]
54 FileNotFound {
55 path: String,
57 },
58
59 #[error("failed to parse config: {reason}")]
61 ParseFailed {
62 reason: String,
64 },
65
66 #[error("invalid config value for '{field}': {reason}")]
68 InvalidValue {
69 field: String,
71 reason: String,
73 },
74}
75
76#[derive(Debug, thiserror::Error)]
78pub enum PipelineError {
79 #[error("channel send failed: {0}")]
81 ChannelSend(String),
82
83 #[error("channel receive failed: {0}")]
85 ChannelRecv(String),
86
87 #[error("pipeline init failed: {0}")]
89 InitFailed(String),
90
91 #[error("pipeline already running")]
93 AlreadyRunning,
94
95 #[error("pipeline not running")]
97 NotRunning,
98}
99
100#[derive(Debug, thiserror::Error)]
102pub enum DetectionError {
103 #[error("ebpf load failed: {0}")]
105 EbpfLoad(String),
106
107 #[error("ebpf map error: {0}")]
109 EbpfMap(String),
110
111 #[error("rule error: {0}")]
113 Rule(String),
114}
115
116#[derive(Debug, thiserror::Error)]
118pub enum ParseError {
119 #[error("unsupported format: {0}")]
121 UnsupportedFormat(String),
122
123 #[error("parse failed at offset {offset}: {reason}")]
125 Failed {
126 offset: usize,
128 reason: String,
130 },
131
132 #[error("input too large: {size} bytes (max: {max})")]
134 TooLarge {
135 size: usize,
137 max: usize,
139 },
140}
141
142#[derive(Debug, thiserror::Error)]
144pub enum StorageError {
145 #[error("connection failed: {0}")]
147 Connection(String),
148
149 #[error("query failed: {0}")]
151 Query(String),
152}
153
154#[derive(Debug, thiserror::Error)]
156pub enum ContainerError {
157 #[error("docker api error: {0}")]
159 DockerApi(String),
160
161 #[error("isolation failed for container '{container_id}': {reason}")]
163 IsolationFailed {
164 container_id: String,
166 reason: String,
168 },
169
170 #[error("policy violation: {0}")]
172 PolicyViolation(String),
173
174 #[error("container not found: {0}")]
176 NotFound(String),
177}
178
179#[derive(Debug, thiserror::Error)]
181pub enum SbomError {
182 #[error("scan failed: {0}")]
184 ScanFailed(String),
185
186 #[error("vulnerability database error: {0}")]
188 VulnDb(String),
189
190 #[error("unsupported sbom format: {0}")]
192 UnsupportedFormat(String),
193
194 #[error("sbom parse failed: {0}")]
196 ParseFailed(String),
197}
198
199#[derive(Debug, thiserror::Error)]
201pub enum PluginError {
202 #[error("plugin already registered: {name}")]
204 AlreadyRegistered {
205 name: String,
207 },
208
209 #[error("plugin not found: {name}")]
211 NotFound {
212 name: String,
214 },
215
216 #[error("invalid state for plugin '{name}': current={current}, expected={expected}")]
218 InvalidState {
219 name: String,
221 current: String,
223 expected: String,
225 },
226
227 #[error("errors stopping plugins: {0}")]
229 StopFailed(String),
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn config_error_display() {
238 let err = ConfigError::FileNotFound {
239 path: "/etc/ironpost.toml".to_owned(),
240 };
241 assert_eq!(err.to_string(), "config file not found: /etc/ironpost.toml");
242
243 let err = ConfigError::InvalidValue {
244 field: "log_level".to_owned(),
245 reason: "must be one of: trace, debug, info, warn, error".to_owned(),
246 };
247 assert!(err.to_string().contains("log_level"));
248 }
249
250 #[test]
251 fn pipeline_error_display() {
252 let err = PipelineError::AlreadyRunning;
253 assert_eq!(err.to_string(), "pipeline already running");
254
255 let err = PipelineError::ChannelSend("buffer full".to_owned());
256 assert!(err.to_string().contains("buffer full"));
257 }
258
259 #[test]
260 fn detection_error_display() {
261 let err = DetectionError::EbpfLoad("permission denied".to_owned());
262 assert!(err.to_string().contains("permission denied"));
263 }
264
265 #[test]
266 fn parse_error_display() {
267 let err = ParseError::TooLarge {
268 size: 2048,
269 max: 1024,
270 };
271 assert!(err.to_string().contains("2048"));
272 assert!(err.to_string().contains("1024"));
273 }
274
275 #[test]
276 fn container_error_display() {
277 let err = ContainerError::IsolationFailed {
278 container_id: "abc123".to_owned(),
279 reason: "network disconnect failed".to_owned(),
280 };
281 assert!(err.to_string().contains("abc123"));
282 assert!(err.to_string().contains("network disconnect failed"));
283 }
284
285 #[test]
286 fn sbom_error_display() {
287 let err = SbomError::UnsupportedFormat("unknown-format".to_owned());
288 assert!(err.to_string().contains("unknown-format"));
289 }
290
291 #[test]
292 fn ironpost_error_from_config() {
293 let config_err = ConfigError::FileNotFound {
294 path: "test.toml".to_owned(),
295 };
296 let err: IronpostError = config_err.into();
297 assert!(matches!(err, IronpostError::Config(_)));
298 assert!(err.to_string().contains("test.toml"));
299 }
300
301 #[test]
302 fn ironpost_error_from_container() {
303 let container_err = ContainerError::NotFound("xyz".to_owned());
304 let err: IronpostError = container_err.into();
305 assert!(matches!(err, IronpostError::Container(_)));
306 }
307
308 #[test]
309 fn ironpost_error_from_sbom() {
310 let sbom_err = SbomError::ScanFailed("timeout".to_owned());
311 let err: IronpostError = sbom_err.into();
312 assert!(matches!(err, IronpostError::Sbom(_)));
313 }
314
315 #[test]
316 fn ironpost_error_from_io() {
317 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
318 let err: IronpostError = io_err.into();
319 assert!(matches!(err, IronpostError::Io(_)));
320 }
321}