use std::sync::Arc;
use http::HeaderMap;
use reqwest::{Client, IntoUrl, RequestBuilder};
use serde::de::DeserializeOwned;
use tracing::debug;
use crate::{errors::ApiError, rate_limit::RateLimit, utils::IsJson, Auth};
#[derive(Copy, Clone, Debug)]
pub(crate) enum RequestMethod {
Get,
Put,
Delete,
}
pub(crate) struct ApiRequest {
auth: Arc<Auth>,
http: reqwest::Client,
#[allow(clippy::complexity)]
response_cb: Option<Box<dyn FnOnce(&HeaderMap) + 'static>>,
request_cb: Option<Box<dyn FnOnce(RequestBuilder) -> RequestBuilder + 'static>>,
}
impl Clone for ApiRequest {
fn clone(&self) -> Self {
let ApiRequest { auth, http, .. } = self;
ApiRequest {
auth: auth.clone(),
http: http.clone(),
response_cb: None,
request_cb: None,
}
}
}
impl ApiRequest {
pub fn new(auth: Arc<Auth>, http: Client) -> Self {
Self {
auth,
http,
response_cb: None,
request_cb: None,
}
}
pub fn response_cb(&mut self, response_cb: impl FnOnce(&HeaderMap) + 'static) {
self.response_cb = Some(Box::new(response_cb));
}
pub fn request_cb(
&mut self,
request_cb: impl FnOnce(RequestBuilder) -> RequestBuilder + 'static,
) {
self.request_cb = Some(Box::new(request_cb));
}
pub async fn get<D>(
&mut self,
url: impl IntoUrl,
is_auth: bool,
) -> Result<(RateLimit, D), ApiError>
where
D: DeserializeOwned,
{
self.api_request(url.into_url()?, RequestMethod::Get, is_auth)
.await
}
pub async fn delete<D>(
&mut self,
url: impl IntoUrl,
is_auth: bool,
) -> Result<(RateLimit, D), ApiError>
where
D: DeserializeOwned,
{
self.api_request(url.into_url()?, RequestMethod::Delete, is_auth)
.await
}
pub async fn put<D>(
&mut self,
url: impl IntoUrl,
is_auth: bool,
) -> Result<(RateLimit, D), ApiError>
where
D: DeserializeOwned,
{
self.api_request(url.into_url()?, RequestMethod::Put, is_auth)
.await
}
async fn api_request<D>(
&mut self,
url: impl IntoUrl,
method: RequestMethod,
is_auth: bool,
) -> Result<(RateLimit, D), ApiError>
where
D: DeserializeOwned,
{
let request = match method {
RequestMethod::Get => self.http.get(url.into_url()?),
RequestMethod::Delete => self.http.delete(url.into_url()?),
RequestMethod::Put => self.http.put(url.into_url()?),
};
let request = if is_auth {
request.bearer_auth(self.auth.access_token().secret())
} else {
request.bearer_auth(self.auth.app_token().secret())
};
let request = if let Some(cb) = self.request_cb.take() {
cb(request)
} else {
request
};
let response = request.send().await?;
let headers = response.headers();
let limit = RateLimit::new(headers);
if let Some(cb) = self.response_cb.take() {
cb(headers);
}
let status = response.status();
let text = response.text().await?;
debug!(status = status.as_u16(), response = text);
if !text.is_json() {
return Err(ApiError::ApiError {
status,
error: text,
});
}
let data = serde_json::from_str(&text)?;
Ok((limit.unwrap(), data))
}
}