1use std::fmt;
2
3use reqwest::{Method, StatusCode};
4
5pub type APIResult<O, E> = Result<O, Error<E>>;
7
8#[allow(clippy::module_name_repetitions)]
11pub type NoSpecificError = std::convert::Infallible;
12
13#[derive(Debug, thiserror::Error)]
21#[allow(clippy::module_name_repetitions)]
22pub enum ErrorKind<T> {
23 #[error("failed to send request: {0}")]
25 Request(#[source] reqwest::Error),
26 #[error("received error response from server: {0}")]
28 Response(ResponseError),
29 #[error("failed to parse JSON response: {0}")]
31 ParseJSON(#[source] reqwest::Error),
32 #[error(transparent)]
34 Other(T),
35}
36
37impl<T> ErrorKind<T> {
38 pub fn map<F, U>(self, f: F) -> ErrorKind<U>
40 where F: FnOnce(T) -> U,
41 {
42 match self {
43 Self::Request(err) => ErrorKind::Request(err),
44 Self::Response(err) => ErrorKind::Response(err),
45 Self::ParseJSON(err) => ErrorKind::ParseJSON(err),
46 Self::Other(err) => ErrorKind::Other(f(err)),
47 }
48 }
49}
50
51#[derive(Debug, thiserror::Error)]
56#[error("failed to {method} to {url:?}: {inner}")]
57pub struct Error<T> where T: std::error::Error + 'static {
58 #[source]
59 inner: ErrorKind<T>,
60 url: String,
61 method: Method,
62}
63
64impl<T> Error<T> where T: std::error::Error + 'static {
65 pub fn map<F, U>(self, f: F) -> Error<U>
67 where F: FnOnce(T) -> U,
68 U: std::error::Error + 'static,
69 {
70 let Self { inner, method, url } = self;
71 let inner = inner.map(f);
72 Error::inner_new(method, url, inner)
73 }
74
75 pub(crate) fn map_err(method: Method, url: String) -> impl FnOnce(T) -> Self {
76 move |inner| Self::new(method, url, inner)
77 }
78
79 pub(crate) fn map_json_err(method: Method, url: String) -> impl FnOnce(reqwest::Error) -> Self {
80 move |error| Self::from_json(method, url, error)
81 }
82
83 pub(crate) fn map_request_err(method: Method, url: String) -> impl FnOnce(reqwest::Error) -> Self {
84 move |error| Self::from_request(method, url, error)
85 }
86
87 pub(crate) fn map_response_err(method: Method, url: String) -> impl FnOnce(ResponseError) -> Self {
88 move |error| Self::from_response(method, url, error)
89 }
90
91 fn inner_new(method: Method, url: String, inner: ErrorKind<T>) -> Self {
92 Self { inner, url, method }
93 }
94
95 pub(crate) fn new(method: Method, url: String, inner: T) -> Self {
96 Self::inner_new(method, url, ErrorKind::Other(inner))
97 }
98
99 pub(crate) fn from_json(method: Method, url: String, inner: reqwest::Error) -> Self {
100 Self::inner_new(method, url, ErrorKind::ParseJSON(inner))
101 }
102
103 pub(crate) fn from_request(method: Method, url: String, inner: reqwest::Error) -> Self {
104 Self::inner_new(method, url, ErrorKind::Request(inner))
105 }
106
107 pub(crate) fn from_response(method: Method, url: String, inner: ResponseError) -> Self {
108 Self::inner_new(method, url, ErrorKind::Response(inner))
109 }
110
111 pub fn method(&self) -> &Method {
116 &self.method
117 }
118
119 pub fn url(&self) -> &str {
124 &self.url
125 }
126
127 pub fn inner(&self) -> &ErrorKind<T> {
129 &self.inner
130 }
131}
132
133#[derive(Debug)]
135#[allow(clippy::module_name_repetitions)]
136pub struct ResponseError {
137 status: StatusCode,
138 body: Option<String>,
139}
140
141impl fmt::Display for ResponseError {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "failed with status {}", self.status)?;
144 if let Some(body) = &self.body {
145 if !body.is_empty() {
146 write!(f, ": {}", body)?;
147 }
148 }
149 Ok(())
150 }
151}
152
153impl std::error::Error for ResponseError {}
154
155pub(crate) async fn error_from_response(resp: reqwest::Response) -> Result<reqwest::Response, ResponseError> {
156 if resp.status().is_client_error() || resp.status().is_server_error() {
157 Err(ResponseError {
158 status: resp.status(),
159 body: resp.text().await.ok(),
160 })
161 } else {
162 Ok(resp)
163 }
164}