url_preview/
error.rs

1use thiserror::Error;
2#[cfg(feature = "logging")]
3use tracing::{error, warn};
4
5#[derive(Debug, Error)]
6pub enum PreviewError {
7    #[error("Failed to parse URL: {0}")]
8    UrlParseError(#[from] url::ParseError),
9
10    #[error("Failed to fetch content: {0}")]
11    FetchError(String),
12
13    #[error("Failed to extract metadata: {0}")]
14    ExtractError(String),
15
16    #[error("Cache error: {0}")]
17    CacheError(String),
18
19    #[error("Rate limit exceeded: {0}")]
20    RateLimitError(String),
21
22    #[error("Invalid content type: {0}")]
23    InvalidContentType(String),
24
25    #[error("Request timeout: {0}")]
26    TimeoutError(String),
27
28    #[error("DNS resolution failed: {0}")]
29    DnsError(String),
30
31    #[error("Connection error: {0}")]
32    ConnectionError(String),
33
34    #[error("HTTP {status}: {message}")]
35    HttpError { status: u16, message: String },
36
37    #[error("Server error (5xx): {status} - {message}")]
38    ServerError { status: u16, message: String },
39
40    #[error("Client error (4xx): {status} - {message}")]
41    ClientError { status: u16, message: String },
42
43    #[error("External service error: {service} - {message}")]
44    ExternalServiceError { service: String, message: String },
45
46    #[error("Parse error: {0}")]
47    ParseError(String),
48
49    #[error("Concurrency limit reached")]
50    ConcurrencyLimitError,
51
52    #[error("Resource not found: {0}")]
53    NotFound(String),
54}
55
56impl PreviewError {
57    pub fn log(&self) {
58        #[cfg(feature = "logging")]
59        match self {
60            PreviewError::UrlParseError(e) => {
61                warn!(error = %e, "URL parsing failed");
62            }
63            PreviewError::FetchError(e) => {
64                error!(error = %e, "Content fetch failed");
65            }
66            PreviewError::ExtractError(e) => {
67                error!(error = %e, "Metadata extraction failed");
68            }
69            PreviewError::CacheError(e) => {
70                warn!(error = %e, "Cache operation failed");
71            }
72            PreviewError::RateLimitError(e) => {
73                warn!(error = %e, "Rate limit exceeded");
74            }
75            PreviewError::InvalidContentType(e) => {
76                warn!(error = %e, "Invalid content type received");
77            }
78            PreviewError::TimeoutError(e) => {
79                warn!(error = %e, "Request timed out");
80            }
81            PreviewError::ExternalServiceError { service, message } => {
82                error!(
83                    service = %service,
84                    error = %message,
85                    "External service error occurred"
86                );
87            }
88            PreviewError::ParseError(e) => {
89                error!(error = %e, "Parse error occurred");
90            }
91            PreviewError::ConcurrencyLimitError => {
92                warn!("Concurrency limit reached");
93            }
94            PreviewError::NotFound(e) => {
95                warn!(error = %e, "Resource not found");
96            }
97            PreviewError::DnsError(e) => {
98                error!(error = %e, "DNS resolution failed");
99            }
100            PreviewError::ConnectionError(e) => {
101                error!(error = %e, "Connection failed");
102            }
103            PreviewError::HttpError { status, message } => {
104                warn!(status = %status, error = %message, "HTTP error");
105            }
106            PreviewError::ServerError { status, message } => {
107                error!(status = %status, error = %message, "Server error");
108            }
109            PreviewError::ClientError { status, message } => {
110                warn!(status = %status, error = %message, "Client error");
111            }
112        }
113        #[cfg(not(feature = "logging"))]
114        {
115            // No-op when logging is disabled
116        }
117    }
118
119    /// Convert a reqwest error into a more specific PreviewError
120    pub fn from_reqwest_error(error: reqwest::Error) -> Self {
121        if error.is_timeout() {
122            PreviewError::TimeoutError(error.to_string())
123        } else if error.is_connect() {
124            // Connection errors including DNS
125            let error_msg = error.to_string();
126            if error_msg.contains("dns")
127                || error_msg.contains("resolve")
128                || error_msg.contains("lookup")
129            {
130                PreviewError::DnsError(error_msg)
131            } else {
132                PreviewError::ConnectionError(error_msg)
133            }
134        } else if let Some(status) = error.status() {
135            let status_code = status.as_u16();
136            let message = error.to_string();
137
138            match status_code {
139                404 => PreviewError::NotFound(message),
140                400..=499 => PreviewError::ClientError {
141                    status: status_code,
142                    message,
143                },
144                500..=599 => PreviewError::ServerError {
145                    status: status_code,
146                    message,
147                },
148                _ => PreviewError::HttpError {
149                    status: status_code,
150                    message,
151                },
152            }
153        } else {
154            // Generic fetch error for other cases
155            PreviewError::FetchError(error.to_string())
156        }
157    }
158}