oauth1_twitter/endpoints/
request_token.rs1use 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#[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#[derive(Deserialize, Serialize, Debug, Clone)]
92pub struct RequestTokenRequestQuery {
93 pub oauth_callback: String,
94 pub x_auth_access_type: Option<String>,
95}
96
97#[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 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 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 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 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}