1use std::io;
12use thiserror::Error;
13
14#[derive(Error, Debug)]
16pub enum Error {
17 #[error("Configuration error: {0}")]
19 Config(String),
20
21 #[error("Proxy error: {0}")]
23 Proxy(String),
24
25 #[error("Scanner error: {0}")]
27 Scanner(String),
28
29 #[error("Detection error: {0}")]
31 Detection(String),
32
33 #[error("Plugin error: {0}")]
35 Plugin(String),
36
37 #[error("API error: {0}")]
39 Api(String),
40
41 #[error("Network error: {0}")]
43 Network(String),
44
45 #[error("TLS error: {0}")]
47 Tls(String),
48
49 #[error("Target error: {0}")]
51 Target(String),
52
53 #[error("Payload error: {0}")]
55 Payload(String),
56
57 #[error("Pattern error: {0}")]
59 Pattern(String),
60
61 #[error("Rate limit error: {0}")]
63 RateLimit(String),
64
65 #[error("Threading error: {0}")]
67 Threading(String),
68
69 #[error("Database error: {0}")]
71 Database(String),
72
73 #[error("Serialization error: {0}")]
75 Serialization(String),
76
77 #[error(transparent)]
79 Io(#[from] io::Error),
80
81 #[error(transparent)]
83 Json(#[from] serde_json::Error),
84
85 #[error(transparent)]
87 Toml(#[from] toml::de::Error),
88
89 #[error("{context}: {message}")]
91 Custom { context: String, message: String },
92
93 #[error("Error: {0}")]
95 Other(String),
96}
97
98impl Error {
99 pub fn config(msg: impl Into<String>) -> Self {
101 Error::Config(msg.into())
102 }
103
104 pub fn scanner(msg: impl Into<String>) -> Self {
106 Error::Scanner(msg.into())
107 }
108
109 pub fn api(msg: impl Into<String>) -> Self {
111 Error::Api(msg.into())
112 }
113
114 pub fn network(msg: impl Into<String>) -> Self {
116 Error::Network(msg.into())
117 }
118
119 pub fn plugin(msg: impl Into<String>) -> Self {
121 Error::Plugin(msg.into())
122 }
123
124 pub fn target(msg: impl Into<String>) -> Self {
126 Error::Target(msg.into())
127 }
128
129 pub fn custom(context: impl Into<String>, message: impl Into<String>) -> Self {
131 Error::Custom {
132 context: context.into(),
133 message: message.into(),
134 }
135 }
136
137 pub fn kind(&self) -> &'static str {
139 match self {
140 Error::Config(_) => "CONFIG",
141 Error::Proxy(_) => "PROXY",
142 Error::Scanner(_) => "SCANNER",
143 Error::Detection(_) => "DETECTION",
144 Error::Plugin(_) => "PLUGIN",
145 Error::Api(_) => "API",
146 Error::Network(_) => "NETWORK",
147 Error::Tls(_) => "TLS",
148 Error::Target(_) => "TARGET",
149 Error::Payload(_) => "PAYLOAD",
150 Error::Pattern(_) => "PATTERN",
151 Error::RateLimit(_) => "RATE_LIMIT",
152 Error::Threading(_) => "THREADING",
153 Error::Database(_) => "DATABASE",
154 Error::Serialization(_) => "SERIALIZATION",
155 Error::Io(_) => "IO",
156 Error::Json(_) => "JSON",
157 Error::Toml(_) => "TOML",
158 Error::Custom { .. } => "CUSTOM",
159 Error::Other(_) => "OTHER",
160 }
161 }
162
163 pub fn is_transient(&self) -> bool {
165 matches!(
166 self,
167 Error::Network(_)
168 | Error::RateLimit(_)
169 | Error::Threading(_)
170 | Error::Io(_)
171 | Error::Custom { .. }
172 )
173 }
174
175 pub fn is_critical(&self) -> bool {
177 matches!(
178 self,
179 Error::Config(_) | Error::Tls(_) | Error::Target(_) | Error::Database(_)
180 )
181 }
182}
183
184pub type Result<T> = std::result::Result<T, Error>;
186
187impl Error {
189 pub fn proxy(msg: impl Into<String>) -> Self {
191 Error::Proxy(msg.into())
192 }
193
194 pub fn detection(msg: impl Into<String>) -> Self {
196 Error::Detection(msg.into())
197 }
198
199 pub fn tls(msg: impl Into<String>) -> Self {
201 Error::Tls(msg.into())
202 }
203
204 pub fn payload(msg: impl Into<String>) -> Self {
206 Error::Payload(msg.into())
207 }
208
209 pub fn rate_limit(msg: impl Into<String>) -> Self {
211 Error::RateLimit(msg.into())
212 }
213
214 pub fn threading(msg: impl Into<String>) -> Self {
216 Error::Threading(msg.into())
217 }
218
219 pub fn database(msg: impl Into<String>) -> Self {
221 Error::Database(msg.into())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_error_display() {
231 let err = Error::scanner("test scanner error");
232 assert_eq!(err.to_string(), "Scanner error: test scanner error");
233 }
234
235 #[test]
236 fn test_error_kind() {
237 assert_eq!(Error::scanner("msg").kind(), "SCANNER");
238 assert_eq!(Error::config("msg").kind(), "CONFIG");
239 assert_eq!(Error::network("msg").kind(), "NETWORK");
240 assert_eq!(Error::plugin("msg").kind(), "PLUGIN");
241 }
242
243 #[test]
244 fn test_error_is_transient() {
245 assert!(Error::network("msg").is_transient());
246 assert!(Error::rate_limit("msg").is_transient());
247 assert!(!Error::config("msg").is_transient());
248 }
249
250 #[test]
251 fn test_error_is_critical() {
252 assert!(Error::config("msg").is_critical());
253 assert!(Error::tls("msg").is_critical());
254 assert!(!Error::network("msg").is_critical());
255 }
256
257 #[test]
258 fn test_error_custom() {
259 let err = Error::custom("SCAN", "Target unreachable");
260 assert_eq!(err.to_string(), "SCAN: Target unreachable");
261 }
262
263 #[test]
264 fn test_error_from_io() {
265 let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
266 let err: Error = io_err.into();
267 assert_eq!(err.kind(), "IO");
268 }
269
270 #[test]
271 fn test_error_from_json() {
272 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
273 let _err: Error = json_err.into();
274 }
275
276 #[test]
277 fn test_error_source_chain() {
278 let err = Error::scanner("test");
279 assert_eq!(err.to_string(), "Scanner error: test");
281
282 let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
283 let err: Error = io_err.into();
284 assert_eq!(err.kind(), "IO");
286 }
287}