Skip to main content

scrapling_fetch/
error.rs

1//! Error types for the scrapling-fetch crate.
2//!
3//! Every fallible operation in this crate returns [`Result<T>`], which is an alias
4//! for `std::result::Result<T, FetchError>`. The [`FetchError`] enum covers the full
5//! range of things that can go wrong -- from low-level network failures through to
6//! proxy misconfiguration and session lifecycle mistakes.
7//!
8//! All variants implement the standard [`Error`](std::error::Error) trait with proper
9//! `source()` chaining, so you can inspect the underlying cause when needed.
10
11use std::fmt;
12
13/// Errors that can occur during HTTP fetching operations.
14///
15/// This is the single error type for the entire crate. It wraps errors from
16/// underlying libraries (wreq, url, serde_json) and adds scrapling-specific
17/// variants for proxy, session, and retry failures.
18#[derive(Debug)]
19pub enum FetchError {
20    /// An error from the underlying wreq HTTP client, such as a connection failure,
21    /// timeout, or TLS handshake error. Inspect the inner [`wreq::Error`] for details.
22    Request(wreq::Error),
23    /// The URL string could not be parsed. This typically happens when a relative URL
24    /// is passed where an absolute one is expected, or when the scheme is missing.
25    Url(url::ParseError),
26    /// A JSON serialization or deserialization error. This occurs when a
27    /// [`RequestConfig::json`](crate::RequestConfig::json) body cannot be serialized,
28    /// or when response JSON is malformed.
29    Json(serde_json::Error),
30    /// The proxy configuration is invalid. The contained string describes what went
31    /// wrong -- for example, a duplicate proxy in a rotator or a malformed proxy URL.
32    InvalidProxy(String),
33    /// A request was attempted on a [`FetcherSession`](crate::FetcherSession) that
34    /// has not been opened yet. Call [`open()`](crate::FetcherSession::open) first.
35    SessionNotActive,
36    /// [`open()`](crate::FetcherSession::open) was called on a session that is
37    /// already active. Close the existing session before opening a new one.
38    SessionAlreadyActive,
39    /// All retry attempts have been exhausted without a successful response. The
40    /// `last_error` field contains the error from the final attempt so you can
41    /// diagnose the root cause.
42    MaxRetriesExceeded {
43        /// The total number of attempts made.
44        attempts: u32,
45        /// The error from the final attempt.
46        last_error: Box<FetchError>,
47    },
48    /// The response has no associated request metadata. This can happen when a
49    /// `Response` is constructed manually rather than by the fetcher.
50    NoRequest,
51    /// A catch-all error with a descriptive message for situations not covered by
52    /// the other variants (e.g., an invalid HTTP method string).
53    Other(String),
54}
55
56impl fmt::Display for FetchError {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::Request(e) => write!(f, "HTTP request error: {e}"),
60            Self::Url(e) => write!(f, "URL parse error: {e}"),
61            Self::Json(e) => write!(f, "JSON error: {e}"),
62            Self::InvalidProxy(msg) => write!(f, "invalid proxy: {msg}"),
63            Self::SessionNotActive => write!(f, "no active session"),
64            Self::SessionAlreadyActive => write!(f, "session already active"),
65            Self::MaxRetriesExceeded {
66                attempts,
67                last_error,
68            } => write!(f, "failed after {attempts} attempts: {last_error}"),
69            Self::NoRequest => write!(f, "response has no associated request (not from a spider)"),
70            Self::Other(msg) => write!(f, "{msg}"),
71        }
72    }
73}
74
75impl std::error::Error for FetchError {
76    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77        match self {
78            Self::Request(e) => Some(e),
79            Self::Url(e) => Some(e),
80            Self::Json(e) => Some(e),
81            Self::MaxRetriesExceeded { last_error, .. } => Some(last_error.as_ref()),
82            _ => None,
83        }
84    }
85}
86
87impl From<wreq::Error> for FetchError {
88    fn from(e: wreq::Error) -> Self {
89        Self::Request(e)
90    }
91}
92
93impl From<url::ParseError> for FetchError {
94    fn from(e: url::ParseError) -> Self {
95        Self::Url(e)
96    }
97}
98
99impl From<serde_json::Error> for FetchError {
100    fn from(e: serde_json::Error) -> Self {
101        Self::Json(e)
102    }
103}
104
105/// A convenience result type alias that pins the error type to [`FetchError`].
106///
107/// Every public function in this crate that can fail returns this type, so callers
108/// only need to handle one error enum.
109pub type Result<T> = std::result::Result<T, FetchError>;