onedrive_api/
error.rs

1use std::time::Duration;
2
3use crate::resource::{ErrorResponse, OAuth2ErrorResponse};
4use reqwest::StatusCode;
5use thiserror::Error;
6
7/// An alias to `Result` of [`Error`][error].
8///
9/// [error]: ./struct.Error.html
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Error of API request
13#[derive(Debug, Error)]
14#[error(transparent)]
15pub struct Error {
16    inner: Box<ErrorKind>,
17}
18
19#[derive(Debug, Error)]
20enum ErrorKind {
21    // Errors about ser/de are included.
22    #[error("Request error: {0}")]
23    RequestError(#[source] reqwest::Error),
24    #[error("Unexpected response: {reason}")]
25    UnexpectedResponse { reason: &'static str },
26    #[error("Api error with {status}: ({}) {}", .response.code, .response.message)]
27    ErrorResponse {
28        status: StatusCode,
29        response: ErrorResponse,
30        retry_after: Option<u32>,
31    },
32    #[error("OAuth2 error with {status}: ({}) {}", .response.error, .response.error_description)]
33    OAuth2Error {
34        status: StatusCode,
35        response: OAuth2ErrorResponse,
36        retry_after: Option<u32>,
37    },
38}
39
40impl Error {
41    pub(crate) fn from_error_response(
42        status: StatusCode,
43        response: ErrorResponse,
44        retry_after: Option<u32>,
45    ) -> Self {
46        Self {
47            inner: Box::new(ErrorKind::ErrorResponse {
48                status,
49                response,
50                retry_after,
51            }),
52        }
53    }
54
55    pub(crate) fn unexpected_response(reason: &'static str) -> Self {
56        Self {
57            inner: Box::new(ErrorKind::UnexpectedResponse { reason }),
58        }
59    }
60
61    pub(crate) fn from_oauth2_error_response(
62        status: StatusCode,
63        response: OAuth2ErrorResponse,
64        retry_after: Option<u32>,
65    ) -> Self {
66        Self {
67            inner: Box::new(ErrorKind::OAuth2Error {
68                status,
69                response,
70                retry_after,
71            }),
72        }
73    }
74
75    /// Get the error response from API if caused by error status code.
76    #[must_use]
77    pub fn error_response(&self) -> Option<&ErrorResponse> {
78        match &*self.inner {
79            ErrorKind::ErrorResponse { response, .. } => Some(response),
80            _ => None,
81        }
82    }
83
84    /// Get the OAuth2 error response from API if caused by OAuth2 error response.
85    #[must_use]
86    pub fn oauth2_error_response(&self) -> Option<&OAuth2ErrorResponse> {
87        match &*self.inner {
88            ErrorKind::OAuth2Error { response, .. } => Some(response),
89            _ => None,
90        }
91    }
92
93    /// Get the HTTP status code if caused by error status code.
94    #[must_use]
95    pub fn status_code(&self) -> Option<StatusCode> {
96        match &*self.inner {
97            ErrorKind::RequestError(source) => source.status(),
98            ErrorKind::UnexpectedResponse { .. } => None,
99            ErrorKind::ErrorResponse { status, .. } | ErrorKind::OAuth2Error { status, .. } => {
100                Some(*status)
101            }
102        }
103    }
104
105    /// Get the retry delay hint on rate limited (HTTP 429) or server unavailability, if any.
106    ///
107    /// This is parsed from response header `Retry-After`.
108    /// See: <https://learn.microsoft.com/en-us/graph/throttling>
109    #[must_use]
110    pub fn retry_after(&self) -> Option<Duration> {
111        match &*self.inner {
112            ErrorKind::ErrorResponse { retry_after, .. }
113            | ErrorKind::OAuth2Error { retry_after, .. } => {
114                Some(Duration::from_secs((*retry_after)?.into()))
115            }
116            _ => None,
117        }
118    }
119}
120
121impl From<reqwest::Error> for Error {
122    fn from(source: reqwest::Error) -> Self {
123        Self {
124            inner: Box::new(ErrorKind::RequestError(source)),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use std::error::Error as _;
132
133    use super::*;
134
135    #[test]
136    fn error_source() {
137        let err = reqwest::blocking::get("urn:urn").unwrap_err();
138        let original_err_fmt = err.to_string();
139        let source_err_fmt = Error::from(err).source().unwrap().to_string();
140        assert_eq!(source_err_fmt, original_err_fmt);
141    }
142}