oauth2/
error.rs

1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Serialize};
3
4use std::error::Error;
5use std::fmt::{Debug, Display, Formatter};
6
7/// Server Error Response
8///
9/// See [Section 5.2](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2) of RFC 6749.
10/// This trait exists separately from the `StandardErrorResponse` struct
11/// to support customization by clients, such as supporting interoperability with
12/// non-standards-complaint OAuth2 providers.
13///
14/// The [`Display`] trait implementation for types implementing [`ErrorResponse`] should be a
15/// human-readable string suitable for printing (e.g., within a [`RequestTokenError`]).
16pub trait ErrorResponse: Debug + Display + DeserializeOwned + Serialize {}
17
18/// Error types enum.
19///
20/// NOTE: The serialization must return the `snake_case` representation of
21/// this error type. This value must match the error type from the relevant OAuth 2.0 standards
22/// (RFC 6749 or an extension).
23pub trait ErrorResponseType: Debug + DeserializeOwned + Serialize {}
24
25/// Error response returned by server after requesting an access token.
26///
27/// The fields in this structure are defined in
28/// [Section 5.2 of RFC 6749](https://tools.ietf.org/html/rfc6749#section-5.2). This
29/// trait is parameterized by a `ErrorResponseType` to support error types specific to future OAuth2
30/// authentication schemes and extensions.
31#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
32pub struct StandardErrorResponse<T: ErrorResponseType> {
33    #[serde(bound = "T: ErrorResponseType")]
34    pub(crate) error: T,
35    #[serde(default)]
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub(crate) error_description: Option<String>,
38    #[serde(default)]
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub(crate) error_uri: Option<String>,
41}
42
43impl<T: ErrorResponseType> StandardErrorResponse<T> {
44    /// Instantiate a new `ErrorResponse`.
45    ///
46    /// # Arguments
47    ///
48    /// * `error` - REQUIRED. A single ASCII error code deserialized to the generic parameter.
49    ///   `ErrorResponseType`.
50    /// * `error_description` - OPTIONAL. Human-readable ASCII text providing additional
51    ///   information, used to assist the client developer in understanding the error that
52    ///   occurred. Values for this parameter MUST NOT include characters outside the set
53    ///   `%x20-21 / %x23-5B / %x5D-7E`.
54    /// * `error_uri` - OPTIONAL. A URI identifying a human-readable web page with information
55    ///   about the error used to provide the client developer with additional information about
56    ///   the error. Values for the "error_uri" parameter MUST conform to the URI-reference
57    ///   syntax and thus MUST NOT include characters outside the set `%x21 / %x23-5B / %x5D-7E`.
58    pub fn new(error: T, error_description: Option<String>, error_uri: Option<String>) -> Self {
59        Self {
60            error,
61            error_description,
62            error_uri,
63        }
64    }
65
66    /// REQUIRED. A single ASCII error code deserialized to the generic parameter
67    /// `ErrorResponseType`.
68    pub fn error(&self) -> &T {
69        &self.error
70    }
71    /// OPTIONAL. Human-readable ASCII text providing additional information, used to assist
72    /// the client developer in understanding the error that occurred. Values for this
73    /// parameter MUST NOT include characters outside the set `%x20-21 / %x23-5B / %x5D-7E`.
74    pub fn error_description(&self) -> Option<&String> {
75        self.error_description.as_ref()
76    }
77    /// OPTIONAL. URI identifying a human-readable web page with information about the error,
78    /// used to provide the client developer with additional information about the error.
79    /// Values for the "error_uri" parameter MUST conform to the URI-reference syntax and
80    /// thus MUST NOT include characters outside the set `%x21 / %x23-5B / %x5D-7E`.
81    pub fn error_uri(&self) -> Option<&String> {
82        self.error_uri.as_ref()
83    }
84}
85
86impl<T> ErrorResponse for StandardErrorResponse<T> where T: ErrorResponseType + Display + 'static {}
87
88impl<TE> Display for StandardErrorResponse<TE>
89where
90    TE: ErrorResponseType + Display,
91{
92    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
93        let mut formatted = self.error().to_string();
94
95        if let Some(error_description) = self.error_description() {
96            formatted.push_str(": ");
97            formatted.push_str(error_description);
98        }
99
100        if let Some(error_uri) = self.error_uri() {
101            formatted.push_str(" (see ");
102            formatted.push_str(error_uri);
103            formatted.push(')');
104        }
105
106        write!(f, "{formatted}")
107    }
108}
109
110/// Error encountered while requesting access token.
111#[derive(Debug, thiserror::Error)]
112pub enum RequestTokenError<RE, T>
113where
114    RE: Error + 'static,
115    T: ErrorResponse + 'static,
116{
117    /// Error response returned by authorization server. Contains the parsed `ErrorResponse`
118    /// returned by the server.
119    #[error("Server returned error response: {0}")]
120    ServerResponse(T),
121    /// An error occurred while sending the request or receiving the response (e.g., network
122    /// connectivity failed).
123    #[error("Request failed")]
124    Request(#[from] RE),
125    /// Failed to parse server response. Parse errors may occur while parsing either successful
126    /// or error responses.
127    #[error("Failed to parse server response")]
128    Parse(
129        #[source] serde_path_to_error::Error<serde_json::error::Error>,
130        Vec<u8>,
131    ),
132    /// Some other type of error occurred (e.g., an unexpected server response).
133    #[error("Other error: {}", _0)]
134    Other(String),
135}
136
137#[cfg(test)]
138mod tests {
139    use crate::basic::{BasicErrorResponse, BasicErrorResponseType};
140
141    #[test]
142    fn test_error_response_serializer() {
143        assert_eq!(
144            "{\"error\":\"unauthorized_client\"}",
145            serde_json::to_string(&BasicErrorResponse::new(
146                BasicErrorResponseType::UnauthorizedClient,
147                None,
148                None,
149            ))
150            .unwrap(),
151        );
152
153        assert_eq!(
154            "{\
155         \"error\":\"invalid_client\",\
156         \"error_description\":\"Invalid client_id\",\
157         \"error_uri\":\"https://example.com/errors/invalid_client\"\
158         }",
159            serde_json::to_string(&BasicErrorResponse::new(
160                BasicErrorResponseType::InvalidClient,
161                Some("Invalid client_id".to_string()),
162                Some("https://example.com/errors/invalid_client".to_string()),
163            ))
164            .unwrap(),
165        );
166    }
167}