Skip to main content

scrapebadger/core/
error.rs

1use serde_json::Value;
2
3/// Convenient result alias used throughout the crate.
4pub type Result<T> = std::result::Result<T, Error>;
5
6/// Every error the SDK can produce.
7///
8/// API failures are mapped to typed variants by HTTP status where the meaning
9/// is well-defined (`401`, `402`, `422`, `429`); anything else lands in
10/// [`Error::Api`] with the raw status and decoded JSON body so callers never
11/// lose information.
12#[derive(Debug, thiserror::Error)]
13#[non_exhaustive]
14pub enum Error {
15    /// No API key was supplied and `SCRAPEBADGER_API_KEY` was not set.
16    #[error("missing API key: pass one to ScrapeBadger::new or set SCRAPEBADGER_API_KEY")]
17    MissingApiKey,
18
19    /// The configured base URL could not be used to build a request URL.
20    #[error("invalid URL: {0}")]
21    InvalidUrl(String),
22
23    /// Transport-level failure (DNS, TLS, connection, timeout).
24    #[error("http transport error: {0}")]
25    Transport(#[from] reqwest::Error),
26
27    /// `401 Unauthorized` — the API key is missing or invalid.
28    #[error("unauthorized (401): {message}")]
29    Unauthorized { message: String },
30
31    /// `402 Payment Required` — out of credits or no active subscription.
32    #[error("payment required (402): {message}")]
33    PaymentRequired { message: String },
34
35    /// `429 Too Many Requests` — rate limit exceeded.
36    #[error("rate limited (429){}", match retry_after { Some(s) => format!(", retry after {s}s"), None => String::new() })]
37    RateLimited {
38        /// Seconds to wait before retrying, if the server told us.
39        retry_after: Option<u64>,
40        message: String,
41    },
42
43    /// `422 Unprocessable Entity` — request validation failed.
44    #[error("validation error (422): {message}")]
45    Validation {
46        message: String,
47        /// The raw FastAPI `detail` array.
48        detail: Value,
49    },
50
51    /// Any other non-success HTTP status.
52    #[error("api error (status {status}): {message}")]
53    Api {
54        status: u16,
55        message: String,
56        /// Decoded JSON body (or `Value::Null` if the body was not JSON).
57        body: Value,
58    },
59
60    /// The response body could not be decoded into the expected type.
61    #[error("failed to decode response: {0}")]
62    Decode(String),
63
64    /// WebSocket streaming error (Twitter Streams).
65    #[cfg(feature = "stream")]
66    #[error("websocket error: {0}")]
67    WebSocket(String),
68}