use std::future::Future;
use std::pin::Pin;
use crate::error::{Error, Result};
use crate::feature::Feature;
use crate::request::{
CancelOrdersRequest, CandleRequest, DepositAddressRequest, HistoryRequest, MarginRequest,
OrderHistoryRequest, OrderLookupRequest, OrderRequest, TransferHistoryRequest,
TransferLookupRequest, WithdrawRequest,
};
use crate::stream::{AccountStream, MarketStream};
use crate::types::{
AssetNetwork, Balance, CancelOrdersResult, Candle, Deposit, DepositAddress,
DepositAddressEntry, Exchange, Feed, FundingPayment, FundingRate, MarginSummary, Market,
MarketInfo, MarketKind, Order, OrderBook, OrderRules, Page, Position, StreamConfig,
Subscription, Ticker, Trade, Withdrawal, WithdrawalQuote,
};
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait Adapter: Send + Sync + 'static {
fn exchange(&self) -> Exchange;
fn supports(&self, feature: Feature) -> bool;
fn markets(&self, kind: MarketKind) -> BoxFuture<'_, Result<Vec<MarketInfo>>> {
let _ = kind;
unsupported(self.exchange(), Feature::Markets)
}
fn trades(&self, market: &Market, limit: Option<u32>) -> BoxFuture<'_, Result<Vec<Trade>>> {
let _ = (market, limit);
unsupported(self.exchange(), Feature::Trades)
}
fn order_book(&self, market: &Market, depth: Option<u32>) -> BoxFuture<'_, Result<OrderBook>> {
let _ = (market, depth);
unsupported(self.exchange(), Feature::OrderBook)
}
fn ticker(&self, market: &Market) -> BoxFuture<'_, Result<Ticker>> {
let _ = market;
unsupported(self.exchange(), Feature::Ticker)
}
fn candles(&self, request: &CandleRequest) -> BoxFuture<'_, Result<Vec<Candle>>> {
let _ = request;
unsupported(self.exchange(), Feature::Candles)
}
fn subscribe(
&self,
subscription: &Subscription,
config: &StreamConfig,
) -> BoxFuture<'_, Result<MarketStream>> {
if subscription.markets().is_empty() {
return Box::pin(async {
Err(Error::invalid_request(
"markets",
"a subscription needs at least one market",
))
});
}
if subscription.feeds().is_empty() {
return Box::pin(async {
Err(Error::invalid_request(
"feeds",
"a subscription needs at least one feed",
))
});
}
let _ = config;
let feature = match subscription.feeds()[0] {
Feed::Trades => Feature::TradeStream,
Feed::OrderBook => Feature::OrderBookStream,
Feed::Ticker => Feature::TickerStream,
Feed::Candles(_) => Feature::CandleStream,
};
unsupported(self.exchange(), feature)
}
fn balances(&self) -> BoxFuture<'_, Result<Vec<Balance>>> {
unsupported(self.exchange(), Feature::Balances)
}
fn order_rules(&self, market: &Market) -> BoxFuture<'_, Result<OrderRules>> {
let _ = market;
unsupported(self.exchange(), Feature::Trading)
}
fn asset_networks(&self, asset: &str) -> BoxFuture<'_, Result<Vec<AssetNetwork>>> {
let _ = asset;
unsupported(self.exchange(), Feature::AssetNetworks)
}
fn deposit_addresses(&self) -> BoxFuture<'_, Result<Vec<DepositAddressEntry>>> {
unsupported(self.exchange(), Feature::DepositAddresses)
}
fn deposit_address(
&self,
request: &DepositAddressRequest,
) -> BoxFuture<'_, Result<DepositAddress>> {
let _ = request;
unsupported(self.exchange(), Feature::DepositAddresses)
}
fn create_deposit_address(
&self,
request: &DepositAddressRequest,
) -> BoxFuture<'_, Result<DepositAddress>> {
let _ = request;
unsupported(self.exchange(), Feature::DepositAddresses)
}
fn prepare_withdrawal(
&self,
request: &WithdrawRequest,
) -> BoxFuture<'_, Result<WithdrawalQuote>> {
let _ = request;
unsupported(self.exchange(), Feature::WithdrawalQuotes)
}
fn withdraw(&self, request: &WithdrawRequest) -> BoxFuture<'_, Result<Withdrawal>> {
let _ = request;
unsupported(self.exchange(), Feature::Withdrawals)
}
fn deposit(&self, request: &TransferLookupRequest) -> BoxFuture<'_, Result<Deposit>> {
let _ = request;
unsupported(self.exchange(), Feature::DepositLookup)
}
fn withdrawal(&self, request: &TransferLookupRequest) -> BoxFuture<'_, Result<Withdrawal>> {
let _ = request;
unsupported(self.exchange(), Feature::WithdrawalLookup)
}
fn cancel_withdrawal(&self, withdrawal_id: &str) -> BoxFuture<'_, Result<()>> {
let _ = withdrawal_id;
unsupported(self.exchange(), Feature::WithdrawalCancellation)
}
fn deposits(&self, request: &TransferHistoryRequest) -> BoxFuture<'_, Result<Page<Deposit>>> {
let _ = request;
unsupported(self.exchange(), Feature::DepositHistory)
}
fn withdrawals(
&self,
request: &TransferHistoryRequest,
) -> BoxFuture<'_, Result<Page<Withdrawal>>> {
let _ = request;
unsupported(self.exchange(), Feature::WithdrawalHistory)
}
fn open_orders(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Order>>> {
let _ = market;
unsupported(self.exchange(), Feature::OpenOrders)
}
fn order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<Order>> {
let _ = (market, order_id);
unsupported(self.exchange(), Feature::OrderHistory)
}
fn order_by_client_id(&self, market: &Market, client_id: &str) -> BoxFuture<'_, Result<Order>> {
let _ = (market, client_id);
unsupported(self.exchange(), Feature::OrderHistory)
}
fn orders_by_ids(&self, request: &OrderLookupRequest) -> BoxFuture<'_, Result<Vec<Order>>> {
let _ = request;
unsupported(self.exchange(), Feature::OrderHistory)
}
fn order_history(&self, request: &OrderHistoryRequest) -> BoxFuture<'_, Result<Page<Order>>> {
let _ = request;
unsupported(self.exchange(), Feature::OrderHistory)
}
fn subscribe_account(&self, config: &StreamConfig) -> BoxFuture<'_, Result<AccountStream>> {
let _ = config;
unsupported(self.exchange(), Feature::AccountStream)
}
fn place_order(&self, request: &OrderRequest) -> BoxFuture<'_, Result<Order>> {
let _ = request;
unsupported(self.exchange(), Feature::Trading)
}
fn cancel_order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<()>> {
let _ = (market, order_id);
unsupported(self.exchange(), Feature::Trading)
}
fn cancel_order_by_client_id(
&self,
market: &Market,
client_id: &str,
) -> BoxFuture<'_, Result<()>> {
let _ = (market, client_id);
unsupported(self.exchange(), Feature::Trading)
}
fn cancel_orders(
&self,
request: &CancelOrdersRequest,
) -> BoxFuture<'_, Result<CancelOrdersResult>> {
let _ = request;
unsupported(self.exchange(), Feature::Trading)
}
fn positions(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Position>>> {
let _ = market;
unsupported(self.exchange(), Feature::Positions)
}
fn margin_summary(&self) -> BoxFuture<'_, Result<MarginSummary>> {
unsupported(self.exchange(), Feature::Margin)
}
fn funding_rates(&self, request: &HistoryRequest) -> BoxFuture<'_, Result<Page<FundingRate>>> {
let _ = request;
unsupported(self.exchange(), Feature::FundingRates)
}
fn funding_payments(
&self,
request: &HistoryRequest,
) -> BoxFuture<'_, Result<Page<FundingPayment>>> {
let _ = request;
unsupported(self.exchange(), Feature::FundingPayments)
}
fn set_margin(&self, request: &MarginRequest) -> BoxFuture<'_, Result<()>> {
let _ = request;
unsupported(self.exchange(), Feature::MarginConfig)
}
}
fn unsupported<'a, T: Send + 'a>(exchange: Exchange, feature: Feature) -> BoxFuture<'a, Result<T>> {
let exchange = exchange.id();
Box::pin(async move {
Err(Error::unsupported(
feature,
exchange,
format!("{exchange} has no endpoint for {feature}"),
))
})
}
impl Adapter for Box<dyn Adapter> {
fn exchange(&self) -> Exchange {
(**self).exchange()
}
fn supports(&self, feature: Feature) -> bool {
(**self).supports(feature)
}
fn markets(&self, kind: MarketKind) -> BoxFuture<'_, Result<Vec<MarketInfo>>> {
(**self).markets(kind)
}
fn trades(&self, market: &Market, limit: Option<u32>) -> BoxFuture<'_, Result<Vec<Trade>>> {
(**self).trades(market, limit)
}
fn order_book(&self, market: &Market, depth: Option<u32>) -> BoxFuture<'_, Result<OrderBook>> {
(**self).order_book(market, depth)
}
fn ticker(&self, market: &Market) -> BoxFuture<'_, Result<Ticker>> {
(**self).ticker(market)
}
fn candles(&self, request: &CandleRequest) -> BoxFuture<'_, Result<Vec<Candle>>> {
(**self).candles(request)
}
fn subscribe(
&self,
subscription: &Subscription,
config: &StreamConfig,
) -> BoxFuture<'_, Result<MarketStream>> {
(**self).subscribe(subscription, config)
}
fn balances(&self) -> BoxFuture<'_, Result<Vec<Balance>>> {
(**self).balances()
}
fn order_rules(&self, market: &Market) -> BoxFuture<'_, Result<OrderRules>> {
(**self).order_rules(market)
}
fn asset_networks(&self, asset: &str) -> BoxFuture<'_, Result<Vec<AssetNetwork>>> {
(**self).asset_networks(asset)
}
fn deposit_addresses(&self) -> BoxFuture<'_, Result<Vec<DepositAddressEntry>>> {
(**self).deposit_addresses()
}
fn deposit_address(
&self,
request: &DepositAddressRequest,
) -> BoxFuture<'_, Result<DepositAddress>> {
(**self).deposit_address(request)
}
fn create_deposit_address(
&self,
request: &DepositAddressRequest,
) -> BoxFuture<'_, Result<DepositAddress>> {
(**self).create_deposit_address(request)
}
fn prepare_withdrawal(
&self,
request: &WithdrawRequest,
) -> BoxFuture<'_, Result<WithdrawalQuote>> {
(**self).prepare_withdrawal(request)
}
fn withdraw(&self, request: &WithdrawRequest) -> BoxFuture<'_, Result<Withdrawal>> {
(**self).withdraw(request)
}
fn deposit(&self, request: &TransferLookupRequest) -> BoxFuture<'_, Result<Deposit>> {
(**self).deposit(request)
}
fn withdrawal(&self, request: &TransferLookupRequest) -> BoxFuture<'_, Result<Withdrawal>> {
(**self).withdrawal(request)
}
fn cancel_withdrawal(&self, withdrawal_id: &str) -> BoxFuture<'_, Result<()>> {
(**self).cancel_withdrawal(withdrawal_id)
}
fn deposits(&self, request: &TransferHistoryRequest) -> BoxFuture<'_, Result<Page<Deposit>>> {
(**self).deposits(request)
}
fn withdrawals(
&self,
request: &TransferHistoryRequest,
) -> BoxFuture<'_, Result<Page<Withdrawal>>> {
(**self).withdrawals(request)
}
fn open_orders(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Order>>> {
(**self).open_orders(market)
}
fn order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<Order>> {
(**self).order(market, order_id)
}
fn order_by_client_id(&self, market: &Market, client_id: &str) -> BoxFuture<'_, Result<Order>> {
(**self).order_by_client_id(market, client_id)
}
fn orders_by_ids(&self, request: &OrderLookupRequest) -> BoxFuture<'_, Result<Vec<Order>>> {
(**self).orders_by_ids(request)
}
fn order_history(&self, request: &OrderHistoryRequest) -> BoxFuture<'_, Result<Page<Order>>> {
(**self).order_history(request)
}
fn subscribe_account(&self, config: &StreamConfig) -> BoxFuture<'_, Result<AccountStream>> {
(**self).subscribe_account(config)
}
fn place_order(&self, request: &OrderRequest) -> BoxFuture<'_, Result<Order>> {
(**self).place_order(request)
}
fn cancel_order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<()>> {
(**self).cancel_order(market, order_id)
}
fn cancel_order_by_client_id(
&self,
market: &Market,
client_id: &str,
) -> BoxFuture<'_, Result<()>> {
(**self).cancel_order_by_client_id(market, client_id)
}
fn cancel_orders(
&self,
request: &CancelOrdersRequest,
) -> BoxFuture<'_, Result<CancelOrdersResult>> {
(**self).cancel_orders(request)
}
fn positions(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Position>>> {
(**self).positions(market)
}
fn margin_summary(&self) -> BoxFuture<'_, Result<MarginSummary>> {
(**self).margin_summary()
}
fn funding_rates(&self, request: &HistoryRequest) -> BoxFuture<'_, Result<Page<FundingRate>>> {
(**self).funding_rates(request)
}
fn funding_payments(
&self,
request: &HistoryRequest,
) -> BoxFuture<'_, Result<Page<FundingPayment>>> {
(**self).funding_payments(request)
}
fn set_margin(&self, request: &MarginRequest) -> BoxFuture<'_, Result<()>> {
(**self).set_margin(request)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MarketDataOnly;
impl Adapter for MarketDataOnly {
fn exchange(&self) -> Exchange {
Exchange::Upbit
}
fn supports(&self, feature: Feature) -> bool {
matches!(feature, Feature::Markets | Feature::Ticker)
}
}
#[tokio::test]
async fn unimplemented_methods_report_the_missing_feature_by_name() {
let error = MarketDataOnly.balances().await.unwrap_err();
let Error::Unsupported {
feature, exchange, ..
} = error
else {
panic!("expected an unsupported-feature error");
};
assert_eq!(feature, Feature::Balances);
assert_eq!(exchange, "upbit");
}
#[tokio::test]
async fn an_adapter_survives_being_held_behind_dyn() {
let adapters: Vec<Box<dyn Adapter>> = vec![Box::new(MarketDataOnly)];
for adapter in &adapters {
assert_eq!(adapter.exchange(), Exchange::Upbit);
assert!(adapter.supports(Feature::Ticker));
assert!(adapter.positions(None).await.is_err());
}
}
#[test]
fn supports_reflects_the_adapter_not_the_trait_defaults() {
assert!(MarketDataOnly.supports(Feature::Markets));
assert!(!MarketDataOnly.supports(Feature::Trading));
}
}