1#![doc(html_root_url = "https://docs.rs/twitter-client/0.0.1")]
2
3pub mod auth;
4pub mod error;
5pub mod request;
6pub mod response;
7pub mod traits;
8
9mod util;
10
11pub use crate::error::{Error, ErrorCode};
12pub use crate::request::Request;
13pub use crate::response::Response;
14
15#[derive(Clone, Copy, Debug)]
16pub struct RateLimit {
17 pub limit: u64,
18 pub remaining: u64,
19 pub reset: u64,
20}
21
22impl RateLimit {
23 fn from_headers(headers: &http::HeaderMap) -> Option<Self> {
24 pub const RATE_LIMIT_LIMIT: &str = "x-rate-limit-limit";
25 pub const RATE_LIMIT_REMAINING: &str = "x-rate-limit-remaining";
26 pub const RATE_LIMIT_RESET: &str = "x-rate-limit-reset";
27
28 fn header(headers: &http::HeaderMap, name: &str) -> Option<u64> {
29 headers
30 .get(name)
31 .and_then(|value| atoi::atoi(value.as_bytes()))
32 }
33
34 Some(RateLimit {
35 limit: header(headers, RATE_LIMIT_LIMIT)?,
36 remaining: header(headers, RATE_LIMIT_REMAINING)?,
37 reset: header(headers, RATE_LIMIT_RESET)?,
38 })
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use std::convert::Infallible;
45
46 use hyper::Body;
47 use oauth_credentials::Token;
48 use tower::ServiceExt;
49
50 use crate::request::RawRequest;
51
52 use super::*;
53
54 #[tokio::test]
55 async fn parse_errors() {
56 #[derive(oauth::Request)]
57 struct Foo {
58 param: u32,
59 }
60
61 impl RawRequest for Foo {
62 fn method(&self) -> &http::Method {
63 &http::Method::GET
64 }
65
66 fn uri(&self) -> &'static str {
67 "https://api.twitter.com/1.1/test/foo.json"
68 }
69 }
70
71 let token = Token::from_parts("", "", "", "");
72
73 let (http, mut handle) = tower_test::mock::pair::<http::Request<Vec<u8>>, _>();
74 let mut http = http.map_err(|e| -> Infallible { panic!("{:?}", e) });
75
76 let res_fut =
77 tokio::spawn(Foo { param: 42 }.send_raw(&token, http.ready_and().await.unwrap()));
78
79 let (_req, tx) = handle.next_request().await.unwrap();
80 let payload = br#"{"errors":[{"code":104,"message":"You aren't allowed to add members to this list."}]}"#;
81 let res = http::Response::builder()
82 .status(http::StatusCode::FORBIDDEN)
83 .body(Body::from(&payload[..]))
84 .unwrap();
85 tx.send_response(res);
86
87 match res_fut.await.unwrap().unwrap_err() {
88 Error::Twitter(crate::error::TwitterErrors {
89 status: http::StatusCode::FORBIDDEN,
90 errors,
91 rate_limit: None,
92 }) => {
93 assert_eq!(errors.len(), 1);
94 assert_eq!(errors[0].code, 104);
95 assert_eq!(
96 errors[0].message,
97 "You aren't allowed to add members to this list.",
98 );
99 }
100 e => panic!("{:?}", e),
101 };
102 }
103}