1use serde_json::{json, Value};
2use std::fmt;
3
4pub const EXIT_SUCCESS: i32 = 0;
6pub const EXIT_CHECK_FINDINGS: i32 = 1;
7pub const EXIT_INVALID_ARGS: i32 = 2;
8pub const EXIT_FILE_ERROR: i32 = 3;
9pub const EXIT_PARSE_ERROR: i32 = 4;
10pub const EXIT_TARGET_NOT_FOUND: i32 = 5;
11pub const EXIT_INVALID_QUERY: i32 = 6;
12pub const EXIT_INTERNAL_ERROR: i32 = 7;
13
14#[derive(Debug, Clone)]
16pub enum AppError {
17 InvalidArgs { message: String },
18 FileError { message: String },
19 ParseError { message: String },
20 TargetNotFound { message: String },
21 InvalidQuery { message: String },
22 InternalError { message: String },
23}
24
25impl AppError {
26 pub fn code(&self) -> &'static str {
27 match self {
28 AppError::InvalidArgs { .. } => "invalid_args",
29 AppError::FileError { .. } => "file_error",
30 AppError::ParseError { .. } => "parse_error",
31 AppError::TargetNotFound { .. } => "target_not_found",
32 AppError::InvalidQuery { .. } => "invalid_query",
33 AppError::InternalError { .. } => "internal_error",
34 }
35 }
36
37 pub fn exit_code(&self) -> i32 {
38 match self {
39 AppError::InvalidArgs { .. } => EXIT_INVALID_ARGS,
40 AppError::FileError { .. } => EXIT_FILE_ERROR,
41 AppError::ParseError { .. } => EXIT_PARSE_ERROR,
42 AppError::TargetNotFound { .. } => EXIT_TARGET_NOT_FOUND,
43 AppError::InvalidQuery { .. } => EXIT_INVALID_QUERY,
44 AppError::InternalError { .. } => EXIT_INTERNAL_ERROR,
45 }
46 }
47
48 pub fn message(&self) -> String {
49 match self {
50 AppError::InvalidArgs { message } => message.clone(),
51 AppError::FileError { message } => message.clone(),
52 AppError::ParseError { message } => message.clone(),
53 AppError::TargetNotFound { message } => message.clone(),
54 AppError::InvalidQuery { message } => message.clone(),
55 AppError::InternalError { message } => message.clone(),
56 }
57 }
58
59 pub fn to_envelope(&self, command: &str, file_path: &str, file_format: &str) -> Value {
61 json!({
62 "schema_version": "1.0",
63 "command": command,
64 "file": {
65 "path": file_path,
66 "format": file_format,
67 },
68 "error": {
69 "code": self.code(),
70 "message": self.message(),
71 "details": {},
72 },
73 })
74 }
75}
76
77impl fmt::Display for AppError {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.message())
80 }
81}
82
83impl std::error::Error for AppError {}
84
85pub fn anyhow_to_app_error(err: anyhow::Error) -> AppError {
87 let msg = err.to_string();
88 let lower = msg.to_lowercase();
89
90 if err
91 .chain()
92 .any(|cause| cause.downcast_ref::<std::io::Error>().is_some())
93 {
94 AppError::FileError { message: msg }
95 } else if lower.contains("unable to parse excel file")
96 || lower.contains("parser panic: malformed workbook data")
97 || lower.contains("no worksheets found")
98 {
99 AppError::ParseError { message: msg }
100 } else if lower.contains("unable to read worksheet")
101 || lower.contains("cannot load sheet")
102 || (lower.contains("sheet") && lower.contains("not found"))
103 {
104 AppError::TargetNotFound { message: msg }
105 } else if lower.contains("invalid")
106 || lower.contains("mutually exclusive")
107 || lower.contains("expected")
108 {
109 AppError::InvalidArgs { message: msg }
110 } else {
111 AppError::InternalError { message: msg }
112 }
113}