oauth2_core/
access_token_response.rs

1//! https://datatracker.ietf.org/doc/html/rfc6749#section-5
2
3use mime::Mime;
4use serde::{Deserialize, Serialize};
5use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
6use serde_json::{Map, Value};
7use url::Url;
8
9use crate::types::{AccessTokenType, IdToken, Scope, ScopeFromStrError, ScopeParameter};
10
11pub const CONTENT_TYPE: Mime = mime::APPLICATION_JSON;
12pub const GENERAL_ERROR_BODY_KEY_ERROR: &str = "error";
13
14//
15//
16//
17#[derive(Serialize, Deserialize, Debug, Clone)]
18pub struct SuccessfulBody<SCOPE>
19where
20    SCOPE: Scope,
21{
22    pub access_token: String,
23    // e.g. instagram {"access_token":"xxx", "user_id":0}
24    #[serde(default)]
25    pub token_type: AccessTokenType,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub expires_in: Option<usize>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub refresh_token: Option<String>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    // e.g. bitbucket {"scopes": "account repository"}
32    // e.g. linode {"scope": "account:read_only linodes:read_only", "scopes": "account:read_only linodes:read_only"}
33    #[serde(alias = "scopes")]
34    pub scope: Option<ScopeParameter<SCOPE>>,
35
36    // OIDC
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub id_token: Option<IdToken>,
39
40    #[serde(flatten, skip_serializing_if = "Option::is_none")]
41    pub _extra: Option<Map<String, Value>>,
42}
43impl<SCOPE> SuccessfulBody<SCOPE>
44where
45    SCOPE: Scope,
46{
47    pub fn new(
48        access_token: String,
49        token_type: AccessTokenType,
50        expires_in: Option<usize>,
51        refresh_token: Option<String>,
52        scope: Option<ScopeParameter<SCOPE>>,
53    ) -> Self {
54        Self {
55            access_token,
56            token_type,
57            expires_in,
58            refresh_token,
59            scope,
60            id_token: None,
61            _extra: None,
62        }
63    }
64
65    pub fn set_extra(&mut self, extra: Map<String, Value>) {
66        self._extra = Some(extra);
67    }
68    pub fn extra(&self) -> Option<&Map<String, Value>> {
69        self._extra.as_ref()
70    }
71
72    pub fn try_from_t_with_string(
73        body: &SuccessfulBody<String>,
74    ) -> Result<Self, ScopeFromStrError> {
75        let scope = if let Some(x) = &body.scope {
76            Some(ScopeParameter::<SCOPE>::try_from_t_with_string(x)?)
77        } else {
78            None
79        };
80
81        let mut this = Self::new(
82            body.access_token.to_owned(),
83            body.token_type.to_owned(),
84            body.expires_in.to_owned(),
85            body.refresh_token.to_owned(),
86            scope,
87        );
88        if let Some(extra) = body.extra() {
89            this.set_extra(extra.to_owned());
90        }
91        Ok(this)
92    }
93}
94
95impl<SCOPE> From<&SuccessfulBody<SCOPE>> for SuccessfulBody<String>
96where
97    SCOPE: Scope,
98{
99    fn from(body: &SuccessfulBody<SCOPE>) -> Self {
100        let mut this = Self::new(
101            body.access_token.to_owned(),
102            body.token_type.to_owned(),
103            body.expires_in.to_owned(),
104            body.refresh_token.to_owned(),
105            body.scope
106                .to_owned()
107                .map(|x| ScopeParameter::<String>::from(&x)),
108        );
109        if let Some(extra) = body.extra() {
110            this.set_extra(extra.to_owned());
111        }
112        this
113    }
114}
115
116//
117//
118//
119#[derive(Serialize, Deserialize, Debug, Clone)]
120pub struct ErrorBody {
121    // e.g. twitch {"status":400, "message":"Invalid authorization code"}
122    #[serde(default)]
123    pub error: ErrorBodyError,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub error_description: Option<String>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub error_uri: Option<Url>,
128
129    #[serde(flatten, skip_serializing_if = "Option::is_none")]
130    _extra: Option<Map<String, Value>>,
131}
132impl ErrorBody {
133    pub fn new(
134        error: ErrorBodyError,
135        error_description: Option<String>,
136        error_uri: Option<Url>,
137    ) -> Self {
138        Self {
139            error,
140            error_description,
141            error_uri,
142            _extra: None,
143        }
144    }
145
146    pub fn set_extra(&mut self, extra: Map<String, Value>) {
147        self._extra = Some(extra);
148    }
149    pub fn extra(&self) -> Option<&Map<String, Value>> {
150        self._extra.as_ref()
151    }
152}
153
154#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, Clone, PartialEq, Eq)]
155#[serde(rename_all = "snake_case")]
156pub enum ErrorBodyError {
157    //
158    //
159    //
160    /// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
161    InvalidRequest,
162    /// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
163    InvalidClient,
164    /// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
165    InvalidGrant,
166    /// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
167    UnauthorizedClient,
168    /// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
169    UnsupportedGrantType,
170    /// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
171    InvalidScope,
172    //
173    //
174    //
175    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
176    UnsupportedResponseType,
177    //
178    //
179    //
180    /// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
181    AuthorizationPending,
182    /// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
183    SlowDown,
184    /// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
185    ExpiredToken,
186    //
187    //
188    //
189    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
190    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
191    /// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
192    AccessDenied,
193    //
194    //
195    //
196    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
197    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
198    ServerError,
199    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
200    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
201    TemporarilyUnavailable,
202    //
203    //
204    //
205    #[serde(other)]
206    Other(String),
207}
208impl Default for ErrorBodyError {
209    fn default() -> Self {
210        Self::Other("".to_owned())
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_ser_de_error_body() {
220        let body_str = r#"
221        {
222            "error": "invalid_scope"
223        }
224        "#;
225        match serde_json::from_str::<ErrorBody>(body_str) {
226            Ok(body) => {
227                assert_eq!(body.error, ErrorBodyError::InvalidScope);
228            }
229            Err(err) => panic!("{err}"),
230        }
231    }
232}
233
234#[cfg(test)]
235mod tests_with_authorization_code_grant {
236    use super::*;
237
238    #[test]
239    fn test_ser_de_error_body() {
240        let body_str = r#"
241        {
242            "error": "authorization_pending"
243        }
244        "#;
245        match serde_json::from_str::<ErrorBody>(body_str) {
246            Ok(body) => {
247                assert_eq!(body.error, ErrorBodyError::AuthorizationPending);
248            }
249            Err(err) => panic!("{err}"),
250        }
251    }
252}