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}