adaptive_pipeline_bootstrap/
exit_code.rs1use std::fmt;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47#[repr(i32)]
48pub enum ExitCode {
49 #[default]
51 Success = 0,
52
53 Error = 1,
55
56 UsageError = 64,
61
62 DataError = 65,
67
68 NoInput = 66,
73
74 NoUser = 67,
78
79 NoHost = 68,
83
84 Unavailable = 69,
89
90 Software = 70,
95
96 OsError = 71,
101
102 OsFile = 72,
106
107 CantCreate = 73,
112
113 IoError = 74,
118
119 TempFail = 75,
123
124 Protocol = 76,
129
130 NoPerm = 77,
135
136 Config = 78,
141
142 Interrupted = 130,
146
147 Terminated = 143,
151}
152
153impl ExitCode {
154 pub fn as_i32(self) -> i32 {
156 self as i32
157 }
158
159 pub fn from_error(error: &dyn std::error::Error) -> Self {
169 let error_string = error.to_string().to_lowercase();
170
171 if error_string.contains("permission") || error_string.contains("access denied") {
173 ExitCode::NoPerm
174 } else if error_string.contains("not found") || error_string.contains("no such") {
175 ExitCode::NoInput
176 } else if error_string.contains("invalid") || error_string.contains("argument") {
177 ExitCode::UsageError
178 } else if error_string.contains("parse") || error_string.contains("format") {
179 ExitCode::DataError
180 } else if error_string.contains("io") || error_string.contains("read") || error_string.contains("write") {
181 ExitCode::IoError
182 } else if error_string.contains("config") {
183 ExitCode::Config
184 } else if error_string.contains("unavailable") || error_string.contains("not available") {
185 ExitCode::Unavailable
186 } else {
187 ExitCode::Error
188 }
189 }
190
191 pub fn description(self) -> &'static str {
193 match self {
194 ExitCode::Success => "Success",
195 ExitCode::Error => "General error",
196 ExitCode::UsageError => "Command line usage error",
197 ExitCode::DataError => "Data format error",
198 ExitCode::NoInput => "Cannot open input",
199 ExitCode::NoUser => "User does not exist",
200 ExitCode::NoHost => "Host name unknown",
201 ExitCode::Unavailable => "Service unavailable",
202 ExitCode::Software => "Internal software error",
203 ExitCode::OsError => "System error",
204 ExitCode::OsFile => "Critical OS file missing",
205 ExitCode::CantCreate => "Cannot create output file",
206 ExitCode::IoError => "I/O error",
207 ExitCode::TempFail => "Temporary failure, retry",
208 ExitCode::Protocol => "Remote error in protocol",
209 ExitCode::NoPerm => "Permission denied",
210 ExitCode::Config => "Configuration error",
211 ExitCode::Interrupted => "Interrupted by signal (SIGINT)",
212 ExitCode::Terminated => "Terminated by signal (SIGTERM)",
213 }
214 }
215
216 pub fn is_success(self) -> bool {
218 matches!(self, ExitCode::Success)
219 }
220
221 pub fn is_error(self) -> bool {
223 !self.is_success()
224 }
225
226 pub fn is_signal(self) -> bool {
228 matches!(self, ExitCode::Interrupted | ExitCode::Terminated)
229 }
230}
231
232impl fmt::Display for ExitCode {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(f, "{} ({})", self.description(), self.as_i32())
235 }
236}
237
238impl From<ExitCode> for i32 {
239 fn from(code: ExitCode) -> i32 {
240 code.as_i32()
241 }
242}
243
244impl From<ExitCode> for std::process::ExitCode {
245 fn from(code: ExitCode) -> std::process::ExitCode {
246 std::process::ExitCode::from(code.as_i32() as u8)
247 }
248}
249
250pub fn map_error_to_exit_code(error_message: &str) -> ExitCode {
280 if error_message.contains("Failed to initialize") {
281 ExitCode::Software } else if error_message.contains("not found") || error_message.contains("does not exist") {
283 ExitCode::NoInput } else if error_message.contains("invalid") || error_message.contains("Invalid") {
285 ExitCode::DataError } else if error_message.contains("I/O")
287 || error_message.contains("Failed to read")
288 || error_message.contains("Failed to write")
289 {
290 ExitCode::IoError } else {
292 ExitCode::Error }
294}
295
296pub fn result_to_exit_code<E: std::fmt::Display>(result: Result<(), E>) -> std::process::ExitCode {
321 match result {
322 Ok(()) => std::process::ExitCode::SUCCESS,
323 Err(e) => {
324 let error_message = e.to_string();
325 let code = map_error_to_exit_code(&error_message);
326 code.into()
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_exit_code_values() {
337 assert_eq!(ExitCode::Success.as_i32(), 0);
338 assert_eq!(ExitCode::Error.as_i32(), 1);
339 assert_eq!(ExitCode::UsageError.as_i32(), 64);
340 assert_eq!(ExitCode::Config.as_i32(), 78);
341 assert_eq!(ExitCode::Interrupted.as_i32(), 130);
342 assert_eq!(ExitCode::Terminated.as_i32(), 143);
343 }
344
345 #[test]
346 fn test_is_success() {
347 assert!(ExitCode::Success.is_success());
348 assert!(!ExitCode::Error.is_success());
349 assert!(!ExitCode::UsageError.is_success());
350 }
351
352 #[test]
353 fn test_is_error() {
354 assert!(!ExitCode::Success.is_error());
355 assert!(ExitCode::Error.is_error());
356 assert!(ExitCode::Config.is_error());
357 }
358
359 #[test]
360 fn test_is_signal() {
361 assert!(ExitCode::Interrupted.is_signal());
362 assert!(ExitCode::Terminated.is_signal());
363 assert!(!ExitCode::Success.is_signal());
364 assert!(!ExitCode::Error.is_signal());
365 }
366
367 #[test]
368 fn test_default() {
369 assert_eq!(ExitCode::default(), ExitCode::Success);
370 }
371
372 #[test]
373 fn test_display() {
374 let code = ExitCode::UsageError;
375 let display = format!("{}", code);
376 assert!(display.contains("Command line usage error"));
377 assert!(display.contains("64"));
378 }
379
380 #[test]
381 fn test_from_error() {
382 use std::io;
383
384 let err = io::Error::new(io::ErrorKind::PermissionDenied, "permission denied");
386 assert_eq!(ExitCode::from_error(&err), ExitCode::NoPerm);
387
388 let err = io::Error::new(io::ErrorKind::NotFound, "file not found");
390 assert_eq!(ExitCode::from_error(&err), ExitCode::NoInput);
391 }
392
393 #[test]
394 fn test_conversion_to_i32() {
395 let code: i32 = ExitCode::Config.into();
396 assert_eq!(code, 78);
397 }
398
399 #[test]
401 fn test_map_error_initialization_error() {
402 assert_eq!(
403 map_error_to_exit_code("Failed to initialize resource manager").as_i32(),
404 70
405 );
406 assert_eq!(
407 map_error_to_exit_code("Error: Failed to initialize database connection").as_i32(),
408 70
409 );
410 }
411
412 #[test]
413 fn test_map_error_file_not_found() {
414 assert_eq!(map_error_to_exit_code("File not found: input.txt").as_i32(), 66);
415 assert_eq!(map_error_to_exit_code("The file does not exist").as_i32(), 66);
416 }
417
418 #[test]
419 fn test_map_error_invalid_data() {
420 assert_eq!(map_error_to_exit_code("invalid chunk size specified").as_i32(), 65);
421 assert_eq!(map_error_to_exit_code("Invalid pipeline configuration").as_i32(), 65);
422 }
423
424 #[test]
425 fn test_map_error_io_error() {
426 assert_eq!(map_error_to_exit_code("I/O error occurred").as_i32(), 74);
427 assert_eq!(map_error_to_exit_code("Failed to read from disk").as_i32(), 74);
428 assert_eq!(map_error_to_exit_code("Failed to write to output file").as_i32(), 74);
429 }
430
431 #[test]
432 fn test_map_error_general_error() {
433 assert_eq!(map_error_to_exit_code("Unknown error occurred").as_i32(), 1);
434 assert_eq!(map_error_to_exit_code("Something went wrong").as_i32(), 1);
435 }
436
437 #[test]
438 fn test_map_error_case_sensitivity() {
439 assert_eq!(map_error_to_exit_code("Invalid input provided").as_i32(), 65);
441 assert_eq!(map_error_to_exit_code("invalid input provided").as_i32(), 65);
442 }
443
444 #[test]
445 fn test_map_error_priority() {
446 assert_eq!(
450 map_error_to_exit_code("Failed to initialize with invalid data").as_i32(),
451 70 );
453 }
454
455 #[test]
456 fn test_map_error_exact_messages() {
457 assert_eq!(map_error_to_exit_code("Pipeline 'test' not found").as_i32(), 66);
459 assert_eq!(map_error_to_exit_code("I/O error: permission denied").as_i32(), 74);
460 assert_eq!(map_error_to_exit_code("Invalid pipeline name").as_i32(), 65);
461 }
462
463 #[test]
464 fn test_result_to_exit_code() {
465 let result: Result<(), String> = Ok(());
467 let exit_code = result_to_exit_code(result);
468 assert_eq!(exit_code, std::process::ExitCode::SUCCESS);
469
470 let result: Result<(), String> = Err("File not found".to_string());
472 let exit_code = result_to_exit_code(result);
473 let expected: std::process::ExitCode = ExitCode::NoInput.into();
475 assert_eq!(format!("{:?}", exit_code), format!("{:?}", expected));
476 }
477}