oauth2_wechat/extensions/
internal_sns_userinfo_endpoint.rs

1use oauth2_client::re_exports::{
2    http::header::ACCEPT, serde_json, thiserror, Body, Deserialize, Endpoint, HttpError, Request,
3    Response, SerdeJsonError, Serialize, Value, MIME_APPLICATION_JSON,
4};
5
6pub const URL: &str = "https://api.weixin.qq.com/sns/userinfo";
7
8//
9#[derive(Debug, Clone)]
10pub struct SnsUserinfoEndpoint {
11    access_token: String,
12    openid: String,
13}
14impl SnsUserinfoEndpoint {
15    pub fn new(access_token: impl AsRef<str>, openid: impl AsRef<str>) -> Self {
16        Self {
17            access_token: access_token.as_ref().to_owned(),
18            openid: openid.as_ref().to_owned(),
19        }
20    }
21}
22
23impl Endpoint for SnsUserinfoEndpoint {
24    type RenderRequestError = SnsUserinfoEndpointError;
25
26    type ParseResponseOutput = SnsUserinfo;
27    type ParseResponseError = SnsUserinfoEndpointError;
28
29    fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
30        let url = format!(
31            "{}?access_token={}&openid={}&lang=zh_CN",
32            URL, self.access_token, self.openid
33        );
34
35        let request = Request::builder()
36            .uri(url)
37            .header(ACCEPT, MIME_APPLICATION_JSON)
38            .body(vec![])
39            .map_err(SnsUserinfoEndpointError::MakeRequestFailed)?;
40
41        Ok(request)
42    }
43
44    fn parse_response(
45        &self,
46        response: Response<Body>,
47    ) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
48        let body = serde_json::from_slice::<SnsUserinfo>(response.body())
49            .map_err(SnsUserinfoEndpointError::DeResponseBodyFailed)?;
50
51        Ok(body)
52    }
53}
54
55#[derive(Deserialize, Serialize, Debug, Clone)]
56pub struct SnsUserinfo {
57    pub openid: String,
58    pub nickname: Option<String>,
59    pub sex: Option<usize>,
60    pub province: Option<String>,
61    pub city: Option<String>,
62    pub country: Option<String>,
63    pub headimgurl: Option<String>,
64    pub privilege: Option<Value>,
65    pub unionid: Option<String>,
66}
67
68#[derive(thiserror::Error, Debug)]
69pub enum SnsUserinfoEndpointError {
70    #[error("MakeRequestFailed {0}")]
71    MakeRequestFailed(HttpError),
72    //
73    #[error("DeResponseBodyFailed {0}")]
74    DeResponseBodyFailed(SerdeJsonError),
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn de_sns_userinfo() {
83        match serde_json::from_str::<SnsUserinfo>(include_str!(
84            "../../tests/response_body_json_files/sns_userinfo.json"
85        )) {
86            Ok(user) => {
87                assert_eq!(user.openid, "OPENID");
88            }
89            Err(err) => panic!("{err}"),
90        }
91    }
92}