Skip to main content

api_scanner/
error.rs

1// src/error.rs
2//
3// Scanner error types.
4
5use thiserror::Error;
6
7/// Typed error variants produced during scanning.
8#[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/// A captured error event stored in the report.
43#[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    /// Construct a non-HTTP internal error (e.g. task panic).
100    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    /// Construct a parse error.
111    #[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>;