oauth1_twitter/endpoints/
request_token.rs

1//! https://developer.twitter.com/en/docs/authentication/api-reference/request_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_request_token::AuthenticationRequestToken, consumer_key::ConsumerKey,
9};
10
11pub const URL: &str = "https://api.twitter.com/oauth/request_token";
12
13//
14#[derive(Debug, Clone)]
15pub struct RequestTokenEndpoint {
16    pub consumer_key: ConsumerKey,
17    pub oauth_callback: String,
18    pub x_auth_access_type: Option<String>,
19}
20impl RequestTokenEndpoint {
21    pub fn new(consumer_key: ConsumerKey, oauth_callback: impl AsRef<str>) -> Self {
22        Self {
23            consumer_key,
24            oauth_callback: oauth_callback.as_ref().into(),
25            x_auth_access_type: None,
26        }
27    }
28
29    pub fn with_x_auth_access_type(mut self, x_auth_access_type: impl AsRef<str>) -> Self {
30        self.x_auth_access_type = Some(x_auth_access_type.as_ref().into());
31        self
32    }
33}
34
35impl Endpoint for RequestTokenEndpoint {
36    type RenderRequestError = EndpointError;
37
38    type ParseResponseOutput = EndpointRet<RequestTokenResponseBody>;
39    type ParseResponseError = EndpointError;
40
41    fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
42        let query = RequestTokenRequestQuery {
43            oauth_callback: self.oauth_callback.to_owned(),
44            x_auth_access_type: self.x_auth_access_type.to_owned(),
45        };
46
47        let request_tmp = reqwest_oauth1::Client::new()
48            .post(URL)
49            .sign(self.consumer_key.secrets())
50            .query(&query)
51            .generate_signature()
52            .map_err(EndpointError::MakeReqwestRequestBuilderFailed)?
53            .build()
54            .map_err(EndpointError::MakeReqwestRequestFailed)?;
55
56        let mut request = Request::builder()
57            .method(request_tmp.method())
58            .uri(request_tmp.url().as_str())
59            .body(vec![])
60            .map_err(EndpointError::MakeRequestFailed)?;
61
62        let headers = request.headers_mut();
63        *headers = request_tmp.headers().to_owned();
64
65        Ok(request)
66    }
67
68    fn parse_response(
69        &self,
70        response: Response<Body>,
71    ) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
72        let status = response.status();
73
74        match status {
75            StatusCode::OK => Ok(EndpointRet::Ok(
76                serde_urlencoded::from_bytes::<RequestTokenResponseBody>(response.body())
77                    .map_err(EndpointError::DeResponseBodyOkFailed)?,
78            )),
79            status => match serde_json::from_slice(response.body()) {
80                Ok(fail_json) => Ok(EndpointRet::Other((status, Ok(fail_json)))),
81                Err(_) => Ok(EndpointRet::Other((
82                    status,
83                    Err(response.body().to_owned()),
84                ))),
85            },
86        }
87    }
88}
89
90//
91#[derive(Deserialize, Serialize, Debug, Clone)]
92pub struct RequestTokenRequestQuery {
93    pub oauth_callback: String,
94    pub x_auth_access_type: Option<String>,
95}
96
97//
98#[derive(Deserialize, Serialize, Debug, Clone)]
99pub struct RequestTokenResponseBody {
100    pub oauth_token: String,
101    pub oauth_token_secret: String,
102    pub oauth_callback_confirmed: bool,
103}
104
105impl RequestTokenResponseBody {
106    pub fn authentication_request_token(&self) -> AuthenticationRequestToken {
107        AuthenticationRequestToken::new(&self.oauth_token, &self.oauth_token_secret)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    use http_api_client_endpoint::http::Method;
116
117    #[test]
118    fn test_render_request() {
119        //
120        let req = RequestTokenEndpoint::new(
121            ConsumerKey::new("foo", "bar"),
122            "http://examplecallbackurl.local/auth.php",
123        )
124        .render_request()
125        .unwrap();
126        assert_eq!(req.method(), Method::POST);
127        assert_eq!(req.uri(), "https://api.twitter.com/oauth/request_token");
128        let req_header_authorization =
129            String::from_utf8_lossy(req.headers().get("Authorization").unwrap().as_bytes());
130        assert!(
131            req_header_authorization.starts_with(r#"OAuth oauth_callback="http%3A%2F%2Fexamplecallbackurl.local%2Fauth.php",oauth_consumer_key="foo""#)
132        );
133
134        //
135        let req = RequestTokenEndpoint::new(
136            ConsumerKey::new("foo", "bar"),
137            "http://examplecallbackurl.local/auth.php",
138        )
139        .with_x_auth_access_type("write")
140        .render_request()
141        .unwrap();
142        assert_eq!(req.method(), Method::POST);
143        assert_eq!(
144            req.uri(),
145            "https://api.twitter.com/oauth/request_token?x_auth_access_type=write"
146        );
147        assert!(
148            String::from_utf8_lossy(req.headers().get("Authorization").unwrap().as_bytes())
149                .starts_with(r#"OAuth oauth_callback="http%3A%2F%2Fexamplecallbackurl.local%2Fauth.php",oauth_consumer_key="foo""#)
150        );
151    }
152
153    #[test]
154    fn test_parse_response() {
155        //
156        let body = include_str!("../../tests/response_body_files/request_token.txt");
157        let res = Response::builder()
158            .status(StatusCode::OK)
159            .body(body.as_bytes().to_owned())
160            .unwrap();
161        let ret = RequestTokenEndpoint::new(
162            ConsumerKey::new("foo", "bar"),
163            "http://examplecallbackurl.local/auth.php",
164        )
165        .parse_response(res)
166        .unwrap();
167        match &ret {
168            EndpointRet::Ok(body) => {
169                assert_eq!(body.oauth_token, "zlgW3QAAAAAA2_NZAAABfxxxxxxk");
170                assert_eq!(body.oauth_token_secret, "pBYEQzdbyMqIcyDzyn0X7LDxxxxxxxxx");
171                assert!(body.oauth_callback_confirmed);
172            }
173            EndpointRet::Other(_) => panic!("{ret:?}"),
174        }
175
176        //
177        let body = include_str!("../../tests/response_body_files/request_token__400.json");
178        let res = Response::builder()
179            .status(StatusCode::BAD_REQUEST)
180            .body(body.as_bytes().to_owned())
181            .unwrap();
182        let ret = RequestTokenEndpoint::new(
183            ConsumerKey::new("foo", "bar"),
184            "http://examplecallbackurl.local/auth.php",
185        )
186        .parse_response(res)
187        .unwrap();
188        match &ret {
189            EndpointRet::Ok(_) => {
190                panic!("{ret:?}")
191            }
192            EndpointRet::Other((status_code, body)) => {
193                assert_eq!(status_code, &StatusCode::BAD_REQUEST);
194                assert_eq!(
195                    body.as_ref().unwrap().errors.first().map(|x| x.code),
196                    Some(215)
197                );
198            }
199        }
200    }
201}