pub mod api;
pub mod request;
pub mod types;
#[cfg(feature = "ws")]
pub mod ws;
use polyoxide_core::{
HttpClient, HttpClientBuilder, RetryConfig, DEFAULT_POOL_SIZE, DEFAULT_TIMEOUT_MS,
};
use crate::{
error::BinanceError,
usdm::api::{exchange::ExchangeApi, health::Health, market::MarketApi},
weight::WeightBudget,
};
pub use request::WeightedRequest;
pub const DEFAULT_BASE_URL: &str = "https://fapi.binance.com";
pub const DEFAULT_MAX_CONCURRENT: usize = 4;
#[derive(Debug, Clone)]
pub struct Usdm {
http: HttpClient,
budget: WeightBudget,
}
impl Usdm {
pub fn new() -> Result<Self, BinanceError> {
Self::builder().build()
}
pub fn builder() -> UsdmBuilder {
UsdmBuilder::new()
}
pub fn health(&self) -> Health {
Health {
http: self.http.clone(),
budget: self.budget.clone(),
}
}
pub fn exchange(&self) -> ExchangeApi {
ExchangeApi {
http: self.http.clone(),
budget: self.budget.clone(),
}
}
pub fn market(&self) -> MarketApi {
MarketApi {
http: self.http.clone(),
budget: self.budget.clone(),
}
}
pub fn weight_budget(&self) -> &WeightBudget {
&self.budget
}
}
#[derive(Debug)]
pub struct UsdmBuilder {
base_url: String,
timeout_ms: u64,
pool_size: usize,
retry_config: Option<RetryConfig>,
max_concurrent: Option<usize>,
budget: Option<WeightBudget>,
}
impl UsdmBuilder {
fn new() -> Self {
Self {
base_url: DEFAULT_BASE_URL.to_owned(),
timeout_ms: DEFAULT_TIMEOUT_MS,
pool_size: DEFAULT_POOL_SIZE,
retry_config: None,
max_concurrent: None,
budget: None,
}
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn timeout_ms(mut self, timeout: u64) -> Self {
self.timeout_ms = timeout;
self
}
pub fn pool_size(mut self, size: usize) -> Self {
self.pool_size = size;
self
}
pub fn with_retry_config(mut self, config: RetryConfig) -> Self {
self.retry_config = Some(config);
self
}
pub fn max_concurrent(mut self, max: usize) -> Self {
self.max_concurrent = Some(max);
self
}
pub fn weight_budget(mut self, budget: WeightBudget) -> Self {
self.budget = Some(budget);
self
}
pub fn build(self) -> Result<Usdm, BinanceError> {
let mut builder = HttpClientBuilder::new(&self.base_url)
.timeout_ms(self.timeout_ms)
.pool_size(self.pool_size)
.with_max_concurrent(self.max_concurrent.unwrap_or(DEFAULT_MAX_CONCURRENT))
.gzip(true);
if let Some(config) = self.retry_config {
builder = builder.with_retry_config(config);
}
Ok(Usdm {
http: builder.build()?,
budget: self.budget.unwrap_or_default(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_builder_targets_the_production_host() {
let usdm = Usdm::new().expect("client builds");
assert_eq!(usdm.http.base_url.as_str(), "https://fapi.binance.com/");
}
#[test]
fn a_bad_base_url_is_a_url_error() {
let err = Usdm::builder().base_url("not a url").build().unwrap_err();
assert!(matches!(
err,
BinanceError::Api(polyoxide_core::ApiError::Url(_))
));
}
#[test]
fn clients_given_one_budget_share_it() {
let budget = WeightBudget::new();
let a = Usdm::builder()
.weight_budget(budget.clone())
.build()
.unwrap();
let b = Usdm::builder().weight_budget(budget).build().unwrap();
assert!(a.weight_budget().is_shared_with(b.weight_budget()));
let c = Usdm::new().unwrap();
assert!(!a.weight_budget().is_shared_with(c.weight_budget()));
}
}