Skip to main content

termivar_core/
error.rs

1//! Historical cross-crate error compatibility facade.
2//!
3//! This module is available only with the non-default `legacy-contracts`
4//! feature. Current scanner and adapter crates own their errors locally.
5//!
6//! ## Deprecated compatibility surface
7//!
8//! The broad variants remain solely for the pinned pre-1.0 patch-compatibility
9//! baseline. They do not describe errors emitted by the default runtime.
10
11use std::io;
12use thiserror::Error;
13
14/// Historical shared error variants retained for compatibility.
15#[derive(Error, Debug)]
16pub enum Error {
17    /// Configuration validation or loading error
18    #[error("Configuration error: {0}")]
19    Config(String),
20
21    /// Proxy/MITM operation failed
22    #[error("Proxy error: {0}")]
23    Proxy(String),
24
25    /// Scanner execution failed
26    #[error("Scanner error: {0}")]
27    Scanner(String),
28
29    /// Vulnerability detection or analysis failed
30    #[error("Detection error: {0}")]
31    Detection(String),
32
33    /// Plugin loading or execution failed
34    #[error("Plugin error: {0}")]
35    Plugin(String),
36
37    /// API endpoint or communication error
38    #[error("API error: {0}")]
39    Api(String),
40
41    /// Network operation failed
42    #[error("Network error: {0}")]
43    Network(String),
44
45    /// TLS/Certificate operation failed
46    #[error("TLS error: {0}")]
47    Tls(String),
48
49    /// Target URL parsing or validation failed
50    #[error("Target error: {0}")]
51    Target(String),
52
53    /// Payload generation or encoding failed
54    #[error("Payload error: {0}")]
55    Payload(String),
56
57    /// Pattern matching or analysis failed
58    #[error("Pattern error: {0}")]
59    Pattern(String),
60
61    /// Rate limiting or throttling error
62    #[error("Rate limit error: {0}")]
63    RateLimit(String),
64
65    /// Thread pool or async operation error
66    #[error("Threading error: {0}")]
67    Threading(String),
68
69    /// Database or persistence error
70    #[error("Database error: {0}")]
71    Database(String),
72
73    /// Serialization/Deserialization error
74    #[error("Serialization error: {0}")]
75    Serialization(String),
76
77    /// IO operation failed (transparent source chain)
78    #[error(transparent)]
79    Io(#[from] io::Error),
80
81    /// JSON serialization failed (transparent source chain)
82    #[error(transparent)]
83    Json(#[from] serde_json::Error),
84
85    /// TOML serialization failed (transparent source chain)
86    #[error(transparent)]
87    Toml(#[from] toml::de::Error),
88
89    /// Custom error with context
90    #[error("{context}: {message}")]
91    Custom { context: String, message: String },
92
93    /// Generic error (fallback)
94    #[error("Error: {0}")]
95    Other(String),
96}
97
98impl Error {
99    /// Creates a config error with context
100    pub fn config(msg: impl Into<String>) -> Self {
101        Error::Config(msg.into())
102    }
103
104    /// Creates a scanner error with context
105    pub fn scanner(msg: impl Into<String>) -> Self {
106        Error::Scanner(msg.into())
107    }
108
109    /// Creates an API error with context
110    pub fn api(msg: impl Into<String>) -> Self {
111        Error::Api(msg.into())
112    }
113
114    /// Creates a network error with context
115    pub fn network(msg: impl Into<String>) -> Self {
116        Error::Network(msg.into())
117    }
118
119    /// Creates a plugin error with context
120    pub fn plugin(msg: impl Into<String>) -> Self {
121        Error::Plugin(msg.into())
122    }
123
124    /// Creates a target error with context
125    pub fn target(msg: impl Into<String>) -> Self {
126        Error::Target(msg.into())
127    }
128
129    /// Creates a custom error with context label
130    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    /// Returns error kind as string (for logging, metrics)
138    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    /// Returns true if error is transient (can retry)
164    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    /// Returns true if error is critical (should stop)
176    pub fn is_critical(&self) -> bool {
177        matches!(
178            self,
179            Error::Config(_) | Error::Tls(_) | Error::Target(_) | Error::Database(_)
180        )
181    }
182}
183
184/// Historical result alias retained for compatibility.
185pub type Result<T> = std::result::Result<T, Error>;
186
187// Additional helper methods for common patterns
188impl Error {
189    /// Creates a proxy error
190    pub fn proxy(msg: impl Into<String>) -> Self {
191        Error::Proxy(msg.into())
192    }
193
194    /// Creates a detection error
195    pub fn detection(msg: impl Into<String>) -> Self {
196        Error::Detection(msg.into())
197    }
198
199    /// Creates a TLS error
200    pub fn tls(msg: impl Into<String>) -> Self {
201        Error::Tls(msg.into())
202    }
203
204    /// Creates a payload error
205    pub fn payload(msg: impl Into<String>) -> Self {
206        Error::Payload(msg.into())
207    }
208
209    /// Creates a rate limit error
210    pub fn rate_limit(msg: impl Into<String>) -> Self {
211        Error::RateLimit(msg.into())
212    }
213
214    /// Creates a threading error
215    pub fn threading(msg: impl Into<String>) -> Self {
216        Error::Threading(msg.into())
217    }
218
219    /// Creates a database error
220    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        // Custom variant display includes message
280        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        // IO error preserves the original error
285        assert_eq!(err.kind(), "IO");
286    }
287}