1use thiserror::Error;
6
7#[derive(Debug, Error)]
9#[allow(dead_code)]
10pub enum ScannerError {
11 #[error("HTTP request failed: {0}")]
12 Request(#[from] reqwest::Error),
13
14 #[error("URL parse error: {0}")]
15 UrlParse(#[from] url::ParseError),
16
17 #[error("IO error: {0}")]
18 Io(#[from] std::io::Error),
19
20 #[error("JSON parse error: {0}")]
21 Json(#[from] serde_json::Error),
22
23 #[error("Regex error: {0}")]
24 Regex(#[from] regex::Error),
25
26 #[error("Invalid configuration: {0}")]
27 Config(String),
28
29 #[error("Task join error: {0}")]
30 Join(#[from] tokio::task::JoinError),
31
32 #[error("Response body too large (limit: {limit} bytes)")]
33 ResponseTooLarge { limit: usize },
34
35 #[error("Timeout after {0}s")]
36 Timeout(u64),
37
38 #[error("{0}")]
39 Other(String),
40}
41
42#[derive(Debug, Clone, serde::Serialize)]
44pub struct CapturedError {
45 pub timestamp: String,
46 pub context: String,
47 pub url: Option<String>,
48 pub error_type: String,
49 pub message: String,
50}
51
52impl std::fmt::Display for CapturedError {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 if let Some(ref url) = self.url {
55 write!(
56 f,
57 "[{}] {} (url: {}): {}",
58 self.context, self.error_type, url, self.message
59 )
60 } else {
61 write!(
62 f,
63 "[{}] {}: {}",
64 self.context, self.error_type, self.message
65 )
66 }
67 }
68}
69
70impl CapturedError {
71 pub fn new(
72 context: impl Into<String>,
73 url: Option<String>,
74 err: &dyn std::error::Error,
75 ) -> Self {
76 Self {
77 timestamp: chrono::Utc::now().to_rfc3339(),
78 context: context.into(),
79 url,
80 error_type: std::any::type_name_of_val(err).to_string(),
81 message: err.to_string(),
82 }
83 }
84
85 pub fn from_str(
86 context: impl Into<String>,
87 url: Option<String>,
88 msg: impl Into<String>,
89 ) -> Self {
90 Self {
91 timestamp: chrono::Utc::now().to_rfc3339(),
92 context: context.into(),
93 url,
94 error_type: "String".to_string(),
95 message: msg.into(),
96 }
97 }
98
99 pub fn internal(msg: impl Into<String>) -> Self {
101 Self {
102 timestamp: chrono::Utc::now().to_rfc3339(),
103 context: "internal".to_string(),
104 url: None,
105 error_type: "Internal".to_string(),
106 message: msg.into(),
107 }
108 }
109
110 #[allow(dead_code)]
112 pub fn parse(context: impl Into<String>, msg: impl Into<String>) -> Self {
113 Self {
114 timestamp: chrono::Utc::now().to_rfc3339(),
115 context: context.into(),
116 url: None,
117 error_type: "ParseError".to_string(),
118 message: msg.into(),
119 }
120 }
121}
122
123pub type ScannerResult<T> = Result<T, ScannerError>;