namecheap_client/error.rs
1//! Error types returned by this crate.
2
3use std::fmt;
4
5/// A convenient alias for results returned by this crate.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Any error that can occur while talking to the Namecheap API.
9///
10/// Failures are split into distinct categories so callers can react to them
11/// individually. In particular, errors reported by Namecheap *inside* the XML
12/// response body are surfaced as [`Error::Api`] and kept separate from
13/// transport-level HTTP failures ([`Error::Http`]).
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum Error {
17 /// The HTTP request could not be sent or the response could not be read
18 /// (DNS failure, connection reset, timeout, TLS error, and so on).
19 #[error("HTTP transport error: {0}")]
20 Http(#[from] reqwest::Error),
21
22 /// Namecheap accepted the request but returned one or more errors in the
23 /// `<Errors>` block of the XML response.
24 #[error(transparent)]
25 Api(#[from] ApiError),
26
27 /// The response body could not be decoded as the expected Namecheap XML.
28 #[error("failed to decode Namecheap XML response: {0}")]
29 Decode(#[from] quick_xml::DeError),
30
31 /// The server returned a non-success HTTP status and the body was not a
32 /// recognizable Namecheap error document.
33 #[error("unexpected HTTP status {status}")]
34 UnexpectedStatus {
35 /// The HTTP status code returned by the server.
36 status: reqwest::StatusCode,
37 /// The raw response body, retained for debugging.
38 body: String,
39 },
40
41 /// Namecheap reported success but the response contained no command result.
42 #[error("the API reported success but returned no command result")]
43 EmptyResponse,
44
45 /// The [`Client`](crate::Client) was misconfigured (for example, a required
46 /// credential was missing when calling
47 /// [`ClientBuilder::build`](crate::ClientBuilder::build)).
48 #[error("invalid client configuration: {0}")]
49 Configuration(String),
50}
51
52/// One or more errors returned by Namecheap inside an XML response.
53///
54/// Namecheap signals failures with `Status="ERROR"` on the response envelope and
55/// lists the individual problems in an `<Errors>` element. Each entry carries a
56/// numeric code and a human-readable message; inspect [`ApiError::errors`] (or
57/// use [`ApiError::has_code`]) to branch on specific codes.
58#[derive(Debug, Clone)]
59#[non_exhaustive]
60pub struct ApiError {
61 /// The individual errors reported by the API, in the order returned.
62 pub errors: Vec<ApiErrorEntry>,
63}
64
65impl ApiError {
66 /// Returns `true` if any returned error carries the given numeric code.
67 ///
68 /// Codes are compared as strings because Namecheap documents them that way
69 /// (for example `"2030280"` for "TLD is not supported").
70 #[must_use]
71 pub fn has_code(&self, code: &str) -> bool {
72 self.errors.iter().any(|entry| entry.number == code)
73 }
74
75 /// Returns the first reported error, if any.
76 #[must_use]
77 pub fn first(&self) -> Option<&ApiErrorEntry> {
78 self.errors.first()
79 }
80}
81
82impl fmt::Display for ApiError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "Namecheap API error")?;
85 for (index, entry) in self.errors.iter().enumerate() {
86 let separator = if index == 0 { ": " } else { "; " };
87 write!(f, "{separator}[{}] {}", entry.number, entry.message)?;
88 }
89 Ok(())
90 }
91}
92
93impl std::error::Error for ApiError {}
94
95/// A single error reported by Namecheap.
96#[derive(Debug, Clone)]
97#[non_exhaustive]
98pub struct ApiErrorEntry {
99 /// The Namecheap error number (for example `"2019166"`).
100 pub number: String,
101 /// The human-readable error message.
102 pub message: String,
103}