use crate::error::Error::{self, *};
use crate::error::{Result, TwitterErrors};
use hyper::client::{HttpConnector, ResponseFuture};
use hyper::{self, Body, Request};
use serde::{de::DeserializeOwned, Deserialize};
use std::convert::TryFrom;
use super::Headers;
const X_RATE_LIMIT_LIMIT: &str = "X-Rate-Limit-Limit";
const X_RATE_LIMIT_REMAINING: &str = "X-Rate-Limit-Remaining";
const X_RATE_LIMIT_RESET: &str = "X-Rate-Limit-Reset";
fn rate_limit(headers: &Headers, header: &'static str) -> Result<Option<i32>> {
let val = headers.get(header);
if let Some(val) = val {
let val = val.to_str()?.parse::<i32>()?;
Ok(Some(val))
} else {
Ok(None)
}
}
fn rate_limit_limit(headers: &Headers) -> Result<Option<i32>> {
rate_limit(headers, X_RATE_LIMIT_LIMIT)
}
fn rate_limit_remaining(headers: &Headers) -> Result<Option<i32>> {
rate_limit(headers, X_RATE_LIMIT_REMAINING)
}
fn rate_limit_reset(headers: &Headers) -> Result<Option<i32>> {
rate_limit(headers, X_RATE_LIMIT_RESET)
}
#[derive(
Debug, Deserialize, derive_more::Constructor, derive_more::Deref, derive_more::DerefMut,
)]
pub struct Response<T> {
#[serde(flatten)]
pub rate_limit_status: RateLimit,
#[deref]
#[deref_mut]
#[serde(default)]
pub response: T,
}
impl<T> Response<T> {
pub fn map<F, U>(src: Response<T>, fun: F) -> Response<U>
where
F: FnOnce(T) -> U,
{
Response {
rate_limit_status: src.rate_limit_status,
response: fun(src.response),
}
}
pub fn try_map<F, U, E>(src: Response<T>, fun: F) -> std::result::Result<Response<U>, E>
where
F: FnOnce(T) -> std::result::Result<U, E>,
{
Ok(Response {
rate_limit_status: src.rate_limit_status,
response: fun(src.response)?,
})
}
pub fn into<U>(src: Self) -> Response<U>
where
T: Into<U>,
{
Response {
rate_limit_status: src.rate_limit_status,
response: src.response.into(),
}
}
}
impl<T: IntoIterator> IntoIterator for Response<T> {
type IntoIter = ResponseIter<T::IntoIter>;
type Item = Response<T::Item>;
fn into_iter(self) -> Self::IntoIter {
ResponseIter {
it: Response::map(self, |it| it.into_iter()),
}
}
}
pub struct ResponseIter<T> {
it: Response<T>,
}
impl<T: Iterator> Iterator for ResponseIter<T> {
type Item = Response<T::Item>;
fn next(&mut self) -> Option<Self::Item> {
Some(Response {
rate_limit_status: self.it.rate_limit_status,
response: self.it.response.next()?,
})
}
}
#[cfg(not(any(feature = "native_tls", feature = "rustls", feature = "rustls_webpki")))]
compile_error!(
"Crate `egg_mode` must be compiled with exactly one of the three \
feature flags `native_tls`, `rustls` or `rustls_webpki` enabled, you attempted to \
compile `egg_mode` with none of them enabled"
);
#[cfg(any(
all(
feature = "native_tls",
any(feature = "rustls", feature = "rustls_webpki")
),
all(
feature = "rustls",
any(feature = "native_tls", feature = "rustls_webpki")
),
all(
feature = "rustls_webpki",
any(feature = "native_tls", feature = "rustls")
),
))]
compile_error!(
"features `egg_mode/native_tls`, `egg_mode/rustls` and \
`egg_mode/rustls_webpki` are mutually exclusive, you attempted to compile `egg_mode` \
with more than one of these feature flags enabled at the same time"
);
#[cfg(feature = "native_tls")]
fn new_https_connector() -> hyper_tls::HttpsConnector<HttpConnector> {
hyper_tls::HttpsConnector::new()
}
#[cfg(feature = "rustls")]
fn new_https_connector() -> hyper_rustls::HttpsConnector<HttpConnector> {
hyper_rustls::HttpsConnector::with_native_roots()
}
#[cfg(feature = "rustls_webpki")]
fn new_https_connector() -> hyper_rustls::HttpsConnector<HttpConnector> {
hyper_rustls::HttpsConnector::with_webpki_roots()
}
pub fn get_response(request: Request<Body>) -> ResponseFuture {
let connector = new_https_connector();
let client = hyper::Client::builder().build(connector);
client.request(request)
}
pub async fn raw_request(request: Request<Body>) -> Result<(Headers, Vec<u8>)> {
let connector = new_https_connector();
let client = hyper::Client::builder().build(connector);
let resp = client.request(request).await?;
let (parts, body) = resp.into_parts();
let body: Vec<_> = hyper::body::to_bytes(body).await?.to_vec();
if let Ok(errors) = serde_json::from_slice::<TwitterErrors>(&body) {
if errors.errors.iter().any(|e| e.code == 88)
&& parts.headers.contains_key(X_RATE_LIMIT_RESET)
{
return Err(RateLimit(rate_limit_reset(&parts.headers)?.unwrap()));
} else {
return Err(TwitterError(parts.headers, errors));
}
}
if !parts.status.is_success() {
return Err(BadStatus(parts.status));
}
Ok((parts.headers, body))
}
pub async fn request_with_empty_response(request: Request<Body>) -> Result<Response<()>> {
let (headers, _) = raw_request(request).await?;
let rate_limit_status = RateLimit::try_from(&headers)?;
Ok(Response {
rate_limit_status,
response: (),
})
}
pub async fn request_with_json_response<T: DeserializeOwned>(
request: Request<Body>,
) -> Result<Response<T>> {
let (headers, body) = raw_request(request).await?;
let response = serde_json::from_slice(&body)?;
let rate_limit_status = RateLimit::try_from(&headers)?;
Ok(Response {
rate_limit_status,
response,
})
}
#[derive(Copy, Clone, Debug, Deserialize)]
pub struct RateLimit {
pub limit: i32,
pub remaining: i32,
pub reset: i32,
}
impl TryFrom<&Headers> for RateLimit {
type Error = Error;
fn try_from(headers: &Headers) -> Result<Self> {
Ok(Self {
limit: rate_limit_limit(headers)?.unwrap_or(-1),
remaining: rate_limit_remaining(headers)?.unwrap_or(-1),
reset: rate_limit_reset(headers)?.unwrap_or(-1),
})
}
}