v_exchanges_adapters/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![feature(default_field_values)]
3#![feature(duration_constructors)]
4#![feature(try_blocks)]
5pub extern crate v_exchanges_api_generics as generics;
6use std::sync::Arc;
7
8pub use exchanges::*;
9use generics::UrlError;
10use serde::Serialize;
11use tokio::sync::Semaphore;
12use traits::*;
13use v_exchanges_api_generics::{
14	http::{self, *},
15	ws::*,
16};
17
18mod exchanges;
19pub mod traits;
20
21// very long type, make it a macro
22macro_rules! request_ret {
23    ($lt:lifetime, $Response:ty, $Options:ty,  $Body:ty) => {
24        Result<
25            <<$Options as HttpOption<$lt, $Response, $Body>>::RequestHandler as RequestHandler<$Body>>::Successful,
26            RequestError,
27        >
28    };
29}
30
31/// Default maximum number of simultaneous requests allowed
32pub const DEFAULT_MAX_SIMULTANEOUS_REQUESTS: usize = 100;
33
34#[derive(Clone, Debug)]
35pub struct Client {
36	pub client: http::Client,
37	/// Semaphore for limiting simultaneous requests.
38	/// Shared across clones of this client.
39	pub request_semaphore: Arc<Semaphore>,
40	#[cfg(feature = "binance")]
41	binance: binance::BinanceOptions,
42	#[cfg(feature = "bitflyer")]
43	bitflyer: bitflyer::BitFlyerOptions,
44	#[cfg(feature = "bybit")]
45	bybit: bybit::BybitOptions,
46	#[cfg(feature = "coincheck")]
47	coincheck: coincheck::CoincheckOptions,
48	#[cfg(feature = "kucoin")]
49	kucoin: kucoin::KucoinOptions,
50	#[cfg(feature = "mexc")]
51	mexc: mexc::MexcOptions,
52}
53
54impl Default for Client {
55	fn default() -> Self {
56		Self {
57			client: http::Client::default(),
58			request_semaphore: Arc::new(Semaphore::new(DEFAULT_MAX_SIMULTANEOUS_REQUESTS)),
59			#[cfg(feature = "binance")]
60			binance: binance::BinanceOptions::default(),
61			#[cfg(feature = "bitflyer")]
62			bitflyer: bitflyer::BitFlyerOptions::default(),
63			#[cfg(feature = "bybit")]
64			bybit: bybit::BybitOptions::default(),
65			#[cfg(feature = "coincheck")]
66			coincheck: coincheck::CoincheckOptions::default(),
67			#[cfg(feature = "kucoin")]
68			kucoin: kucoin::KucoinOptions::default(),
69			#[cfg(feature = "mexc")]
70			mexc: mexc::MexcOptions::default(),
71		}
72	}
73}
74
75impl Client {
76	/// Set the maximum number of simultaneous requests allowed.
77	///
78	/// This creates a new semaphore with the specified number of permits.
79	/// Note: This will NOT affect existing clones of this client - they will keep using the old semaphore.
80	/// Call this before cloning if you need all instances to share the same limit.
81	pub fn set_max_simultaneous_requests(&mut self, max: usize) {
82		self.request_semaphore = Arc::new(Semaphore::new(max));
83	}
84
85	/// Update the default options for this [Client]
86	pub fn update_default_option<O>(&mut self, option: O)
87	where
88		O: HandlerOption,
89		Self: GetOptions<O::Options>, {
90		self.default_options_mut().update(option);
91	}
92
93	pub fn is_authenticated<O>(&self) -> bool
94	where
95		O: HandlerOption,
96		Self: GetOptions<O::Options>, {
97		self.default_options().is_authenticated()
98	}
99
100	#[inline]
101	fn merged_options<O>(&self, options: impl IntoIterator<Item = O>) -> O::Options
102	where
103		O: HandlerOption,
104		Self: GetOptions<O::Options>, {
105		let mut default_options = self.default_options().clone();
106		for option in options {
107			default_options.update(option);
108		}
109		default_options
110	}
111
112	/// see [http::Client::request()]
113	pub async fn request<'a, R, O, Q, B>(&self, method: Method, url: &str, query: Option<&Q>, body: Option<B>, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, B)
114	where
115		O: HttpOption<'a, R, B>,
116		O::RequestHandler: RequestHandler<B>,
117		Self: GetOptions<O::Options>,
118		Q: Serialize + ?Sized + std::fmt::Debug, {
119		self.client.request(method, url, query, body, &O::request_handler(self.merged_options(options))).await
120	}
121
122	/// see [http::Client::get()]
123	pub async fn get<'a, R, O, Q>(&self, url: &str, query: &Q, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, ())
124	where
125		O: HttpOption<'a, R, ()>,
126		O::RequestHandler: RequestHandler<()>,
127		Self: GetOptions<O::Options>,
128		Q: Serialize + ?Sized + std::fmt::Debug, {
129		self.client.get(url, query, &O::request_handler(self.merged_options(options))).await
130	}
131
132	/// see [http::Client::get_no_query()]
133	pub async fn get_no_query<'a, R, O>(&self, url: &str, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, ())
134	where
135		O: HttpOption<'a, R, ()>,
136		O::RequestHandler: RequestHandler<()>,
137		Self: GetOptions<O::Options>, {
138		self.client.get_no_query(url, &O::request_handler(self.merged_options(options))).await
139	}
140
141	/// see [http::Client::post()]
142	pub async fn post<'a, R, O, B>(&self, url: &str, body: B, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, B)
143	where
144		O: HttpOption<'a, R, B>,
145		O::RequestHandler: RequestHandler<B>,
146		Self: GetOptions<O::Options>, {
147		self.client.post(url, body, &O::request_handler(self.merged_options(options))).await
148	}
149
150	/// see [http::Client::post_no_body()]
151	pub async fn post_no_body<'a, R, O>(&self, url: &str, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, ())
152	where
153		O: HttpOption<'a, R, ()>,
154		O::RequestHandler: RequestHandler<()>,
155		Self: GetOptions<O::Options>, {
156		self.client.post_no_body(url, &O::request_handler(self.merged_options(options))).await
157	}
158
159	/// see [http::Client::put()]
160	pub async fn put<'a, R, O, B>(&self, url: &str, body: B, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, B)
161	where
162		O: HttpOption<'a, R, B>,
163		O::RequestHandler: RequestHandler<B>,
164		Self: GetOptions<O::Options>, {
165		self.client.put(url, body, &O::request_handler(self.merged_options(options))).await
166	}
167
168	/// see [http::Client::put_no_body()]
169	pub async fn put_no_body<'a, R, O>(&self, url: &str, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, ())
170	where
171		O: HttpOption<'a, R, ()>,
172		O::RequestHandler: RequestHandler<()>,
173		Self: GetOptions<O::Options>, {
174		self.client.put_no_body(url, &O::request_handler(self.merged_options(options))).await
175	}
176
177	/// see [http::Client::delete()]
178	pub async fn delete<'a, R, O, Q>(&self, url: &str, query: &Q, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, ())
179	where
180		O: HttpOption<'a, R, ()>,
181		O::RequestHandler: RequestHandler<()>,
182		Self: GetOptions<O::Options>,
183		Q: Serialize + ?Sized + std::fmt::Debug, {
184		self.client.delete(url, query, &O::request_handler(self.merged_options(options))).await
185	}
186
187	/// see [http::Client::delete_no_query()]
188	pub async fn delete_no_query<'a, R, O>(&self, url: &str, options: impl IntoIterator<Item = O>) -> request_ret!('a, R, O, ())
189	where
190		O: HttpOption<'a, R, ()>,
191		O::RequestHandler: RequestHandler<()>,
192		Self: GetOptions<O::Options>, {
193		self.client.delete_no_query(url, &O::request_handler(self.merged_options(options))).await
194	}
195
196	pub fn ws_connection<O>(&self, url: &str, options: impl IntoIterator<Item = O>) -> Result<WsConnection<O::WsHandler>, UrlError>
197	where
198		O: WsOption,
199		O::WsHandler: WsHandler,
200		Self: GetOptions<O::Options>, {
201		WsConnection::try_new(url, O::ws_handler(self.merged_options(options)))
202	}
203}
204
205pub trait GetOptions<O: HandlerOptions> {
206	fn default_options(&self) -> &O;
207	fn default_options_mut(&mut self) -> &mut O;
208	fn is_authenticated(&self) -> bool {
209		self.default_options().is_authenticated()
210	}
211}
212
213#[cfg(feature = "binance")]
214#[cfg_attr(docsrs, doc(cfg(feature = "binance")))]
215impl GetOptions<binance::BinanceOptions> for Client {
216	fn default_options(&self) -> &binance::BinanceOptions {
217		&self.binance
218	}
219
220	fn default_options_mut(&mut self) -> &mut binance::BinanceOptions {
221		&mut self.binance
222	}
223}
224
225#[cfg(feature = "bitflyer")]
226#[cfg_attr(docsrs, doc(cfg(feature = "bitflyer")))]
227impl GetOptions<bitflyer::BitFlyerOptions> for Client {
228	fn default_options(&self) -> &bitflyer::BitFlyerOptions {
229		&self.bitflyer
230	}
231
232	fn default_options_mut(&mut self) -> &mut bitflyer::BitFlyerOptions {
233		&mut self.bitflyer
234	}
235}
236
237#[cfg(feature = "bybit")]
238#[cfg_attr(docsrs, doc(cfg(feature = "bybit")))]
239impl GetOptions<bybit::BybitOptions> for Client {
240	fn default_options(&self) -> &bybit::BybitOptions {
241		&self.bybit
242	}
243
244	fn default_options_mut(&mut self) -> &mut bybit::BybitOptions {
245		&mut self.bybit
246	}
247}
248
249#[cfg(feature = "coincheck")]
250#[cfg_attr(docsrs, doc(cfg(feature = "coincheck")))]
251impl GetOptions<coincheck::CoincheckOptions> for Client {
252	fn default_options(&self) -> &coincheck::CoincheckOptions {
253		&self.coincheck
254	}
255
256	fn default_options_mut(&mut self) -> &mut coincheck::CoincheckOptions {
257		&mut self.coincheck
258	}
259}
260#[cfg(feature = "kucoin")]
261#[cfg_attr(docsrs, doc(cfg(feature = "kucoin")))]
262impl GetOptions<kucoin::KucoinOptions> for Client {
263	fn default_options(&self) -> &kucoin::KucoinOptions {
264		&self.kucoin
265	}
266
267	fn default_options_mut(&mut self) -> &mut kucoin::KucoinOptions {
268		&mut self.kucoin
269	}
270}
271#[cfg(feature = "mexc")]
272#[cfg_attr(docsrs, doc(cfg(feature = "mexc")))]
273impl GetOptions<mexc::MexcOptions> for Client {
274	fn default_options(&self) -> &mexc::MexcOptions {
275		&self.mexc
276	}
277
278	fn default_options_mut(&mut self) -> &mut mexc::MexcOptions {
279		&mut self.mexc
280	}
281}