pub mod data;
pub mod error;
pub mod helpers;
pub mod sort;
pub(crate) mod http;
pub(crate) mod throttler;
use data::metrics::MetricsSnapshot;
use error::{APIError, Result};
use helpers::alerts::AlertsHelper;
use helpers::resources::ResourceHelper;
use helpers::conversations::ConversationsHelper;
use helpers::members::MembersHelper;
use helpers::threads::ThreadsHelper;
use throttler::RateLimitStore;
use sort::SortOptions;
use std::time::{Duration, Instant};
use reqwest::{header::HeaderMap, Client, ClientBuilder};
use serde::{de::DeserializeOwned, Serialize, Deserialize};
pub(crate) const BASE_URL: &str = "https://api.builtbybit.com/v1";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum APIToken {
Private(String),
Shared(String),
}
impl APIToken {
pub(crate) fn as_header(&self) -> String {
match self {
APIToken::Private(value) => format!("Private {}", value),
APIToken::Shared(value) => format!("Shared {}", value),
}
}
}
pub struct APIWrapper {
pub(crate) http_client: Client,
pub(crate) rate_limit_store: RateLimitStore,
}
impl APIWrapper {
pub async fn new(token: APIToken) -> Result<APIWrapper> {
let mut default_headers = HeaderMap::new();
default_headers.insert("Authorization", token.as_header().parse().expect("token not a valid HeaderValue"));
let http_client = ClientBuilder::new().https_only(true).default_headers(default_headers).build().expect("http client build failed");
let wrapper = APIWrapper { http_client, rate_limit_store: RateLimitStore::new() };
wrapper.health().await?;
Ok(wrapper)
}
async fn get<D>(&self, endpoint: &str, sort: Option<&SortOptions<'_>>) -> Result<D>
where
D: DeserializeOwned,
{
if sort.is_some() {
let endpoint = format!("{}?{}", endpoint, &sort.unwrap().to_query_string()?);
http::get(self, &endpoint).await?.as_result()
} else {
http::get(self, endpoint).await?.as_result()
}
}
async fn post<D, B>(&self, endpoint: &str, body: &B) -> Result<D>
where
D: DeserializeOwned,
B: Serialize,
{
http::post(self, endpoint, body).await?.as_result()
}
async fn patch<D, B>(&self, endpoint: &str, body: &B) -> Result<D>
where
D: DeserializeOwned,
B: Serialize,
{
http::patch(self, endpoint, body).await?.as_result()
}
async fn delete<D>(&self, endpoint: &str) -> Result<D>
where
D: DeserializeOwned,
{
http::delete(self, endpoint).await?.as_result()
}
pub async fn health(&self) -> Result<()> {
let data: String = self.get(&format!("{}/health", BASE_URL), None).await?;
if data != "ok" {
return Err(APIError::from_raw("HealthEndpointError".to_string(), format!("{} != \"ok\"", data)));
}
Ok(())
}
pub async fn ping(&self) -> Result<Duration> {
let time = Instant::now();
self.health().await?;
Ok(time.elapsed())
}
pub async fn metrics(&self) -> Result<MetricsSnapshot> {
self.get(&format!("{}/metrics", BASE_URL), None).await
}
pub fn resources(&self) -> ResourceHelper<'_> {
ResourceHelper { wrapper: self }
}
pub fn alerts(&self) -> AlertsHelper<'_> {
AlertsHelper { wrapper: self }
}
pub fn conversations(&self) -> ConversationsHelper<'_> {
ConversationsHelper { wrapper: self }
}
pub fn threads(&self) -> ThreadsHelper<'_> {
ThreadsHelper { wrapper: self }
}
pub fn members(&self) -> MembersHelper<'_> {
MembersHelper { wrapper: self }
}
}