oauth1_twitter/endpoints/
access_token.rs

1//! https://developer.twitter.com/en/docs/authentication/api-reference/access_token
2
3use http_api_client_endpoint::{http::StatusCode, Body, Endpoint, Request, Response};
4use serde::{Deserialize, Serialize};
5
6use super::common::{EndpointError, EndpointRet};
7use crate::objects::{
8    authentication_access_token::AuthenticationAccessToken,
9    authentication_request_token::AuthenticationRequestToken, consumer_key::ConsumerKey,
10};
11
12pub const URL: &str = "https://api.twitter.com/oauth/access_token";
13
14//
15#[derive(Debug, Clone)]
16pub struct AccessTokenEndpoint {
17    pub consumer_key: ConsumerKey,
18    pub authentication_request_token: AuthenticationRequestToken,
19    pub oauth_verifier: String,
20}
21impl AccessTokenEndpoint {
22    pub fn new(
23        consumer_key: ConsumerKey,
24        authentication_request_token: AuthenticationRequestToken,
25        oauth_verifier: impl AsRef<str>,
26    ) -> Self {
27        Self {
28            consumer_key,
29            authentication_request_token,
30            oauth_verifier: oauth_verifier.as_ref().into(),
31        }
32    }
33}
34
35impl Endpoint for AccessTokenEndpoint {
36    type RenderRequestError = EndpointError;
37
38    type ParseResponseOutput = EndpointRet<AccessTokenResponseBody>;
39    type ParseResponseError = EndpointError;
40
41    fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
42        let query = AccessTokenRequestQuery {
43            oauth_verifier: self.oauth_verifier.to_owned(),
44        };
45
46        let request_tmp = reqwest_oauth1::Client::new()
47            .post(URL)
48            .sign(
49                self.consumer_key
50                    .secrets_with_request_token(&self.authentication_request_token),
51            )
52            .query(&query)
53            .generate_signature()
54            .map_err(EndpointError::MakeReqwestRequestBuilderFailed)?
55            .build()
56            .map_err(EndpointError::MakeReqwestRequestFailed)?;
57
58        let mut request = Request::builder()
59            .method(request_tmp.method())
60            .uri(request_tmp.url().as_str())
61            .body(vec![])
62            .map_err(EndpointError::MakeRequestFailed)?;
63
64        let headers = request.headers_mut();
65        *headers = request_tmp.headers().to_owned();
66
67        Ok(request)
68    }
69
70    fn parse_response(
71        &self,
72        response: Response<Body>,
73    ) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
74        let status = response.status();
75
76        match status {
77            StatusCode::OK => Ok(EndpointRet::Ok(
78                serde_urlencoded::from_bytes::<AccessTokenResponseBody>(response.body())
79                    .map_err(EndpointError::DeResponseBodyOkFailed)?,
80            )),
81            status => match serde_json::from_slice(response.body()) {
82                Ok(fail_json) => Ok(EndpointRet::Other((status, Ok(fail_json)))),
83                Err(_) => Ok(EndpointRet::Other((
84                    status,
85                    Err(response.body().to_owned()),
86                ))),
87            },
88        }
89    }
90}
91
92//
93#[derive(Deserialize, Serialize, Debug, Clone)]
94pub struct AccessTokenRequestQuery {
95    pub oauth_verifier: String,
96}
97
98//
99#[derive(Deserialize, Serialize, Debug, Clone)]
100pub struct AccessTokenResponseBody {
101    pub oauth_token: String,
102    pub oauth_token_secret: String,
103    pub user_id: u64,
104    pub screen_name: String,
105}
106
107impl AccessTokenResponseBody {
108    pub fn authentication_access_token(&self) -> AuthenticationAccessToken {
109        AuthenticationAccessToken::new(&self.oauth_token, &self.oauth_token_secret)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    use http_api_client_endpoint::http::Method;
118
119    #[test]
120    fn test_render_request() {
121        //
122        let req = AccessTokenEndpoint::new(
123            ConsumerKey::new("foo", "bar"),
124            AuthenticationRequestToken::new("aaa", "xxx"),
125            "bbb",
126        )
127        .render_request()
128        .unwrap();
129        assert_eq!(req.method(), Method::POST);
130        assert_eq!(req.uri(), "https://api.twitter.com/oauth/access_token");
131        let req_header_authorization =
132            String::from_utf8_lossy(req.headers().get("Authorization").unwrap().as_bytes());
133        assert!(req_header_authorization.starts_with(r#"OAuth oauth_consumer_key="foo""#));
134        assert!(req_header_authorization.contains(r#"oauth_verifier="bbb""#));
135    }
136
137    #[test]
138    fn test_parse_response() {
139        //
140        let body = include_str!("../../tests/response_body_files/access_token.txt");
141        let res = Response::builder()
142            .status(StatusCode::OK)
143            .body(body.as_bytes().to_owned())
144            .unwrap();
145        let ret = AccessTokenEndpoint::new(
146            ConsumerKey::new("foo", "bar"),
147            AuthenticationRequestToken::new("aaa", "xxx"),
148            "bbb",
149        )
150        .parse_response(res)
151        .unwrap();
152        match &ret {
153            EndpointRet::Ok(body) => {
154                assert_eq!(
155                    body.oauth_token,
156                    "62532xx-eWudHldSbIaelX7swmsiHImEL4KinwaGloxxxxxx"
157                );
158                assert_eq!(
159                    body.oauth_token_secret,
160                    "2EEfA6BG5ly3sR3XjE0IBSnlQu4ZrUzPiYxxxxxx"
161                );
162                assert_eq!(body.user_id, 6253282);
163                assert_eq!(body.screen_name, "twitterapi");
164            }
165            EndpointRet::Other(_) => panic!("{ret:?}"),
166        }
167    }
168}