Skip to main content

podcast_api/
error.rs

1use reqwest::{StatusCode, header::HeaderMap};
2
3/// The status, headers, and body returned by an unsuccessful API request.
4#[derive(Debug)]
5pub struct ApiError {
6    /// HTTP status code.
7    pub status: StatusCode,
8    /// API response headers, including usage and quota information.
9    pub headers: HeaderMap,
10    /// Unmodified response body.
11    pub body: String,
12}
13
14impl std::fmt::Display for ApiError {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "HTTP {}: {}", self.status, self.body)
17    }
18}
19
20/// Errors returned by [`crate::Client`]. HTTP errors retain their response context.
21#[derive(Debug)]
22#[non_exhaustive]
23pub enum Error {
24    /// Wrong API key or a suspended account (401).
25    AuthenticationError(Box<ApiError>),
26    /// The account is not allowed to perform this operation (403).
27    PermissionDeniedError(Box<ApiError>),
28    /// Unable to connect or a request timed out.
29    ApiConnectionError(reqwest::Error),
30    /// Invalid API request (400).
31    InvalidRequestError(Box<ApiError>),
32    /// Request quota or rate limit exceeded (429).
33    RateLimitError(Box<ApiError>),
34    /// Endpoint or requested content was not found (404).
35    NotFoundError(Box<ApiError>),
36    /// Other unsuccessful HTTP responses, including redirects and server errors.
37    ListenApiError(Box<ApiError>),
38    /// Invalid local parameter structure or path identifier.
39    InvalidParameter(String),
40    /// Other errors from the HTTP client.
41    Reqwest(reqwest::Error),
42    /// JSON creation or processing error.
43    Json(serde_json::Error),
44}
45
46impl Error {
47    pub(crate) fn from_api(error: Box<ApiError>) -> Self {
48        match error.status {
49            StatusCode::BAD_REQUEST => Self::InvalidRequestError(error),
50            StatusCode::UNAUTHORIZED => Self::AuthenticationError(error),
51            StatusCode::FORBIDDEN => Self::PermissionDeniedError(error),
52            StatusCode::NOT_FOUND => Self::NotFoundError(error),
53            StatusCode::TOO_MANY_REQUESTS => Self::RateLimitError(error),
54            _ => Self::ListenApiError(error),
55        }
56    }
57
58    /// Access the response status, headers, and body for an HTTP error.
59    pub fn api_error(&self) -> Option<&ApiError> {
60        match self {
61            Self::AuthenticationError(e)
62            | Self::PermissionDeniedError(e)
63            | Self::InvalidRequestError(e)
64            | Self::RateLimitError(e)
65            | Self::NotFoundError(e)
66            | Self::ListenApiError(e) => Some(e),
67            _ => None,
68        }
69    }
70}
71
72impl From<reqwest::Error> for Error {
73    fn from(error: reqwest::Error) -> Self {
74        if error.is_connect() || error.is_timeout() {
75            Self::ApiConnectionError(error)
76        } else {
77            Self::Reqwest(error)
78        }
79    }
80}
81
82impl From<serde_json::Error> for Error {
83    fn from(error: serde_json::Error) -> Self {
84        Self::Json(error)
85    }
86}
87
88impl std::error::Error for Error {
89    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
90        match self {
91            Self::Reqwest(e) | Self::ApiConnectionError(e) => Some(e),
92            Self::Json(e) => Some(e),
93            _ => None,
94        }
95    }
96}
97
98impl std::fmt::Display for Error {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        if let Some(error) = self.api_error() {
101            return std::fmt::Display::fmt(error, f);
102        }
103        match self {
104            Self::Reqwest(e) | Self::ApiConnectionError(e) => std::fmt::Display::fmt(e, f),
105            Self::Json(e) => std::fmt::Display::fmt(e, f),
106            Self::InvalidParameter(message) => f.write_str(message),
107            _ => unreachable!("HTTP error handled above"),
108        }
109    }
110}