oauth2_zoho/extensions/
internal_oauth_user_info_endpoint.rs1use 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#[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 .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 #[error("MakeRequestFailed {0}")]
105 MakeRequestFailed(HttpError),
106 #[error("DeResponseBodyOkJsonFailed {0}")]
108 DeResponseBodyOkJsonFailed(SerdeJsonError),
109 #[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 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 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}