oauth2_zoho/extensions/
internal_oauth_user_info_endpoint.rs

1// [Using the Access token to make API requests](https://www.zoho.com/accounts/protocol/oauth/use-access-token.html)
2// [Zoho API: Get the user that is making the request](https://stackoverflow.com/a/54449818/918930)
3
4use oauth2_client::re_exports::{
5    http::{
6        header::{ACCEPT, AUTHORIZATION},
7        StatusCode,
8    },
9    serde_json, thiserror, Body, Deserialize, Endpoint, HttpError, Request, Response,
10    SerdeJsonError, Serialize, MIME_APPLICATION_JSON,
11};
12
13pub const URL: &str = "https://accounts.zoho.com/oauth/user/info";
14
15//
16#[derive(Debug, Clone)]
17pub struct OauthUserInfoEndpoint {
18    access_token: String,
19}
20impl OauthUserInfoEndpoint {
21    pub fn new(access_token: impl AsRef<str>) -> Self {
22        Self {
23            access_token: access_token.as_ref().to_owned(),
24        }
25    }
26}
27
28impl Endpoint for OauthUserInfoEndpoint {
29    type RenderRequestError = OauthUserInfoEndpointError;
30
31    type ParseResponseOutput = OauthUserInfoResponseBodyOkJson;
32    type ParseResponseError = OauthUserInfoEndpointError;
33
34    fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
35        let request = Request::builder()
36            .uri(URL)
37            // Or "Zoho-oauthtoken {}"
38            .header(AUTHORIZATION, format!("Bearer {}", &self.access_token))
39            .header(ACCEPT, MIME_APPLICATION_JSON)
40            .body(vec![])
41            .map_err(OauthUserInfoEndpointError::MakeRequestFailed)?;
42
43        Ok(request)
44    }
45
46    fn parse_response(
47        &self,
48        response: Response<Body>,
49    ) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
50        #[allow(clippy::collapsible_else_if)]
51        if response.status().is_success() {
52            Ok(
53                serde_json::from_slice::<OauthUserInfoResponseBodyOkJson>(response.body())
54                    .map_err(OauthUserInfoEndpointError::DeResponseBodyOkJsonFailed)?,
55            )
56        } else {
57            if let Ok(err_json) =
58                serde_json::from_slice::<OauthUserInfoResponseBodyErrJson>(response.body())
59            {
60                Err(OauthUserInfoEndpointError::ResponseBodyError(
61                    response.status(),
62                    Ok(err_json),
63                ))
64            } else {
65                Err(OauthUserInfoEndpointError::ResponseBodyError(
66                    response.status(),
67                    Err(String::from_utf8_lossy(response.body()).to_string()),
68                ))
69            }
70        }
71    }
72}
73
74#[derive(Deserialize, Serialize, Debug, Clone)]
75pub struct OauthUserInfoResponseBodyOkJson {
76    #[serde(rename = "First_Name")]
77    pub first_name: String,
78    #[serde(rename = "Email")]
79    pub email: String,
80    #[serde(rename = "Last_Name")]
81    pub last_name: String,
82    #[serde(rename = "Display_Name")]
83    pub display_name: String,
84    #[serde(rename = "ZUID")]
85    pub zuid: u64,
86}
87
88#[derive(Deserialize, Serialize, Debug, Clone)]
89pub struct OauthUserInfoResponseBodyErrJson {
90    pub response: String,
91    pub cause: String,
92}
93
94impl core::fmt::Display for OauthUserInfoResponseBodyErrJson {
95    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        write!(f, "{self:?}")
97    }
98}
99impl std::error::Error for OauthUserInfoResponseBodyErrJson {}
100
101#[derive(thiserror::Error, Debug)]
102pub enum OauthUserInfoEndpointError {
103    //
104    #[error("MakeRequestFailed {0}")]
105    MakeRequestFailed(HttpError),
106    //
107    #[error("DeResponseBodyOkJsonFailed {0}")]
108    DeResponseBodyOkJsonFailed(SerdeJsonError),
109    //
110    #[error("ResponseBodyError {0} {1:?}")]
111    ResponseBodyError(StatusCode, Result<OauthUserInfoResponseBodyErrJson, String>),
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn de_response_body() {
120        //
121        match serde_json::from_str::<OauthUserInfoResponseBodyOkJson>(include_str!(
122            "../../tests/response_body_json_files/oauth_user_info.json"
123        )) {
124            Ok(ok_json) => {
125                assert_eq!(ok_json.zuid, 795542386);
126            }
127            Err(err) => panic!("{err}"),
128        }
129
130        //
131        /*
132        When both AaaServer.profile.READ scope and profile scope are authorized.
133        401
134        */
135        match serde_json::from_str::<OauthUserInfoResponseBodyErrJson>(include_str!(
136            "../../tests/response_body_json_files/oauth_user_info_err.json"
137        )) {
138            Ok(err_json) => {
139                assert_eq!(err_json.cause, "INVALID_OAUTHSCOPE");
140            }
141            Err(err) => panic!("{err}"),
142        }
143    }
144}