pub mod auth;
pub mod error;
pub mod request;
pub mod response;
pub mod traits;
mod util;
pub use crate::error::{Error, ErrorCode};
pub use crate::request::Request;
pub use crate::response::Response;
use http::header::{HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use http::{Method, Uri};
use oauth_credentials::Token;
use serde::de;
use crate::response::ResponseFuture;
use crate::traits::HttpService;
#[derive(Clone, Copy, Debug)]
pub struct RateLimit {
pub limit: u64,
pub remaining: u64,
pub reset: u64,
}
pub trait Request: oauth::Request {
type Data: de::DeserializeOwned;
const METHOD: Method;
const URI: &'static str;
fn send<C, T, S, B>(
&self,
token: &Token<C, T>,
http: &mut S,
) -> ResponseFuture<Self::Data, S::Future>
where
C: AsRef<str>,
T: AsRef<str>,
S: HttpService<B>,
B: From<Vec<u8>>,
{
let req = prepare_request(&Self::METHOD, Self::URI, self, token.as_ref());
ResponseFuture::new(req.map(Into::into), http)
}
}
impl RateLimit {
fn from_headers(headers: &http::HeaderMap) -> Option<Self> {
pub const RATE_LIMIT_LIMIT: &str = "x-rate-limit-limit";
pub const RATE_LIMIT_REMAINING: &str = "x-rate-limit-remaining";
pub const RATE_LIMIT_RESET: &str = "x-rate-limit-reset";
fn header(headers: &http::HeaderMap, name: &str) -> Option<u64> {
headers
.get(name)
.and_then(|value| atoi::atoi(value.as_bytes()))
}
Some(RateLimit {
limit: header(headers, RATE_LIMIT_LIMIT)?,
remaining: header(headers, RATE_LIMIT_REMAINING)?,
reset: header(headers, RATE_LIMIT_RESET)?,
})
}
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use hyper::Body;
use oauth_credentials::Token;
use tower::ServiceExt;
use crate::request::RawRequest;
use super::*;
#[tokio::test]
async fn parse_errors() {
#[derive(oauth::Request)]
struct Foo {
param: u32,
}
impl RawRequest for Foo {
fn method(&self) -> &http::Method {
&http::Method::GET
}
fn uri(&self) -> &'static str {
"https://api.twitter.com/1.1/test/foo.json"
}
}
let token = Token::from_parts("", "", "", "");
let (http, mut handle) = tower_test::mock::pair::<http::Request<Vec<u8>>, _>();
let mut http = http.map_err(|e| -> Infallible { panic!("{:?}", e) });
let res_fut =
tokio::spawn(Foo { param: 42 }.send_raw(&token, http.ready_and().await.unwrap()));
let (_req, tx) = handle.next_request().await.unwrap();
let payload = br#"{"errors":[{"code":104,"message":"You aren't allowed to add members to this list."}]}"#;
let res = http::Response::builder()
.status(http::StatusCode::FORBIDDEN)
.body(Body::from(&payload[..]))
.unwrap();
tx.send_response(res);
match res_fut.await.unwrap().unwrap_err() {
Error::Twitter(crate::error::TwitterErrors {
status: http::StatusCode::FORBIDDEN,
errors,
rate_limit: None,
}) => {
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, 104);
assert_eq!(
errors[0].message,
"You aren't allowed to add members to this list.",
);
}
e => panic!("{:?}", e),
};
}
}