ipwhois/error.rs
1//! Error type returned by every fallible operation in this crate.
2//!
3//! The library does not intentionally panic — every failure (network
4//! outage, rate limit, bad IP, bad options, …) flows through the
5//! [`Error`] enum below. Three categories cover the failure modes
6//! (`api`, `network`, `invalid_argument`); each variant carries a
7//! human-readable `message`, and API errors additionally carry HTTP
8//! status and a free-plan `Retry-After` value when available.
9
10use thiserror::Error as ThisError;
11
12/// All errors returned by this crate.
13///
14/// Every variant carries a human-readable `message`. API errors additionally
15/// carry the upstream HTTP status code, and HTTP 429 responses on the **free
16/// plan** also carry the `Retry-After` value the API asked us to honour
17/// (the paid endpoint does not send the header).
18///
19/// Match on the variant when you want to branch on the category, or call
20/// [`Error::error_type`] to get a stable category string
21/// (`"api"`, `"network"`, `"invalid_argument"`).
22#[derive(Debug, Clone, ThisError)]
23#[non_exhaustive]
24pub enum Error {
25 /// The ipwhois.io API itself returned an error. Covers HTTP 4xx / 5xx
26 /// responses, malformed JSON bodies, and HTTP 2xx responses where the
27 /// API sets `success: false` (e.g. "Invalid IP address",
28 /// "Reserved range").
29 #[error("{message}")]
30 Api {
31 /// Human-readable description from the API or synthesised by the
32 /// client when the body is unparseable.
33 message: String,
34 /// HTTP status code, when the failure was an HTTP 4xx / 5xx.
35 /// Absent for `success: false` bodies returned with HTTP 2xx.
36 http_status: Option<u16>,
37 /// Value of the `Retry-After` header, in seconds. Only present on
38 /// HTTP 429 responses from the **free plan** (`ipwho.is`); the paid
39 /// endpoint (`ipwhois.pro`) does not send the header.
40 retry_after: Option<u64>,
41 },
42
43 /// Transport-level failure: DNS, connection refused, TLS handshake,
44 /// timeout, etc. The request never reached the API meaningfully.
45 #[error("Network error: {message}")]
46 Network {
47 /// Description of the underlying transport failure.
48 message: String,
49 },
50
51 /// The caller supplied an invalid argument before any request was made
52 /// (unsupported language, empty bulk list, more than 100 IPs, …).
53 #[error("Invalid argument: {message}")]
54 InvalidArgument {
55 /// Human-readable description of what was wrong.
56 message: String,
57 },
58}
59
60impl Error {
61 /// Stable string identifying the category of this error.
62 ///
63 /// Returns one of: `"api"`, `"network"`, or `"invalid_argument"`.
64 pub fn error_type(&self) -> &'static str {
65 match self {
66 Error::Api { .. } => "api",
67 Error::Network { .. } => "network",
68 Error::InvalidArgument { .. } => "invalid_argument",
69 }
70 }
71
72 /// Human-readable message for this error.
73 pub fn message(&self) -> &str {
74 match self {
75 Error::Api { message, .. } => message,
76 Error::Network { message } => message,
77 Error::InvalidArgument { message } => message,
78 }
79 }
80
81 /// HTTP status code, if this is an [`Error::Api`] variant carrying one.
82 pub fn http_status(&self) -> Option<u16> {
83 match self {
84 Error::Api { http_status, .. } => *http_status,
85 _ => None,
86 }
87 }
88
89 /// Value of the `Retry-After` header in seconds, if available.
90 /// Only present on HTTP 429 responses from the free plan.
91 pub fn retry_after(&self) -> Option<u64> {
92 match self {
93 Error::Api { retry_after, .. } => *retry_after,
94 _ => None,
95 }
96 }
97
98 pub(crate) fn invalid_argument(msg: impl Into<String>) -> Self {
99 Error::InvalidArgument {
100 message: msg.into(),
101 }
102 }
103
104 pub(crate) fn network(msg: impl Into<String>) -> Self {
105 Error::Network {
106 message: msg.into(),
107 }
108 }
109
110 pub(crate) fn api(
111 message: impl Into<String>,
112 http_status: Option<u16>,
113 retry_after: Option<u64>,
114 ) -> Self {
115 Error::Api {
116 message: message.into(),
117 http_status,
118 retry_after,
119 }
120 }
121}
122
123impl From<reqwest::Error> for Error {
124 fn from(e: reqwest::Error) -> Self {
125 Error::Network {
126 message: e.to_string(),
127 }
128 }
129}