1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::{Pin, pin};
4use std::sync::Arc;
5use std::task::{Context, Poll, Waker};
6
7use async_trait::async_trait;
8use futures_util::{SinkExt, StreamExt};
9use futures_util::stream::{SplitSink};
10use serde_json::Value;
11use thiserror::Error;
12use tokio::sync::Mutex;
13use tokio::task::JoinHandle;
14use tokio_tungstenite::{connect_async};
15use tokio_tungstenite::tungstenite::Message;
16use tracing::{error, warn};
17use url::Url;
18
19use crate::schema::Request;
20use crate::listener::{listen_for_responses, ResponseHandler, Stream};
21use crate::message_processing::ProcessedMessage;
22
23#[async_trait]
25pub trait XtbConnection {
26
27 type Error;
28
29 type Response: Future<Output=Result<ProcessedMessage, BasicXtbConnectionError>>;
30
31 async fn send_command(&mut self, command: &str, payload: Option<Value>) -> Result<Self::Response, Self::Error>;
33}
34
35
36#[derive(Debug, Error)]
37pub enum BasicXtbConnectionError {
38 #[error("Cannot connect to server ({0}")]
39 CannotConnect(String),
40 #[error("Cannot serialize command payload")]
41 SerializationError(serde_json::Error),
42 #[error("Cannot send request to the XTB server.")]
43 CannotSendRequest(tokio_tungstenite::tungstenite::Error),
44}
45
46
47pub struct BasicXtbConnection {
49 sink: SplitSink<Stream, Message>,
50 tag_maker: TagMaker,
51 promise_state_by_tag: Arc<Mutex<HashMap<String, Arc<Mutex<ResponsePromiseState>>>>>,
52 listener_join: JoinHandle<()>,
53}
54
55
56impl BasicXtbConnection {
57 pub async fn new(url: Url) -> Result<Self, BasicXtbConnectionError> {
59 let host_clone = url.as_str().to_owned();
60 let (conn, _) = connect_async(url).await.map_err(|err| {
61 error!("Cannot connect to server {}: {:?}", host_clone, err);
62 BasicXtbConnectionError::CannotConnect(host_clone)
63 })?;
64
65 let (sink, stream) = conn.split();
66 let lookup = Arc::new(Mutex::new(HashMap::new()));
67 let listener_join = listen_for_responses(stream, BasicConnectionResponseHandler(lookup.clone()));
68 let instance = Self {
69 sink,
70 tag_maker: TagMaker::default(),
71 promise_state_by_tag: lookup,
72 listener_join
73 };
74 Ok(instance)
75 }
76
77 fn build_request(&mut self, command: &str, mut payload: Option<Value>) -> (Request, String) {
80 let tag = self.tag_maker.next();
81
82 if let Some(p) = &payload {
83 if p.is_null() {
84 payload = None;
85 }
86 }
87
88 let r = Request::default()
89 .with_command(command)
90 .with_maybe_arguments(payload)
91 .with_custom_tag(&tag);
92 (r, tag)
93 }
94}
95
96
97
98#[async_trait]
99impl XtbConnection for BasicXtbConnection {
100
101 type Error = BasicXtbConnectionError;
102
103 type Response = ResponsePromise;
104
105 async fn send_command(&mut self, command: &str, payload: Option<Value>) -> Result<Self::Response, Self::Error> {
106 let (request, tag) = self.build_request(command, payload);
107 let request_json = serde_json::to_string(&request).map_err(BasicXtbConnectionError::SerializationError)?;
108 let message = Message::Text(request_json);
109
110 let (promise, state) = ResponsePromise::new();
111 self.promise_state_by_tag.lock().await.insert(tag, state);
112 self.sink.send(message).await.map_err(BasicXtbConnectionError::CannotSendRequest)?;
113
114 Ok(promise)
115 }
116}
117
118
119impl Drop for BasicXtbConnection {
120 fn drop(&mut self) {
121 self.listener_join.abort();
123 }
124}
125
126
127#[derive(Default, Debug)]
130pub struct ResponsePromiseState {
131 result: Option<Result<ProcessedMessage, BasicXtbConnectionError>>,
136 waker: Option<Waker>,
139}
140
141
142impl ResponsePromiseState {
143 pub fn set_result(&mut self, result: Result<ProcessedMessage, BasicXtbConnectionError>) {
145 self.result = Some(result);
146 if let Some(waker) = self.waker.take() {
147 waker.wake();
148 }
149 }
150}
151
152
153struct BasicConnectionResponseHandler(Arc<Mutex<HashMap<String, Arc<Mutex<ResponsePromiseState>>>>>);
155
156#[async_trait]
157impl ResponseHandler for BasicConnectionResponseHandler {
158 async fn handle_response(&self, response: ProcessedMessage) {
159 let maybe_tag = match &response {
160 ProcessedMessage::Response(resp) => resp.custom_tag.as_ref(),
161 ProcessedMessage::ErrorResponse(resp) => resp.custom_tag.as_ref(),
162 };
163
164 let tag = match maybe_tag {
166 Some(t) => t,
167 _ => {
168 warn!("Response has no tag and cannot be routed: {:?}", response);
169 return;
170 }
171 };
172
173 if let Some(state) = self.0.lock().await.remove(tag) {
175 state.lock().await.set_result(Ok(response));
176 }
177 }
178}
179
180
181#[derive(Debug)]
186pub struct ResponsePromise {
187 state: Arc<Mutex<ResponsePromiseState>>,
189}
190
191
192impl ResponsePromise {
193 pub fn new() -> (Self, Arc<Mutex<ResponsePromiseState>>) {
198 let state = ResponsePromiseState::default();
199 let wrapped_state = Arc::new(Mutex::new(state));
200 (Self { state: wrapped_state.clone() }, wrapped_state)
201 }
202}
203
204
205impl Future for ResponsePromise {
206 type Output = Result<ProcessedMessage, BasicXtbConnectionError>;
207
208 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
209 if let Poll::Ready(mut guard) = pin!(self.state.lock()).poll(cx) {
211 if let Some(response) = guard.result.take() {
213 return Poll::Ready(response);
214 }
215 guard.waker = Some(cx.waker().clone());
217 }
218 Poll::Pending
220 }
221}
222
223
224#[derive(Default, Debug)]
231struct TagMaker(u64);
232
233
234impl TagMaker {
235 fn next(&mut self) -> String {
236 self.0 += 1;
237 format!("message_{}", self.0)
238 }
239}
240
241
242#[cfg(test)]
243mod tests {
244 mod response_promise {
245 use std::sync::Arc;
246 use std::time::Duration;
247
248 use rstest::*;
249 use serde_json::to_value;
250 use tokio::spawn;
251 use tokio::sync::Mutex;
252 use tokio::time::sleep;
253
254 use crate::schema::Response;
255 use crate::connection::ResponsePromiseState;
256 use crate::message_processing::ProcessedMessage;
257 use crate::ResponsePromise;
258
259 #[rstest]
260 #[case(0)]
261 #[case(1)]
262 #[case(50)]
263 #[case(100)]
264 #[timeout(Duration::from_millis(500))]
265 #[tokio::test]
266 async fn deliver_data(#[case] delay_ms: u64) {
267 let (instance, target) = ResponsePromise::new();
268 spawn(write_data(target, delay_ms));
269 let result = instance.await;
270 }
271
272 async fn write_data(target: Arc<Mutex<ResponsePromiseState>>, delay: u64) {
273 if delay > 0 {
274 sleep(Duration::from_millis(delay)).await;
275 }
276 let mut lock = target.lock().await;
277 let mut response = Response::default();
278 response.return_data = Some(to_value(42).unwrap());
279 lock.set_result(Ok(ProcessedMessage::Response(response)));
280 }
281 }
282
283 mod tag_maker {
284 use crate::connection::TagMaker;
285
286 #[test]
287 fn make_series() {
288 let mut maker = TagMaker::default();
289
290 let tag = maker.next();
291 assert_eq!(tag, "message_1");
292 let tag = maker.next();
293 assert_eq!(tag, "message_2");
294 let tag = maker.next();
295 assert_eq!(tag, "message_3");
296 }
297 }
298}