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>;