use crate::error::APIError;
use crate::error::Result;
use crate::throttler::{RateLimitStore, RequestType};
use crate::APIWrapper;
use reqwest::{Response, StatusCode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tokio::time::Duration;
#[derive(Deserialize)]
pub struct APIResponse<D> {
pub result: String,
pub data: Option<D>,
pub error: Option<APIError>,
}
impl<D> APIResponse<D> {
pub fn is_success(&self) -> bool {
self.result == "success"
}
pub fn data(self) -> D {
self.data.expect("no data present")
}
pub fn error(self) -> APIError {
self.error.expect("no error present")
}
pub fn as_result(self) -> Result<D> {
if self.is_success() {
Ok(self.data())
} else {
Err(self.error())
}
}
}
pub async fn get<D>(wrapper: &APIWrapper, endpoint: &str) -> Result<APIResponse<D>>
where
D: DeserializeOwned,
{
loop {
loop {
match crate::throttler::stall_for(&wrapper.rate_limit_store, RequestType::READ) {
0 => break,
stall_for => tokio::time::sleep(Duration::from_millis(stall_for)).await,
};
}
let response = wrapper.http_client.get(endpoint).send().await?;
if !did_hit_limit(&wrapper.rate_limit_store, &response, RequestType::READ) {
return Ok(response.json().await?);
}
}
}
pub async fn post<D, B>(wrapper: &APIWrapper, endpoint: &str, body: &B) -> Result<APIResponse<D>>
where
D: DeserializeOwned,
B: Serialize,
{
loop {
loop {
match crate::throttler::stall_for(&wrapper.rate_limit_store, RequestType::WRITE) {
0 => break,
stall_for => tokio::time::sleep(Duration::from_millis(stall_for)).await,
};
}
let response = wrapper.http_client.post(endpoint).json(body).send().await?;
if !did_hit_limit(&wrapper.rate_limit_store, &response, RequestType::WRITE) {
return Ok(response.json().await?);
}
}
}
pub async fn patch<D, B>(wrapper: &APIWrapper, endpoint: &str, body: &B) -> Result<APIResponse<D>>
where
D: DeserializeOwned,
B: Serialize,
{
loop {
loop {
match crate::throttler::stall_for(&wrapper.rate_limit_store, RequestType::WRITE) {
0 => break,
stall_for => tokio::time::sleep(Duration::from_millis(stall_for)).await,
};
}
let response = wrapper.http_client.post(endpoint).json(body).send().await?;
if !did_hit_limit(&wrapper.rate_limit_store, &response, RequestType::WRITE) {
return Ok(response.json().await?);
}
}
}
pub async fn delete<D>(wrapper: &APIWrapper, endpoint: &str) -> Result<APIResponse<D>>
where
D: DeserializeOwned,
{
loop {
loop {
match crate::throttler::stall_for(&wrapper.rate_limit_store, RequestType::WRITE) {
0 => break,
stall_for => tokio::time::sleep(Duration::from_millis(stall_for)).await,
};
}
let response = wrapper.http_client.delete(endpoint).send().await?;
if !did_hit_limit(&wrapper.rate_limit_store, &response, RequestType::WRITE) {
return response.json().await?;
}
}
}
fn did_hit_limit(store: &RateLimitStore, response: &Response, request_type: RequestType) -> bool {
if response.status() != StatusCode::TOO_MANY_REQUESTS {
match &request_type {
RequestType::READ => store.reset_read(),
RequestType::WRITE => store.reset_write(),
};
return false;
}
let retry = response.headers().get("Retry-After").expect("no retry-after header present");
let retry: u64 = retry.to_str().expect("non-ascii characters present").parse().expect("not a valid u64 int");
match &request_type {
RequestType::READ => store.store_read(retry),
RequestType::WRITE => store.store_write(retry),
};
true
}