From fee9afa4fcb584a23c13d99ed8b7d4448b9cd344 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:10:24 +0000 Subject: [PATCH 01/23] revise dependencies --- Cargo.toml | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e0d190a..398f49da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,9 +54,7 @@ serde_json = { version = "1.0.68", default-features = false, features = [ serde_with = "3.2.0" serde_repr = "0.1" zeroize = "1.5.7" -hashbrown = { version = "0.14.0", default-features = false, features = [ - "serde", -] } +hashbrown = { version = "0.14.5", features = ["serde"] } fnv = { version = "1.0.7", default-features = false } derive-new = { version = "0.5.9", default-features = false } thiserror-no-std = "2.0.2" @@ -66,14 +64,15 @@ url = { version = "2.2.2", default-features = false, optional = true } futures = { version = "0.3.28", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } - -[dependencies.embedded-websocket] -# git version needed to use `framer_async` -git = "https://github.com/ninjasource/embedded-websocket" -version = "0.9.2" -rev = "8d87d46f46fa0c75e099ca8aad37e8d00c8854f8" -default-features = false -optional = true +embassy-sync = { version = "0.6.0", default-features = false } +embedded-io-async = "0.6.1" +futures-sink = { version = "0.3.30", default-features = false } +futures-core = { version = "0.3.30", default-features = false } +futures-util = { version = "0.3.30", optional = true } +tokio-util = { version = "0.7.7", features = ["codec"], optional = true } +bytes = { version = "1.4.0", default-features = false } +embassy-futures = "0.1.1" +embedded-websocket = { version = "0.9.3", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -82,8 +81,6 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -futures-util = "0.3.30" -bytes = { version = "1.4.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = [ "getrandom", "std", @@ -96,13 +93,20 @@ harness = false [features] default = ["std", "core", "models", "utils", "tungstenite"] -models = ["core", "transactions", "requests", "ledger"] +models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] +results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] amounts = ["core"] currencies = ["core"] -tungstenite = ["url", "futures", "tokio/full", "tokio-tungstenite/native-tls"] +tungstenite = [ + "url", + "futures", + "tokio/net", + "tokio-tungstenite/native-tls", + "futures-util", +] embedded-ws = ["url", "futures", "embedded-websocket"] core = ["utils"] utils = [] @@ -119,4 +123,5 @@ std = [ "serde/std", "indexmap/std", "secp256k1/std", + "dep:tokio-util", ] From c277b81dc5dd011164305824f1fbd4e968511093 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:11:11 +0000 Subject: [PATCH 02/23] remove old clients --- src/asynch/clients/embedded_ws.rs | 168 ------------------------------ src/asynch/clients/tungstenite.rs | 112 -------------------- 2 files changed, 280 deletions(-) delete mode 100644 src/asynch/clients/embedded_ws.rs delete mode 100644 src/asynch/clients/tungstenite.rs diff --git a/src/asynch/clients/embedded_ws.rs b/src/asynch/clients/embedded_ws.rs deleted file mode 100644 index 76106dee..00000000 --- a/src/asynch/clients/embedded_ws.rs +++ /dev/null @@ -1,168 +0,0 @@ -use super::{ - exceptions::XRPLWebsocketException, - {WebsocketClosed, WebsocketOpen}, -}; -use crate::Err; -use anyhow::Result; -use core::marker::PhantomData; -use core::{fmt::Debug, ops::Deref}; -pub use embedded_websocket::{ - framer_async::{ - Framer as EmbeddedWebsocketFramer, FramerError as EmbeddedWebsocketFramerError, - ReadResult as EmbeddedWebsocketReadMessageType, - }, - Client as EmbeddedWebsocketClient, Error as EmbeddedWebsocketError, - WebSocket as EmbeddedWebsocket, WebSocketCloseStatusCode as EmbeddedWebsocketCloseStatusCode, - WebSocketOptions as EmbeddedWebsocketOptions, - WebSocketSendMessageType as EmbeddedWebsocketSendMessageType, - WebSocketState as EmbeddedWebsocketState, -}; -use futures::{Sink, Stream}; -use rand_core::RngCore; - -pub struct AsyncWebsocketClient { - inner: EmbeddedWebsocketFramer, - status: PhantomData, -} - -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} - -impl AsyncWebsocketClient { - /// Open a websocket connection. - pub async fn open<'a, B, E>( - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - rng: Rng, - websocket_options: &EmbeddedWebsocketOptions<'_>, - ) -> Result> - where - B: AsRef<[u8]>, - E: Debug, - { - let websocket = EmbeddedWebsocket::::new_client(rng); - let mut framer = EmbeddedWebsocketFramer::new(websocket); - match framer.connect(stream, buffer, websocket_options).await { - Ok(Some(_)) => {} - Ok(None) => {} - Err(error) => return Err!(XRPLWebsocketException::from(error)), - } - - Ok(AsyncWebsocketClient { - inner: framer, - status: PhantomData::, - }) - } -} - -impl AsyncWebsocketClient { - /// Encode a message to be sent over the websocket. - pub fn encode( - &mut self, - message_type: EmbeddedWebsocketSendMessageType, - end_of_message: bool, - from: &[u8], - to: &mut [u8], - ) -> Result - where - E: Debug, - { - match self - .inner - .encode::(message_type, end_of_message, from, to) - { - Ok(bytes_written) => Ok(bytes_written), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Send a message over the websocket. - pub async fn send<'b, E, R: serde::Serialize>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - end_of_message: bool, - frame_buf: R, - ) -> Result<()> - where - E: Debug, - { - match serde_json::to_vec(&frame_buf) { - Ok(frame_buf) => match self - .inner - .write( - stream, - stream_buf, - EmbeddedWebsocketSendMessageType::Text, - end_of_message, - frame_buf.as_slice(), - ) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - }, - Err(error) => Err!(error), - } - } - - /// Close the websocket connection. - pub async fn close<'b, E>( - &mut self, - stream: &mut (impl Sink<&'b [u8], Error = E> + Unpin), - stream_buf: &'b mut [u8], - close_status: EmbeddedWebsocketCloseStatusCode, - status_description: Option<&str>, - ) -> Result<()> - where - E: Debug, - { - match self - .inner - .close(stream, stream_buf, close_status, status_description) - .await - { - Ok(()) => Ok(()), - Err(error) => Err!(XRPLWebsocketException::from(error)), - } - } - - /// Read a message from the websocket. - pub async fn next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Option>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Some(Ok(read_result)), - Some(Err(error)) => Some(Err!(XRPLWebsocketException::from(error))), - None => None, - } - } - - /// Read a message from the websocket. - /// - /// This is similar to the `next` method, but returns a `Result>` rather than an `Option>`, making for easy use with the ? operator. - pub async fn try_next<'a, B: Deref, E>( - &'a mut self, - stream: &mut (impl Stream> + Sink<&'a [u8], Error = E> + Unpin), - buffer: &'a mut [u8], - ) -> Result>> - // TODO: Change to Response as soon as implemented - where - E: Debug, - { - match self.inner.read(stream, buffer).await { - Some(Ok(read_result)) => Ok(Some(read_result)), - Some(Err(error)) => Err!(XRPLWebsocketException::from(error)), - None => Ok(None), - } - } -} diff --git a/src/asynch/clients/tungstenite.rs b/src/asynch/clients/tungstenite.rs deleted file mode 100644 index c03bf281..00000000 --- a/src/asynch/clients/tungstenite.rs +++ /dev/null @@ -1,112 +0,0 @@ -use super::{ - exceptions::XRPLWebsocketException, - {WebsocketClosed, WebsocketOpen}, -}; -use crate::Err; - -use anyhow::Result; -use core::marker::PhantomData; -use core::{pin::Pin, task::Poll}; -use futures::{Sink, Stream}; -use tokio::net::TcpStream; -use tokio_tungstenite::{ - connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, - WebSocketStream as TungsteniteWebsocketStream, -}; -use url::Url; - -pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; - -pub struct AsyncWebsocketClient { - inner: TungsteniteWebsocketStream>, - status: PhantomData, -} - -impl AsyncWebsocketClient { - pub fn is_open(&self) -> bool { - core::any::type_name::() == core::any::type_name::() - } -} - -impl Sink for AsyncWebsocketClient -where - I: serde::Serialize, -{ - type Error = anyhow::Error; - - fn poll_ready( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn start_send(mut self: core::pin::Pin<&mut Self>, item: I) -> Result<()> { - match serde_json::to_string(&item) { - Ok(json) => { - match Pin::new(&mut self.inner).start_send(TungsteniteMessage::Text(json)) { - Ok(()) => Ok(()), - Err(error) => Err!(error), - } - } - Err(error) => Err!(error), - } - } - - fn poll_flush( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_flush(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } - - fn poll_close( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll> { - match Pin::new(&mut self.inner).poll_close(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), - Poll::Pending => Poll::Pending, - } - } -} - -impl Stream for AsyncWebsocketClient { - type Item = > as Stream>::Item; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll> { - match Pin::new(&mut self.inner).poll_next(cx) { - Poll::Ready(Some(item)) => Poll::Ready(Some(item)), - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -impl AsyncWebsocketClient { - pub async fn open(uri: Url) -> Result> { - match tungstenite_connect_async(uri).await { - Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { - inner: websocket_stream, - status: PhantomData::, - }), - Err(error) => { - Err!(XRPLWebsocketException::UnableToConnect::( - error - )) - } - } - } -} From b8fb8ff6c201119b2f450481e28c58c17a74f5fa Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:11:39 +0000 Subject: [PATCH 03/23] add new clients --- .../clients/websocket/embedded_websocket.rs | 253 ++++++++++++++++++ src/asynch/clients/websocket/tungstenite.rs | 213 +++++++++++++++ 2 files changed, 466 insertions(+) create mode 100644 src/asynch/clients/websocket/embedded_websocket.rs create mode 100644 src/asynch/clients/websocket/tungstenite.rs diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs new file mode 100644 index 00000000..83fef699 --- /dev/null +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -0,0 +1,253 @@ +use core::{ + fmt::{Debug, Display}, + iter::FromIterator, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use alloc::{ + dbg, + string::{String, ToString}, + sync::Arc, +}; +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_io_async::{ErrorType, Read, Write}; +use embedded_websocket::{ + framer_async::Framer, framer_async::ReadResult, Client, WebSocketClient, WebSocketOptions, + WebSocketSendMessageType, +}; +use futures_core::Stream; +use futures_sink::Sink; +use rand_core::RngCore; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; +use crate::{ + asynch::clients::{ + client::Client as ClientTrait, + websocket::websocket_base::{MessageHandler, WebsocketBase}, + }, + models::requests::Request, +}; + +use super::exceptions::XRPLWebsocketException; + +pub struct AsyncWebsocketClient< + const BUF: usize, + Tcp, + B, + E, + Rng: RngCore, + M = SingleExecutorMutex, + Status = WebsocketClosed, +> where + M: RawMutex, + B: Deref + AsRef<[u8]>, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + tcp: Arc>, + websocket: Arc>>, + tx_buffer: [u8; BUF], + websocket_base: Arc>>, + status: PhantomData, +} + +impl + AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + pub async fn open( + rng: Rng, + tcp: Tcp, + url: Url, + ) -> Result< + AsyncWebsocketClient, + XRPLWebsocketException, + > { + // replace the scheme with http or https + let scheme = match url.scheme() { + "wss" => "https", + "ws" => "http", + _ => url.scheme(), + }; + let port = match url.port() { + Some(port) => port, + None => match url.scheme() { + "wss" => 443, + "ws" => 80, + _ => 80, + }, + } + .to_string(); + let path = url.path(); + let host = url.host_str().unwrap(); + let origin = scheme.to_string() + "://" + host + ":" + &port + path; + let websocket_options = WebSocketOptions { + path, + host, + origin: &origin, + sub_protocols: None, + additional_headers: None, + }; + // dbg!(&websocket_options.path); + // dbg!(&websocket_options.host); + // dbg!(&websocket_options.origin); + // panic!(); + let websocket = Arc::new(Mutex::new(Framer::new(WebSocketClient::new_client(rng)))); + let tcp = Arc::new(Mutex::new(tcp)); + let mut buffer = [0; BUF]; + websocket + .lock() + .await + .connect( + tcp.lock().await.deref_mut(), + &mut buffer, + &websocket_options, + ) + .await + .unwrap(); + + Ok(AsyncWebsocketClient { + tcp, + websocket, + tx_buffer: [0; BUF], + websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + status: PhantomData::, + }) + } +} + +impl + AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn do_write(&self, buf: &[u8]) -> Result::Error> { + let mut inner = self.websocket.lock().await; + let mut tcp = self.tcp.lock().await; + let mut buffer = self.tx_buffer; + match inner + .write( + tcp.deref_mut(), + &mut buffer, + WebSocketSendMessageType::Text, + false, + buf, + ) + .await + { + Ok(()) => Ok(buf.len()), + Err(error) => Err(XRPLWebsocketException::::from(error)), + } + } +} + +impl ErrorType + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + type Error = XRPLWebsocketException; +} + +impl Write + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + self.do_write(buf).await + } +} + +impl Read + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut inner = self.websocket.lock().await; + let mut tcp = self.tcp.lock().await; + match inner.read(tcp.deref_mut(), buf).await { + Some(Ok(ReadResult::Text(t))) => Ok(t.len()), + Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), + Some(Ok(ReadResult::Ping(_))) => Ok(0), + Some(Ok(_)) => Err(XRPLWebsocketException::::UnexpectedMessageType), + Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), + None => Err(XRPLWebsocketException::::Disconnected), + } + } +} + +impl MessageHandler + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn setup_request_future(&mut self, id: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.setup_request_future(id).await; + } + + async fn handle_message(&mut self, message: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.handle_message(message).await; + } + + async fn pop_message(&mut self) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.pop_message().await + } + + async fn request_impl(&mut self, id: String) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.request_impl(id).await + } +} + +impl ClientTrait + for AsyncWebsocketClient +where + M: RawMutex, + B: Deref + AsRef<[u8]>, + E: Debug + Display, + Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, +{ + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + mut request: Req, + ) -> anyhow::Result> { + let request_id = self.set_request_id::(&mut request); + self.do_write(serde_json::to_string(&request).unwrap().as_bytes()) + .await + .unwrap(); + let mut websocket_base = self.websocket_base.lock().await; + let message = websocket_base.request_impl(request_id.to_string()).await; + let response = serde_json::from_str(&message).unwrap(); + Ok(response) + } +} diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs new file mode 100644 index 00000000..ee4660fc --- /dev/null +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -0,0 +1,213 @@ +use super::exceptions::XRPLWebsocketException; +use super::{SingleExecutorMutex, WebsocketClosed, WebsocketOpen}; +use crate::asynch::clients::client::Client; +use crate::asynch::clients::websocket::websocket_base::{MessageHandler, WebsocketBase}; +use crate::models::requests::Request; +use crate::models::results::XRPLResponse; +use crate::Err; + +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use anyhow::Result; +use core::marker::PhantomData; +use core::{pin::Pin, task::Poll}; +use embassy_futures::block_on; +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use futures::{Sink, Stream}; +use futures_util::SinkExt; +use serde::{Deserialize, Serialize}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async as tungstenite_connect_async, MaybeTlsStream as TungsteniteMaybeTlsStream, + WebSocketStream as TungsteniteWebsocketStream, +}; +use url::Url; + +pub use tokio_tungstenite::tungstenite::Message as TungsteniteMessage; + +pub type AsyncWebsocketConnection = + Arc>>>; + +pub struct AsyncWebsocketClient +where + M: RawMutex, +{ + websocket: AsyncWebsocketConnection, + websocket_base: Arc>>, + status: PhantomData, +} + +impl Sink for AsyncWebsocketClient +where + M: RawMutex, + Self: Unpin, +{ + type Error = anyhow::Error; + + fn poll_ready( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { + let mut guard = block_on(self.websocket.lock()); + // let _ = self.websocket.lock().then(|mut guard| async move { + match Pin::new(&mut *guard).start_send(TungsteniteMessage::Text(item)) { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + // }); + // Ok(()) + } + + fn poll_flush( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_flush(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } + + fn poll_close( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_close(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), + Poll::Pending => Poll::Pending, + } + } +} + +impl Stream for AsyncWebsocketClient +where + M: RawMutex, +{ + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll> { + let mut guard = block_on(self.websocket.lock()); + // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); + match Pin::new(&mut *guard).poll_next(cx) { + Poll::Ready(Some(item)) => match item { + Ok(message) => match message { + TungsteniteMessage::Text(response) => Poll::Ready(Some(Ok(response))), + TungsteniteMessage::Binary(response) => { + let response_string = String::from_utf8(response).unwrap(); + Poll::Ready(Some(Ok(response_string))) + } + _ => Poll::Ready(Some(Err!( + XRPLWebsocketException::::UnexpectedMessageType + ))), + }, + Err(error) => Poll::Ready(Some(Err!(error))), + }, + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +impl AsyncWebsocketClient +where + M: RawMutex, +{ + pub async fn open(uri: Url) -> Result> { + match tungstenite_connect_async(uri).await { + Ok((websocket_stream, _)) => Ok(AsyncWebsocketClient { + websocket: Arc::new(Mutex::new(websocket_stream)), + websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), + status: PhantomData::, + }), + Err(error) => { + Err!(XRPLWebsocketException::UnableToConnect::( + error + )) + } + } + } +} + +impl MessageHandler for AsyncWebsocketClient +where + M: RawMutex, +{ + async fn setup_request_future(&mut self, id: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.setup_request_future(id).await; + } + + async fn handle_message(&mut self, message: String) { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.handle_message(message).await; + } + + async fn pop_message(&mut self) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.pop_message().await + } + + async fn request_impl(&mut self, id: String) -> String { + let mut websocket_base = self.websocket_base.lock().await; + websocket_base.request_impl(id).await + } +} + +impl Client for AsyncWebsocketClient +where + M: RawMutex, +{ + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + mut request: Req, + ) -> Result> { + let request_id = self.set_request_id::(&mut request); + let mut websocket = self.websocket.lock().await; + websocket + .send(TungsteniteMessage::Text( + serde_json::to_string(&request).unwrap(), + )) + .await + .unwrap(); + let mut websocket_base = self.websocket_base.lock().await; + let message = websocket_base.request_impl(request_id.to_string()).await; + let response = serde_json::from_str(&message).unwrap(); + Ok(response) + } + + // async fn get_common_fields(&self) -> Result> { + // let server_state = self + // .request::(requests::ServerState::new(None)) + // .await?; + // let state = server_state.result.state; + // let common_fields = CommonFields { + // network_id: state.network_id, + // build_version: Some(state.build_version), + // }; + + // Ok(common_fields) + // } +} From 10a7057517263dbc044942d63ce276b5d1d2f54c Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:12:30 +0000 Subject: [PATCH 04/23] add codec for std embedded websocket --- src/asynch/clients/websocket/codec.rs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/asynch/clients/websocket/codec.rs diff --git a/src/asynch/clients/websocket/codec.rs b/src/asynch/clients/websocket/codec.rs new file mode 100644 index 00000000..95e9b09b --- /dev/null +++ b/src/asynch/clients/websocket/codec.rs @@ -0,0 +1,30 @@ +use alloc::{io, vec::Vec}; +use bytes::{BufMut, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; + +pub struct Codec; + +impl Decoder for Codec { + type Item = Vec; + type Error = io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result>, io::Error> { + if !src.is_empty() { + let len = src.len(); + let data = src.split_to(len).to_vec(); + Ok(Some(data)) + } else { + Ok(None) + } + } +} + +impl Encoder<&[u8]> for Codec { + type Error = io::Error; + + fn encode(&mut self, data: &[u8], buf: &mut BytesMut) -> Result<(), io::Error> { + buf.reserve(data.len()); + buf.put(data); + Ok(()) + } +} From 96c23fe2248c193a96176c54f0fa5f1ce9728b1b Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:13:47 +0000 Subject: [PATCH 05/23] move websocket exceptions --- .../clients/{ => websocket}/exceptions.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) rename src/asynch/clients/{ => websocket}/exceptions.rs (76%) diff --git a/src/asynch/clients/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs similarity index 76% rename from src/asynch/clients/exceptions.rs rename to src/asynch/clients/websocket/exceptions.rs index 220084cb..ac587c52 100644 --- a/src/asynch/clients/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -1,6 +1,8 @@ use core::fmt::Debug; use core::str::Utf8Error; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use embedded_io_async::{Error as EmbeddedIoError, ErrorKind}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_websocket::framer_async::FramerError; use thiserror_no_std::Error; @@ -18,13 +20,17 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] Disconnected, #[error("Read buffer is too small (size: {0:?})")] RxBufferTooSmall(usize), + #[error("Unexpected result type")] + UnexpectedMessageType, + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[error("Embedded I/O error: {0:?}")] + EmbeddedIoError(ErrorKind), } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -42,5 +48,15 @@ impl From> for XRPLWebsocketException { } } +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +impl EmbeddedIoError for XRPLWebsocketException { + fn kind(&self) -> ErrorKind { + match self { + XRPLWebsocketException::EmbeddedIoError(e) => e.kind(), + _ => ErrorKind::Other, + } + } +} + #[cfg(feature = "std")] impl alloc::error::Error for XRPLWebsocketException {} From 3132ceddd3d67bbba9da191e09493b5f285834b4 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:14:08 +0000 Subject: [PATCH 06/23] add WebsocketBase --- .../clients/websocket/websocket_base.rs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/asynch/clients/websocket/websocket_base.rs diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs new file mode 100644 index 00000000..6f01ffcb --- /dev/null +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -0,0 +1,81 @@ +use alloc::string::{String, ToString}; +use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; +use futures::channel::oneshot::{self, Receiver, Sender}; +use hashbrown::HashMap; + +const _MAX_CHANNEL_MSG_CNT: usize = 10; + +/// A struct that handles futures of websocket messages. +pub struct WebsocketBase +where + M: RawMutex, +{ + /// The messages the user requests, which means he is waiting for a specific `id`. + pending_requests: HashMap>, + request_senders: HashMap>, + /// The messages the user waits for when sending and receiving normally. + messages: Channel, +} + +impl WebsocketBase +where + M: RawMutex, +{ + pub fn new() -> Self { + Self { + pending_requests: HashMap::new(), + request_senders: HashMap::new(), + messages: Channel::new(), + } + } +} + +pub(crate) trait MessageHandler { + /// Setup an empty future for a request. + async fn setup_request_future(&mut self, id: String); + async fn handle_message(&mut self, message: String); + async fn pop_message(&mut self) -> String; + async fn request_impl(&mut self, id: String) -> String; +} + +impl MessageHandler for WebsocketBase +where + M: RawMutex, +{ + async fn setup_request_future(&mut self, id: String) { + let (sender, receiver) = oneshot::channel::(); + self.pending_requests.insert(id.clone(), receiver); + self.request_senders.insert(id, sender); + } + + async fn handle_message(&mut self, message: String) { + let message_value = serde_json::to_value(&message).unwrap(); + let id = match message_value.get("id") { + Some(id) => { + let id = id.as_str().unwrap().to_string(); + if id == String::new() { + todo!("`id` must not be an empty string") + } + id + } + None => String::new(), + }; + if let Some(_receiver) = self.pending_requests.get(&id) { + let sender = self.request_senders.remove(&id).unwrap(); + sender.send(message).unwrap(); + } else { + self.messages.send(message).await; + } + } + + async fn pop_message(&mut self) -> String { + self.messages.receive().await + } + + async fn request_impl(&mut self, id: String) -> String { + self.setup_request_future(id.clone()).await; + let fut = self.pending_requests.remove(&id).unwrap(); + let message = fut.await.unwrap(); + serde_json::from_str(&message).unwrap() + } +} From 0f507a8c768f25f643ddd3a2da0cf5854b57b5f5 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:14:38 +0000 Subject: [PATCH 07/23] add Client and AsyncClient traits --- src/asynch/clients/async_client.rs | 18 +++++++++++++++ src/asynch/clients/client.rs | 36 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/asynch/clients/async_client.rs create mode 100644 src/asynch/clients/client.rs diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs new file mode 100644 index 00000000..0b01e8c5 --- /dev/null +++ b/src/asynch/clients/async_client.rs @@ -0,0 +1,18 @@ +use super::client::Client; +use crate::models::{requests::Request, results::XRPLResponse}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +pub trait AsyncClient: Client { + async fn request< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + request: Req, + ) -> Result> { + self.request_impl(request).await + } +} + +impl AsyncClient for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs new file mode 100644 index 00000000..c1ce3c24 --- /dev/null +++ b/src/asynch/clients/client.rs @@ -0,0 +1,36 @@ +use crate::{ + models::{requests::Request, results::XRPLResponse}, + utils::get_random_id, +}; +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +pub(crate) trait Client { + async fn request_impl< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + request: Req, + ) -> Result>; + fn set_request_id< + Res: Serialize + for<'de> Deserialize<'de>, + Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + >( + &self, + request: &mut Req, + ) -> Cow<'_, str> { + let common_fields = request.get_common_fields(); + let request_id: Cow<'_, str> = match common_fields.id.clone() { + Some(id) => id, + None => { + #[cfg(feature = "std")] + let mut rng = rand::thread_rng(); + Cow::Owned(get_random_id(&mut rng)) + } + }; + request.get_common_fields_mut().id = Some(request_id.clone()); + request_id + } +} From 9199ad2e6ddf835a3ec4981665c5d5297fe76ab7 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:15:33 +0000 Subject: [PATCH 08/23] add XRPLWebsocketIO as standardized trait --- src/asynch/clients/websocket/mod.rs | 124 ++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/asynch/clients/websocket/mod.rs diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs new file mode 100644 index 00000000..6ef8fe55 --- /dev/null +++ b/src/asynch/clients/websocket/mod.rs @@ -0,0 +1,124 @@ +use core::fmt::{Debug, Display}; + +use crate::{models::results::XRPLResponse, Err}; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use alloc::string::String; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use alloc::string::ToString; +use anyhow::Result; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +use futures::{Sink, SinkExt, Stream, StreamExt}; +use serde::{Deserialize, Serialize}; + +mod websocket_base; +use websocket_base::MessageHandler; + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +pub mod codec; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +mod embedded_websocket; +pub mod exceptions; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +mod tungstenite; + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub use embedded_websocket::AsyncWebsocketClient; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tungstenite::AsyncWebsocketClient; + +pub struct WebsocketOpen; +pub struct WebsocketClosed; + +pub type MultiExecutorMutex = CriticalSectionRawMutex; +pub type SingleExecutorMutex = NoopRawMutex; + +#[allow(async_fn_in_trait)] +pub trait XRPLWebsocketIO { + async fn xrpl_send(&mut self, message: Req) -> Result<()>; + async fn xrpl_receive< + Res: Serialize + for<'de> Deserialize<'de> + Debug, + Req: Serialize + for<'de> Deserialize<'de> + Debug, + >( + &mut self, + ) -> Result>; +} + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +impl XRPLWebsocketIO for T +where + ::Error: Display, +{ + async fn xrpl_send(&mut self, message: Req) -> Result<()> { + let message = serde_json::to_string(&message).unwrap(); + let message_buffer = message.as_bytes(); + match self.write(message_buffer).await { + Ok(_) => Ok(()), + Err(e) => Err!(e), + } + } + + async fn xrpl_receive< + Res: Serialize + for<'de> Deserialize<'de> + Debug, + Req: Serialize + for<'de> Deserialize<'de> + Debug, + >( + &mut self, + ) -> Result> { + let mut buffer = [0; 1024]; + loop { + match self.read(&mut buffer).await { + Ok(u_size) => { + // If the buffer is empty, continue to the next iteration. + if u_size == 0 { + continue; + } + let response_str = core::str::from_utf8(&buffer[..u_size]).unwrap(); + self.handle_message(response_str.to_string()).await; + let message = self.pop_message().await; + dbg!(&message); + let response = serde_json::from_str(&message).unwrap(); + return Ok(response); + } + Err(error) => return Err!(error), + } + } + } +} + +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +impl XRPLWebsocketIO for T +where + T: Stream> + Sink + MessageHandler + Unpin, + >::Error: Debug + Display, +{ + async fn xrpl_send(&mut self, message: Req) -> Result<()> { + let message = serde_json::to_string(&message).unwrap(); + match self.send(message).await { + Ok(()) => Ok(()), + Err(error) => Err!(error), + } + } + + async fn xrpl_receive< + Res: Serialize + for<'de> Deserialize<'de> + Debug, + Req: Serialize + for<'de> Deserialize<'de> + Debug, + >( + &mut self, + ) -> Result> { + match self.next().await { + Some(Ok(item)) => { + let xrpl_response = serde_json::from_str(&item).unwrap(); + self.handle_message(xrpl_response).await; + let message = self.pop_message().await; + let response = serde_json::from_str(&message).unwrap(); + Ok(response) + } + Some(Err(error)) => Err!(error), + None => { + todo!() + } + } + } +} From 4523e42d28a8ca370e651586f253b9350372f5d4 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:16:19 +0000 Subject: [PATCH 09/23] adjust clients mod --- src/asynch/clients/mod.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 54eb0495..746be0ce 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -1,14 +1,5 @@ -pub mod exceptions; +mod async_client; +mod client; +mod websocket; -pub struct WebsocketOpen; -pub struct WebsocketClosed; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -mod embedded_websocket; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -mod tungstenite; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub use embedded_websocket::*; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub use tungstenite::*; +pub use websocket::*; From 57e916f47ac17de9bfbad32f84f73ddb714fe024 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:17:03 +0000 Subject: [PATCH 10/23] add XRPLResponse --- src/models/mod.rs | 2 ++ src/models/results/mod.rs | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/models/results/mod.rs diff --git a/src/models/mod.rs b/src/models/mod.rs index 7cd5e1dc..6d9c5ebd 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -14,6 +14,8 @@ pub mod model; #[cfg(feature = "requests")] #[allow(clippy::too_many_arguments)] pub mod requests; +#[cfg(feature = "results")] +pub mod results; #[cfg(feature = "transactions")] #[allow(clippy::too_many_arguments)] pub mod transactions; diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs new file mode 100644 index 00000000..d7bc4d20 --- /dev/null +++ b/src/models/results/mod.rs @@ -0,0 +1,46 @@ +use alloc::{borrow::Cow, vec::Vec}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ResponseStatus { + Success, + Error, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum ResponseType { + Response, + LedgerClosed, + Transaction, +} + +/// TODO: Because everything is optional, the deserializing always succeds without returning an error on false data. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLResponse<'a, Res, Req> { + pub id: Option>, + pub error: Option>, + pub error_code: Option, + pub error_message: Option>, + pub forwarded: Option, + pub request: Option, + pub result: Option, + pub status: Option, + pub r#type: Option, + pub warning: Option>, + pub warnings: Option>>, +} + +impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { + pub fn is_success(&self) -> bool { + self.status == Some(ResponseStatus::Success) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct XRPLWarning<'a> { + pub id: Cow<'a, str>, + pub message: Cow<'a, str>, + pub forwarded: Option, +} From 1d49e57ce22f966fc28fdf08718d15a651d1c157 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:18:50 +0000 Subject: [PATCH 11/23] revise the Request trait to have methods to get a resut models common fields --- src/models/requests/account_channels.rs | 12 +++++++++++- src/models/requests/account_currencies.rs | 10 +++++++--- src/models/requests/account_info.rs | 10 +++++++--- src/models/requests/account_lines.rs | 10 +++++++--- src/models/requests/account_nfts.rs | 10 +++++++--- src/models/requests/account_objects.rs | 10 +++++++--- src/models/requests/account_offers.rs | 10 +++++++--- src/models/requests/account_tx.rs | 10 +++++++--- src/models/requests/book_offers.rs | 10 +++++++--- src/models/requests/channel_authorize.rs | 10 +++++++--- src/models/requests/channel_verify.rs | 10 +++++++--- src/models/requests/deposit_authorize.rs | 10 +++++++--- src/models/requests/fee.rs | 10 +++++++--- src/models/requests/gateway_balances.rs | 10 +++++++--- src/models/requests/ledger.rs | 10 +++++++--- src/models/requests/ledger_closed.rs | 10 +++++++--- src/models/requests/ledger_current.rs | 10 +++++++--- src/models/requests/ledger_data.rs | 10 +++++++--- src/models/requests/ledger_entry.rs | 10 +++++++--- src/models/requests/manifest.rs | 10 +++++++--- src/models/requests/mod.rs | 11 +++-------- src/models/requests/nft_buy_offers.rs | 10 +++++++--- src/models/requests/nft_sell_offers.rs | 10 +++++++--- src/models/requests/no_ripple_check.rs | 10 +++++++--- src/models/requests/path_find.rs | 10 +++++++--- src/models/requests/ping.rs | 10 +++++++--- src/models/requests/random.rs | 10 +++++++--- src/models/requests/ripple_path_find.rs | 10 +++++++--- src/models/requests/server_info.rs | 10 +++++++--- src/models/requests/server_state.rs | 10 +++++++--- src/models/requests/submit.rs | 10 +++++++--- src/models/requests/submit_multisigned.rs | 10 +++++++--- src/models/requests/subscribe.rs | 10 +++++++--- src/models/requests/transaction_entry.rs | 10 +++++++--- src/models/requests/tx.rs | 10 +++++++--- src/models/requests/unsubscribe.rs | 10 +++++++--- 36 files changed, 252 insertions(+), 111 deletions(-) diff --git a/src/models/requests/account_channels.rs b/src/models/requests/account_channels.rs index 5965eb52..df0753ed 100644 --- a/src/models/requests/account_channels.rs +++ b/src/models/requests/account_channels.rs @@ -4,7 +4,7 @@ use serde_with::skip_serializing_none; use crate::models::{requests::RequestMethod, Model}; -use super::CommonFields; +use super::{CommonFields, Request}; /// This request returns information about an account's Payment /// Channels. This includes only channels where the specified @@ -85,3 +85,13 @@ impl<'a> AccountChannels<'a> { } } } + +impl<'a> Request<'a> for AccountChannels<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields + } +} diff --git a/src/models/requests/account_currencies.rs b/src/models/requests/account_currencies.rs index c6307543..4ff2e373 100644 --- a/src/models/requests/account_currencies.rs +++ b/src/models/requests/account_currencies.rs @@ -37,9 +37,13 @@ pub struct AccountCurrencies<'a> { impl<'a> Model for AccountCurrencies<'a> {} -impl<'a> Request for AccountCurrencies<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountCurrencies<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_info.rs b/src/models/requests/account_info.rs index 1db06aa4..84d8f42d 100644 --- a/src/models/requests/account_info.rs +++ b/src/models/requests/account_info.rs @@ -44,9 +44,13 @@ pub struct AccountInfo<'a> { impl<'a> Model for AccountInfo<'a> {} -impl<'a> Request for AccountInfo<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountInfo<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_lines.rs b/src/models/requests/account_lines.rs index 432f74d4..3571cf2a 100644 --- a/src/models/requests/account_lines.rs +++ b/src/models/requests/account_lines.rs @@ -38,9 +38,13 @@ pub struct AccountLines<'a> { impl<'a> Model for AccountLines<'a> {} -impl<'a> Request for AccountLines<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountLines<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_nfts.rs b/src/models/requests/account_nfts.rs index 2447f3af..a9e79047 100644 --- a/src/models/requests/account_nfts.rs +++ b/src/models/requests/account_nfts.rs @@ -29,9 +29,13 @@ pub struct AccountNfts<'a> { impl<'a> Model for AccountNfts<'a> {} -impl<'a> Request for AccountNfts<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountNfts<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_objects.rs b/src/models/requests/account_objects.rs index c51bcc4f..d78c4381 100644 --- a/src/models/requests/account_objects.rs +++ b/src/models/requests/account_objects.rs @@ -63,9 +63,13 @@ pub struct AccountObjects<'a> { impl<'a> Model for AccountObjects<'a> {} -impl<'a> Request for AccountObjects<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountObjects<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_offers.rs b/src/models/requests/account_offers.rs index 5e2cfb02..647d3f3e 100644 --- a/src/models/requests/account_offers.rs +++ b/src/models/requests/account_offers.rs @@ -40,9 +40,13 @@ pub struct AccountOffers<'a> { impl<'a> Model for AccountOffers<'a> {} -impl<'a> Request for AccountOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/account_tx.rs b/src/models/requests/account_tx.rs index 48da5afd..1f16e80a 100644 --- a/src/models/requests/account_tx.rs +++ b/src/models/requests/account_tx.rs @@ -53,9 +53,13 @@ pub struct AccountTx<'a> { impl<'a> Model for AccountTx<'a> {} -impl<'a> Request for AccountTx<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for AccountTx<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/book_offers.rs b/src/models/requests/book_offers.rs index 57887d38..ca27a3d5 100644 --- a/src/models/requests/book_offers.rs +++ b/src/models/requests/book_offers.rs @@ -46,9 +46,13 @@ pub struct BookOffers<'a> { impl<'a> Model for BookOffers<'a> {} -impl<'a> Request for BookOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for BookOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/channel_authorize.rs b/src/models/requests/channel_authorize.rs index 835cdd7e..466fa425 100644 --- a/src/models/requests/channel_authorize.rs +++ b/src/models/requests/channel_authorize.rs @@ -80,9 +80,13 @@ impl<'a> Model for ChannelAuthorize<'a> { } } -impl<'a> Request for ChannelAuthorize<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ChannelAuthorize<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/channel_verify.rs b/src/models/requests/channel_verify.rs index 949b6ad1..59e814a9 100644 --- a/src/models/requests/channel_verify.rs +++ b/src/models/requests/channel_verify.rs @@ -30,9 +30,13 @@ pub struct ChannelVerify<'a> { impl<'a> Model for ChannelVerify<'a> {} -impl<'a> Request for ChannelVerify<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ChannelVerify<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/deposit_authorize.rs b/src/models/requests/deposit_authorize.rs index 5204bc69..2c58e473 100644 --- a/src/models/requests/deposit_authorize.rs +++ b/src/models/requests/deposit_authorize.rs @@ -30,9 +30,13 @@ pub struct DepositAuthorized<'a> { impl<'a> Model for DepositAuthorized<'a> {} -impl<'a> Request for DepositAuthorized<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for DepositAuthorized<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/fee.rs b/src/models/requests/fee.rs index 2ab0ee8a..05052d96 100644 --- a/src/models/requests/fee.rs +++ b/src/models/requests/fee.rs @@ -23,9 +23,13 @@ pub struct Fee<'a> { impl<'a> Model for Fee<'a> {} -impl<'a> Request for Fee<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Fee<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/gateway_balances.rs b/src/models/requests/gateway_balances.rs index 58dc61e7..e5f38386 100644 --- a/src/models/requests/gateway_balances.rs +++ b/src/models/requests/gateway_balances.rs @@ -36,9 +36,13 @@ pub struct GatewayBalances<'a> { impl<'a> Model for GatewayBalances<'a> {} -impl<'a> Request for GatewayBalances<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for GatewayBalances<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger.rs b/src/models/requests/ledger.rs index 7ba5faec..d567cd41 100644 --- a/src/models/requests/ledger.rs +++ b/src/models/requests/ledger.rs @@ -58,9 +58,13 @@ pub struct Ledger<'a> { impl<'a> Model for Ledger<'a> {} -impl<'a> Request for Ledger<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Ledger<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_closed.rs b/src/models/requests/ledger_closed.rs index c65f486c..7b601ea6 100644 --- a/src/models/requests/ledger_closed.rs +++ b/src/models/requests/ledger_closed.rs @@ -22,9 +22,13 @@ pub struct LedgerClosed<'a> { impl<'a> Model for LedgerClosed<'a> {} -impl<'a> Request for LedgerClosed<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerClosed<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_current.rs b/src/models/requests/ledger_current.rs index 9b408ee6..aec482f6 100644 --- a/src/models/requests/ledger_current.rs +++ b/src/models/requests/ledger_current.rs @@ -22,9 +22,13 @@ pub struct LedgerCurrent<'a> { impl<'a> Model for LedgerCurrent<'a> {} -impl<'a> Request for LedgerCurrent<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerCurrent<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_data.rs b/src/models/requests/ledger_data.rs index c71e012c..c30bb753 100644 --- a/src/models/requests/ledger_data.rs +++ b/src/models/requests/ledger_data.rs @@ -36,9 +36,13 @@ pub struct LedgerData<'a> { impl<'a> Model for LedgerData<'a> {} -impl<'a> Request for LedgerData<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerData<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ledger_entry.rs b/src/models/requests/ledger_entry.rs index 1dbc4c1d..0022437c 100644 --- a/src/models/requests/ledger_entry.rs +++ b/src/models/requests/ledger_entry.rs @@ -159,9 +159,13 @@ impl<'a> LedgerEntryError for LedgerEntry<'a> { } } -impl<'a> Request for LedgerEntry<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for LedgerEntry<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/manifest.rs b/src/models/requests/manifest.rs index d2615195..e9f38d94 100644 --- a/src/models/requests/manifest.rs +++ b/src/models/requests/manifest.rs @@ -27,9 +27,13 @@ pub struct Manifest<'a> { impl<'a> Model for Manifest<'a> {} -impl<'a> Request for Manifest<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Manifest<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/mod.rs b/src/models/requests/mod.rs index 860b7504..3d81058f 100644 --- a/src/models/requests/mod.rs +++ b/src/models/requests/mod.rs @@ -148,14 +148,9 @@ pub struct CommonFields<'a> { pub id: Option>, } -impl Request for CommonFields<'_> { - fn get_command(&self) -> RequestMethod { - self.command.clone() - } -} - /// The base trait for all request models. /// Used to identify the model as a request. -pub trait Request { - fn get_command(&self) -> RequestMethod; +pub trait Request<'a> { + fn get_common_fields(&self) -> &CommonFields<'a>; + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a>; } diff --git a/src/models/requests/nft_buy_offers.rs b/src/models/requests/nft_buy_offers.rs index 3e9ce276..d8a4c4c5 100644 --- a/src/models/requests/nft_buy_offers.rs +++ b/src/models/requests/nft_buy_offers.rs @@ -31,9 +31,13 @@ pub struct NftBuyOffers<'a> { impl<'a> Model for NftBuyOffers<'a> {} -impl<'a> Request for NftBuyOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NftBuyOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/nft_sell_offers.rs b/src/models/requests/nft_sell_offers.rs index 7404d59c..fa4dd6f9 100644 --- a/src/models/requests/nft_sell_offers.rs +++ b/src/models/requests/nft_sell_offers.rs @@ -19,9 +19,13 @@ pub struct NftSellOffers<'a> { impl<'a> Model for NftSellOffers<'a> {} -impl<'a> Request for NftSellOffers<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NftSellOffers<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/no_ripple_check.rs b/src/models/requests/no_ripple_check.rs index f2467efa..21bffb7f 100644 --- a/src/models/requests/no_ripple_check.rs +++ b/src/models/requests/no_ripple_check.rs @@ -58,9 +58,13 @@ pub struct NoRippleCheck<'a> { impl<'a> Model for NoRippleCheck<'a> {} -impl<'a> Request for NoRippleCheck<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for NoRippleCheck<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/path_find.rs b/src/models/requests/path_find.rs index e2ab7074..55b34865 100644 --- a/src/models/requests/path_find.rs +++ b/src/models/requests/path_find.rs @@ -90,9 +90,13 @@ pub struct PathFind<'a> { impl<'a> Model for PathFind<'a> {} -impl<'a> Request for PathFind<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for PathFind<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ping.rs b/src/models/requests/ping.rs index 079b81be..5c70e863 100644 --- a/src/models/requests/ping.rs +++ b/src/models/requests/ping.rs @@ -21,9 +21,13 @@ pub struct Ping<'a> { impl<'a> Model for Ping<'a> {} -impl<'a> Request for Ping<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Ping<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/random.rs b/src/models/requests/random.rs index 8bc0029f..19c7e986 100644 --- a/src/models/requests/random.rs +++ b/src/models/requests/random.rs @@ -22,9 +22,13 @@ pub struct Random<'a> { impl<'a> Model for Random<'a> {} -impl<'a> Request for Random<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Random<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/ripple_path_find.rs b/src/models/requests/ripple_path_find.rs index ac923963..4f7f5cfd 100644 --- a/src/models/requests/ripple_path_find.rs +++ b/src/models/requests/ripple_path_find.rs @@ -62,9 +62,13 @@ pub struct RipplePathFind<'a> { impl<'a> Model for RipplePathFind<'a> {} -impl<'a> Request for RipplePathFind<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for RipplePathFind<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/server_info.rs b/src/models/requests/server_info.rs index 378fc0f0..7044ee5d 100644 --- a/src/models/requests/server_info.rs +++ b/src/models/requests/server_info.rs @@ -22,9 +22,13 @@ pub struct ServerInfo<'a> { impl<'a> Model for ServerInfo<'a> {} -impl<'a> Request for ServerInfo<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ServerInfo<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/server_state.rs b/src/models/requests/server_state.rs index 4a0b7306..85c910c5 100644 --- a/src/models/requests/server_state.rs +++ b/src/models/requests/server_state.rs @@ -27,9 +27,13 @@ pub struct ServerState<'a> { impl<'a> Model for ServerState<'a> {} -impl<'a> Request for ServerState<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for ServerState<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/submit.rs b/src/models/requests/submit.rs index 132a9910..e68dec41 100644 --- a/src/models/requests/submit.rs +++ b/src/models/requests/submit.rs @@ -49,9 +49,13 @@ pub struct Submit<'a> { impl<'a> Model for Submit<'a> {} -impl<'a> Request for Submit<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Submit<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/submit_multisigned.rs b/src/models/requests/submit_multisigned.rs index 82fdc1cc..913539c9 100644 --- a/src/models/requests/submit_multisigned.rs +++ b/src/models/requests/submit_multisigned.rs @@ -30,9 +30,13 @@ pub struct SubmitMultisigned<'a> { impl<'a> Model for SubmitMultisigned<'a> {} -impl<'a> Request for SubmitMultisigned<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for SubmitMultisigned<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/subscribe.rs b/src/models/requests/subscribe.rs index 0487ca22..fb501f87 100644 --- a/src/models/requests/subscribe.rs +++ b/src/models/requests/subscribe.rs @@ -77,9 +77,13 @@ pub struct Subscribe<'a> { impl<'a> Model for Subscribe<'a> {} -impl<'a> Request for Subscribe<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Subscribe<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/transaction_entry.rs b/src/models/requests/transaction_entry.rs index 24ad02cb..8f3c9885 100644 --- a/src/models/requests/transaction_entry.rs +++ b/src/models/requests/transaction_entry.rs @@ -31,9 +31,13 @@ pub struct TransactionEntry<'a> { impl<'a> Model for TransactionEntry<'a> {} -impl<'a> Request for TransactionEntry<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for TransactionEntry<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/tx.rs b/src/models/requests/tx.rs index 0dd547ee..29b88373 100644 --- a/src/models/requests/tx.rs +++ b/src/models/requests/tx.rs @@ -34,9 +34,13 @@ pub struct Tx<'a> { impl<'a> Model for Tx<'a> {} -impl<'a> Request for Tx<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Tx<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } diff --git a/src/models/requests/unsubscribe.rs b/src/models/requests/unsubscribe.rs index 90f123d0..b056b688 100644 --- a/src/models/requests/unsubscribe.rs +++ b/src/models/requests/unsubscribe.rs @@ -62,9 +62,13 @@ pub struct Unsubscribe<'a> { impl<'a> Model for Unsubscribe<'a> {} -impl<'a> Request for Unsubscribe<'a> { - fn get_command(&self) -> RequestMethod { - self.common_fields.command.clone() +impl<'a> Request<'a> for Unsubscribe<'a> { + fn get_common_fields(&self) -> &CommonFields<'a> { + &self.common_fields + } + + fn get_common_fields_mut(&mut self) -> &mut CommonFields<'a> { + &mut self.common_fields } } From dc8161a246ad0c6a912cbe52ca135629889b5f97 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:19:08 +0000 Subject: [PATCH 12/23] cargo fmt --- src/models/transactions/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 57ad9163..647dc3b5 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -54,10 +54,7 @@ pub use ticket_create::*; pub use trust_set::*; use crate::models::amount::XRPAmount; -use crate::{ - _serde::txn_flags, - serde_with_tag, -}; +use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; use alloc::string::String; use alloc::vec::Vec; From d3b2d16ff19e173af95a6f35ecd6b8090a3cf124 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:19:40 +0000 Subject: [PATCH 13/23] add get_random_id utility function --- src/utils/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 41508f7c..9fff8a72 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,7 +8,10 @@ pub use self::time_conversion::*; pub use self::xrpl_conversion::*; use crate::constants::*; +use alloc::string::String; +use alloc::string::ToString; use alloc::vec::Vec; +use rand::Rng; use regex::Regex; /// Determine if the address string is a hex address. @@ -65,6 +68,12 @@ pub fn is_iso_hex(value: &str) -> bool { regex.is_match(value) } +/// Generate a random id. +pub fn get_random_id(rng: &mut T) -> String { + let id: u32 = rng.gen(); + id.to_string() +} + /// Converter to byte array with endianness. pub trait ToBytes { /// Return the byte array of self. From 19c06962848ce5be1443461c1bf42cfbe3257848 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:20:07 +0000 Subject: [PATCH 14/23] add xrpl testnet uri --- tests/common/constants.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/common/constants.rs b/tests/common/constants.rs index fb33485a..a2ba78a8 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -1,2 +1,4 @@ -pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror/"; -pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror/"; +pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; +pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; + +pub const XRPL_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; From b9b57e246c73f7f8b8dce7c346fb59dea0ac30d6 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sat, 8 Jun 2024 23:20:42 +0000 Subject: [PATCH 15/23] revise the tests to work with the changes --- tests/common/mod.rs | 46 ++++++++--------- tests/integration/clients/mod.rs | 84 +++++++++----------------------- 2 files changed, 46 insertions(+), 84 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1c406bcd..dc7e14be 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,27 +1,27 @@ -pub mod codec; - use anyhow::anyhow; use anyhow::Result; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use std::io; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use tokio::net::TcpStream; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use tokio_util::codec::Framed; -#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -use xrpl::asynch::clients::AsyncWebsocketClient; -use xrpl::asynch::clients::WebsocketOpen; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use xrpl::asynch::clients::{AsyncWebsocketClient, EmbeddedWebsocketOptions}; +use xrpl::asynch::clients::codec::Codec; +use xrpl::asynch::clients::AsyncWebsocketClient; +use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; mod constants; pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_echo() -> Result> { +pub async fn connect_to_wss_tungstinite_echo( +) -> Result> { match ECHO_WSS_SERVER.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { - assert!(websocket.is_open()); + // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), @@ -32,21 +32,23 @@ pub async fn connect_to_wss_tungstinite_echo() -> Result, - buffer: &mut [u8], -) -> Result> { - let rng = rand::thread_rng(); - let websocket_options = EmbeddedWebsocketOptions { - path: "/mirror", - host: "ws.vi-server.org", - origin: "http://ws.vi-server.org:80", - sub_protocols: None, - additional_headers: None, - }; - - match AsyncWebsocketClient::open(stream, buffer, rng, &websocket_options).await { + stream: Framed, +) -> Result< + AsyncWebsocketClient< + 4096, + Framed, + Vec, + io::Error, + rand_core::OsRng, + SingleExecutorMutex, + WebsocketOpen, + >, +> { + let rng = rand_core::OsRng; + let url = XRPL_TEST_NET.parse().unwrap(); + match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { - assert!(websocket.is_open()); + // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 9a76a87d..8dc535e5 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,12 +1,9 @@ -use anyhow::anyhow; use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_echo() -> Result<()> { use super::common::connect_to_wss_tungstinite_echo; - use futures_util::{SinkExt, TryStreamExt}; - use xrpl::asynch::clients::TungsteniteMessage; - use xrpl::models::requests::AccountInfo; + use xrpl::{asynch::clients::XRPLWebsocketIO, models::requests::AccountInfo}; let mut websocket = connect_to_wss_tungstinite_echo().await?; let account_info = AccountInfo::new( @@ -19,37 +16,29 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { None, ); - websocket.send(&account_info).await?; - while let Ok(Some(TungsteniteMessage::Text(response))) = websocket.try_next().await { - let account_info_echo = serde_json::from_str::(response.as_str()); - match account_info_echo { - Ok(account_info_echo) => { - assert_eq!(account_info, account_info_echo); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("Error parsing response: {:?}", err)); - } - }; + websocket.xrpl_send(account_info).await.unwrap(); + loop { + let t = websocket + .xrpl_receive::, AccountInfo<'_>>() + .await + .unwrap(); + dbg!("{:?}", t); } - - Ok(()) } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_echo() -> Result<()> { - use super::common::{codec::Codec, connect_to_ws_embedded_websocket_tokio_echo}; + use super::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; - use xrpl::asynch::clients::EmbeddedWebsocketReadMessageType; + use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::XRPLWebsocketIO; use xrpl::models::requests::AccountInfo; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await - .map_err(|_| anyhow!("Error connecting to websocket"))?; - let mut framed = Framed::new(tcp_stream, Codec::new()); - let mut buffer = [0u8; 4096]; - let mut websocket = - connect_to_ws_embedded_websocket_tokio_echo(&mut framed, &mut buffer).await?; + .unwrap(); + let mut framed = Framed::new(tcp_stream, Codec); + let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; let account_info = AccountInfo::new( None, "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), @@ -59,42 +48,13 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { None, None, ); - websocket - .send(&mut framed, &mut buffer, false, &account_info) - .await?; + websocket.xrpl_send(account_info).await?; - let mut ping_counter = 0; - loop { - match websocket.try_next(&mut framed, &mut buffer).await? { - Some(message) => match message { - EmbeddedWebsocketReadMessageType::Ping(_) => { - ping_counter += 1; - if ping_counter > 1 { - return Err(anyhow!("Expected only one ping")); - } - } - EmbeddedWebsocketReadMessageType::Text(text) => { - match serde_json::from_str::(text) { - Ok(account_info_echo) => { - assert_eq!(account_info, account_info_echo); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("Error parsing response: {:?}", err)); - } - } - } - EmbeddedWebsocketReadMessageType::Binary(_) => { - panic!("Expected text message found binary") - } - EmbeddedWebsocketReadMessageType::Pong(_) => { - panic!("Expected text message found pong") - } - EmbeddedWebsocketReadMessageType::Close(_) => { - panic!("Expected text message found close") - } - }, - None => return Err(anyhow!("No message received")), - } - } + let t = websocket + .xrpl_receive::, AccountInfo<'_>>() + .await + .unwrap(); + dbg!("{:?}", t); + panic!(); + Ok(()) } From 7f2590dc5300f703693fe1351cae33ec745329e7 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 9 Jun 2024 09:43:22 +0000 Subject: [PATCH 16/23] error handling --- .../clients/websocket/embedded_websocket.rs | 62 +++++++++++-------- src/asynch/clients/websocket/exceptions.rs | 6 ++ src/asynch/clients/websocket/mod.rs | 40 +++++++----- src/asynch/clients/websocket/tungstenite.rs | 40 ++++++++---- .../clients/websocket/websocket_base.rs | 49 ++++++++++----- tests/common/constants.rs | 1 + tests/common/mod.rs | 4 +- tests/integration/clients/mod.rs | 20 +++--- 8 files changed, 138 insertions(+), 84 deletions(-) diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 83fef699..9bc43fcb 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -1,21 +1,20 @@ use core::{ fmt::{Debug, Display}, - iter::FromIterator, marker::PhantomData, ops::{Deref, DerefMut}, }; use alloc::{ - dbg, string::{String, ToString}, sync::Arc, }; +use anyhow::Result; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_io_async::{ErrorType, Read, Write}; use embedded_websocket::{ - framer_async::Framer, framer_async::ReadResult, Client, WebSocketClient, WebSocketOptions, - WebSocketSendMessageType, + framer_async::{Framer, FramerError, ReadResult}, + Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; use futures_core::Stream; use futures_sink::Sink; @@ -29,7 +28,8 @@ use crate::{ client::Client as ClientTrait, websocket::websocket_base::{MessageHandler, WebsocketBase}, }, - models::requests::Request, + models::{requests::Request, results::XRPLResponse}, + Err, }; use super::exceptions::XRPLWebsocketException; @@ -66,10 +66,7 @@ where rng: Rng, tcp: Tcp, url: Url, - ) -> Result< - AsyncWebsocketClient, - XRPLWebsocketException, - > { + ) -> Result> { // replace the scheme with http or https let scheme = match url.scheme() { "wss" => "https", @@ -86,7 +83,10 @@ where } .to_string(); let path = url.path(); - let host = url.host_str().unwrap(); + let host = match url.host_str() { + Some(host) => host, + None => return Err!(XRPLWebsocketException::::Disconnected), + }; let origin = scheme.to_string() + "://" + host + ":" + &port + path; let websocket_options = WebSocketOptions { path, @@ -95,14 +95,10 @@ where sub_protocols: None, additional_headers: None, }; - // dbg!(&websocket_options.path); - // dbg!(&websocket_options.host); - // dbg!(&websocket_options.origin); - // panic!(); let websocket = Arc::new(Mutex::new(Framer::new(WebSocketClient::new_client(rng)))); let tcp = Arc::new(Mutex::new(tcp)); let mut buffer = [0; BUF]; - websocket + if let Err(error) = websocket .lock() .await .connect( @@ -111,12 +107,19 @@ where &websocket_options, ) .await - .unwrap(); + { + match error { + // FramerError::WebSocket(embedded_websocket::Error::HttpResponseCodeInvalid( + // Some(308), + // )) => (), + error => return Err!(XRPLWebsocketException::from(error)), + } + } Ok(AsyncWebsocketClient { tcp, websocket, - tx_buffer: [0; BUF], + tx_buffer: buffer, websocket_base: Arc::new(Mutex::new(WebsocketBase::new())), status: PhantomData::, }) @@ -210,9 +213,9 @@ where websocket_base.setup_request_future(id).await; } - async fn handle_message(&mut self, message: String) { + async fn handle_message(&mut self, message: String) -> Result<()> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.handle_message(message).await; + websocket_base.handle_message(message).await } async fn pop_message(&mut self) -> String { @@ -220,7 +223,7 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> String { + async fn request_impl(&mut self, id: String) -> Result { let mut websocket_base = self.websocket_base.lock().await; websocket_base.request_impl(id).await } @@ -240,14 +243,21 @@ where >( &self, mut request: Req, - ) -> anyhow::Result> { + ) -> Result> { let request_id = self.set_request_id::(&mut request); - self.do_write(serde_json::to_string(&request).unwrap().as_bytes()) - .await - .unwrap(); + let request_string = match serde_json::to_string(&request) { + Ok(request_string) => request_string, + Err(error) => return Err!(error), + }; + if let Err(error) = self.do_write(request_string.as_bytes()).await { + return Err!(error); + } let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await; - let response = serde_json::from_str(&message).unwrap(); + let message = websocket_base.request_impl(request_id.to_string()).await?; + let response = match serde_json::from_str(&message) { + Ok(response) => response, + Err(error) => return Err!(error), + }; Ok(response) } } diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index ac587c52..47102697 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -31,6 +31,12 @@ pub enum XRPLWebsocketException { #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Embedded I/O error: {0:?}")] EmbeddedIoError(ErrorKind), + #[error("Missing request channel sender.")] + MissingRequestSender, + #[error("Missing request channel receiver.")] + MissingRequestReceiver, + #[error("Invalid message.")] + InvalidMessage, } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 6ef8fe55..a4aebbd5 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -9,6 +9,7 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -52,7 +53,10 @@ where ::Error: Display, { async fn xrpl_send(&mut self, message: Req) -> Result<()> { - let message = serde_json::to_string(&message).unwrap(); + let message = match serde_json::to_string(&message) { + Ok(message) => message, + Err(error) => return Err!(error), + }; let message_buffer = message.as_bytes(); match self.write(message_buffer).await { Ok(_) => Ok(()), @@ -74,12 +78,18 @@ where if u_size == 0 { continue; } - let response_str = core::str::from_utf8(&buffer[..u_size]).unwrap(); - self.handle_message(response_str.to_string()).await; + let response_str = match core::str::from_utf8(&buffer[..u_size]) { + Ok(response_str) => response_str, + Err(error) => { + return Err!(XRPLWebsocketException::::Utf8(error)) + } + }; + self.handle_message(response_str.to_string()).await?; let message = self.pop_message().await; - dbg!(&message); - let response = serde_json::from_str(&message).unwrap(); - return Ok(response); + match serde_json::from_str(&message) { + Ok(response) => return Ok(response), + Err(error) => return Err!(error), + } } Err(error) => return Err!(error), } @@ -94,7 +104,10 @@ where >::Error: Debug + Display, { async fn xrpl_send(&mut self, message: Req) -> Result<()> { - let message = serde_json::to_string(&message).unwrap(); + let message = match serde_json::to_string(&message) { + Ok(message) => message, + Err(error) => return Err!(error), + }; match self.send(message).await { Ok(()) => Ok(()), Err(error) => Err!(error), @@ -109,16 +122,15 @@ where ) -> Result> { match self.next().await { Some(Ok(item)) => { - let xrpl_response = serde_json::from_str(&item).unwrap(); - self.handle_message(xrpl_response).await; + self.handle_message(item).await?; let message = self.pop_message().await; - let response = serde_json::from_str(&message).unwrap(); - Ok(response) + match serde_json::from_str(&message) { + Ok(response) => Ok(response), + Err(error) => Err!(error), + } } Some(Err(error)) => Err!(error), - None => { - todo!() - } + None => Err!(XRPLWebsocketException::::Disconnected), } } } diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index ee4660fc..cef65c97 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -113,7 +113,16 @@ where Ok(message) => match message { TungsteniteMessage::Text(response) => Poll::Ready(Some(Ok(response))), TungsteniteMessage::Binary(response) => { - let response_string = String::from_utf8(response).unwrap(); + let response_string = match String::from_utf8(response) { + Ok(string) => string, + Err(error) => { + return Poll::Ready(Some(Err!(XRPLWebsocketException::< + anyhow::Error, + >::Utf8( + error.utf8_error() + )))); + } + }; Poll::Ready(Some(Ok(response_string))) } _ => Poll::Ready(Some(Err!( @@ -157,9 +166,9 @@ where websocket_base.setup_request_future(id).await; } - async fn handle_message(&mut self, message: String) { + async fn handle_message(&mut self, message: String) -> Result<()> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.handle_message(message).await; + websocket_base.handle_message(message).await } async fn pop_message(&mut self) -> String { @@ -167,7 +176,7 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> String { + async fn request_impl(&mut self, id: String) -> Result { let mut websocket_base = self.websocket_base.lock().await; websocket_base.request_impl(id).await } @@ -186,16 +195,23 @@ where ) -> Result> { let request_id = self.set_request_id::(&mut request); let mut websocket = self.websocket.lock().await; - websocket - .send(TungsteniteMessage::Text( - serde_json::to_string(&request).unwrap(), - )) + let request_string = match serde_json::to_string(&request) { + Ok(request_string) => request_string, + Err(error) => return Err!(error), + }; + match websocket + .send(TungsteniteMessage::Text(request_string)) .await - .unwrap(); + { + Ok(()) => (), + Err(error) => return Err!(error), + } let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await; - let response = serde_json::from_str(&message).unwrap(); - Ok(response) + let message = websocket_base.request_impl(request_id.to_string()).await?; + match serde_json::from_str(&message) { + Ok(response) => Ok(response), + Err(error) => Err!(error), + } } // async fn get_common_fields(&self) -> Result> { diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index 6f01ffcb..a8be7076 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -1,8 +1,11 @@ use alloc::string::{String, ToString}; +use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; use futures::channel::oneshot::{self, Receiver, Sender}; use hashbrown::HashMap; +use crate::{asynch::clients::exceptions::XRPLWebsocketException, Err}; + const _MAX_CHANNEL_MSG_CNT: usize = 10; /// A struct that handles futures of websocket messages. @@ -33,9 +36,9 @@ where pub(crate) trait MessageHandler { /// Setup an empty future for a request. async fn setup_request_future(&mut self, id: String); - async fn handle_message(&mut self, message: String); + async fn handle_message(&mut self, message: String) -> Result<()>; async fn pop_message(&mut self) -> String; - async fn request_impl(&mut self, id: String) -> String; + async fn request_impl(&mut self, id: String) -> Result; } impl MessageHandler for WebsocketBase @@ -48,34 +51,46 @@ where self.request_senders.insert(id, sender); } - async fn handle_message(&mut self, message: String) { - let message_value = serde_json::to_value(&message).unwrap(); + async fn handle_message(&mut self, message: String) -> Result<()> { + let message_value = match serde_json::to_value(&message) { + Ok(value) => value, + Err(error) => return Err!(error), + }; let id = match message_value.get("id") { - Some(id) => { - let id = id.as_str().unwrap().to_string(); - if id == String::new() { - todo!("`id` must not be an empty string") - } - id - } + Some(id) => match id.as_str() { + Some(id) => id.to_string(), + None => return Err!(XRPLWebsocketException::::InvalidMessage), + }, None => String::new(), }; if let Some(_receiver) = self.pending_requests.get(&id) { - let sender = self.request_senders.remove(&id).unwrap(); - sender.send(message).unwrap(); + let sender = match self.request_senders.remove(&id) { + Some(sender) => sender, + None => return Err!(XRPLWebsocketException::::MissingRequestSender), + }; + match sender.send(message) { + Ok(()) => (), + Err(error) => return Err!(error), + }; } else { self.messages.send(message).await; } + Ok(()) } async fn pop_message(&mut self) -> String { self.messages.receive().await } - async fn request_impl(&mut self, id: String) -> String { + async fn request_impl(&mut self, id: String) -> Result { self.setup_request_future(id.clone()).await; - let fut = self.pending_requests.remove(&id).unwrap(); - let message = fut.await.unwrap(); - serde_json::from_str(&message).unwrap() + let fut = match self.pending_requests.remove(&id) { + Some(fut) => fut, + None => return Err!(XRPLWebsocketException::::MissingRequestReceiver), + }; + match fut.await { + Ok(message) => Ok(message), + Err(error) => return Err!(error), + } } } diff --git a/tests/common/constants.rs b/tests/common/constants.rs index a2ba78a8..97f48f57 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -1,4 +1,5 @@ pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; +// pub const XRPL_TEST_NET: &'static str = "ws://s2.livenet.ripple.com/"; pub const XRPL_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index dc7e14be..d79995fb 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -18,7 +18,7 @@ pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn connect_to_wss_tungstinite_echo( ) -> Result> { - match ECHO_WSS_SERVER.parse() { + match XRPL_TEST_NET.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { // assert!(websocket.is_open()); @@ -45,7 +45,7 @@ pub async fn connect_to_ws_embedded_websocket_tokio_echo( >, > { let rng = rand_core::OsRng; - let url = XRPL_TEST_NET.parse().unwrap(); + let url = ECHO_WS_SERVER.parse().unwrap(); match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { // assert!(websocket.is_open()); diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 8dc535e5..3e0dda43 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -17,13 +17,11 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { ); websocket.xrpl_send(account_info).await.unwrap(); - loop { - let t = websocket - .xrpl_receive::, AccountInfo<'_>>() - .await - .unwrap(); - dbg!("{:?}", t); - } + let _ = websocket + .xrpl_receive::, AccountInfo<'_>>() + .await + .unwrap(); + Ok(()) } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -49,12 +47,8 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { None, ); websocket.xrpl_send(account_info).await?; - - let t = websocket + let _ = websocket .xrpl_receive::, AccountInfo<'_>>() - .await - .unwrap(); - dbg!("{:?}", t); - panic!(); + .await?; Ok(()) } From 44dad7906b6481cb8953864d2cae2b69261bbead Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Sun, 9 Jun 2024 11:37:43 +0000 Subject: [PATCH 17/23] improve testing --- .../clients/websocket/embedded_websocket.rs | 4 +- src/asynch/clients/websocket/exceptions.rs | 2 +- src/models/results/fee.rs | 16 +++++ src/models/results/mod.rs | 65 +++++++++++++++++-- tests/common/constants.rs | 3 +- tests/common/mod.rs | 2 +- tests/integration/clients/mod.rs | 41 +++++------- 7 files changed, 99 insertions(+), 34 deletions(-) create mode 100644 src/models/results/fee.rs diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 9bc43fcb..53beb6a6 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -5,6 +5,7 @@ use core::{ }; use alloc::{ + dbg, panic, string::{String, ToString}, sync::Arc, }; @@ -193,7 +194,8 @@ where Some(Ok(ReadResult::Text(t))) => Ok(t.len()), Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), Some(Ok(ReadResult::Ping(_))) => Ok(0), - Some(Ok(_)) => Err(XRPLWebsocketException::::UnexpectedMessageType), + Some(Ok(ReadResult::Pong(_))) => Ok(0), + Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), None => Err(XRPLWebsocketException::::Disconnected), } diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 47102697..664ec36e 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -26,7 +26,7 @@ pub enum XRPLWebsocketException { Disconnected, #[error("Read buffer is too small (size: {0:?})")] RxBufferTooSmall(usize), - #[error("Unexpected result type")] + #[error("Unexpected message type")] UnexpectedMessageType, #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Embedded I/O error: {0:?}")] diff --git a/src/models/results/fee.rs b/src/models/results/fee.rs new file mode 100644 index 00000000..59284566 --- /dev/null +++ b/src/models/results/fee.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +use crate::models::amount::XRPAmount; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Fee<'a> { + pub drops: Drops<'a>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Drops<'a> { + pub base_fee: XRPAmount<'a>, + pub median_fee: XRPAmount<'a>, + pub minimum_fee: XRPAmount<'a>, + pub open_ledger_fee: XRPAmount<'a>, +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index d7bc4d20..5c74b564 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,5 +1,8 @@ -use alloc::{borrow::Cow, vec::Vec}; -use serde::{Deserialize, Serialize}; +use alloc::{borrow::Cow, string::ToString, vec::Vec}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +mod fee; +pub use fee::{Fee as FeeResult, *}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -16,8 +19,7 @@ pub enum ResponseType { Transaction, } -/// TODO: Because everything is optional, the deserializing always succeds without returning an error on false data. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub struct XRPLResponse<'a, Res, Req> { pub id: Option>, pub error: Option>, @@ -32,6 +34,61 @@ pub struct XRPLResponse<'a, Res, Req> { pub warnings: Option>>, } +impl<'a, 'de, Res, Req> Deserialize<'de> for XRPLResponse<'a, Res, Req> +where + Res: DeserializeOwned, + Req: DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // TODO: add validation for fields that can not coexist in the same response + let mut map = serde_json::Map::deserialize(deserializer)?; + if map.is_empty() { + return Err(serde::de::Error::custom("Empty response")); + } + Ok(XRPLResponse { + id: map.remove("id").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error: map.remove("error").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + error_code: map + .remove("error_code") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + error_message: map.remove("error_message").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + forwarded: map.remove("forwarded").and_then(|v| v.as_bool()), + request: map + .remove("request") + .map(|v| serde_json::from_value(v).unwrap()), + result: map + .remove("result") + .map(|v| serde_json::from_value(v).unwrap()), + status: map + .remove("status") + .map(|v| serde_json::from_value(v).unwrap()), + r#type: map + .remove("type") + .map(|v| serde_json::from_value(v).unwrap()), + warning: map.remove("warning").map(|item| match item.as_str() { + Some(item_str) => Cow::Owned(item_str.to_string()), + None => Cow::Borrowed(""), + }), + warnings: map + .remove("warnings") + .and_then(|v| serde_json::from_value(v).ok()), + }) + } +} + impl<'a, Res, Req> XRPLResponse<'a, Res, Req> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) diff --git a/tests/common/constants.rs b/tests/common/constants.rs index 97f48f57..732059d2 100644 --- a/tests/common/constants.rs +++ b/tests/common/constants.rs @@ -2,4 +2,5 @@ pub const ECHO_WS_SERVER: &'static str = "ws://ws.vi-server.org/mirror"; pub const ECHO_WSS_SERVER: &'static str = "wss://ws.vi-server.org/mirror"; // pub const XRPL_TEST_NET: &'static str = "ws://s2.livenet.ripple.com/"; -pub const XRPL_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; +pub const XRPL_WSS_TEST_NET: &'static str = "wss://testnet.xrpl-labs.com/"; +pub const XRPL_WS_TEST_NET: &'static str = "wss://s.altnet.rippletest.net:51233/"; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d79995fb..1c13ded5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -18,7 +18,7 @@ pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn connect_to_wss_tungstinite_echo( ) -> Result> { - match XRPL_TEST_NET.parse() { + match XRPL_WSS_TEST_NET.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { Ok(websocket) => { // assert!(websocket.is_open()); diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 3e0dda43..390ac84b 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -3,24 +3,19 @@ use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_echo() -> Result<()> { use super::common::connect_to_wss_tungstinite_echo; - use xrpl::{asynch::clients::XRPLWebsocketIO, models::requests::AccountInfo}; + use xrpl::{ + asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, + }; let mut websocket = connect_to_wss_tungstinite_echo().await?; - let account_info = AccountInfo::new( - None, - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), - None, - None, - None, - None, - None, - ); + let fee = Fee::new(None); - websocket.xrpl_send(account_info).await.unwrap(); - let _ = websocket - .xrpl_receive::, AccountInfo<'_>>() + websocket.xrpl_send(fee).await.unwrap(); + let message = websocket + .xrpl_receive::, Fee<'_>>() .await .unwrap(); + assert!(message.result.is_some()); Ok(()) } @@ -30,25 +25,19 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::XRPLWebsocketIO; - use xrpl::models::requests::AccountInfo; + use xrpl::models::requests::Fee; + use xrpl::models::results::FeeResult; let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await .unwrap(); let mut framed = Framed::new(tcp_stream, Codec); let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; - let account_info = AccountInfo::new( - None, - "rJumr5e1HwiuV543H7bqixhtFreChWTaHH".into(), - None, - None, - None, - None, - None, - ); - websocket.xrpl_send(account_info).await?; + let fee = Fee::new(None); + websocket.xrpl_send(fee).await?; let _ = websocket - .xrpl_receive::, AccountInfo<'_>>() - .await?; + .xrpl_receive::, Fee<'_>>() + .await + .unwrap(); Ok(()) } From ea777b2bb4dcbe6da208d584017f2e827ff2384a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 18:02:09 +0000 Subject: [PATCH 18/23] implement websocket client request --- src/asynch/clients/async_client.rs | 9 ++- src/asynch/clients/client.rs | 13 ++-- src/asynch/clients/mod.rs | 1 + src/asynch/clients/websocket/mod.rs | 7 +- src/asynch/clients/websocket/tungstenite.rs | 78 ++++++++++++++----- .../clients/websocket/websocket_base.rs | 32 ++++++-- tests/common/mod.rs | 2 +- tests/integration/clients/mod.rs | 19 ++++- tests/integration_tests.rs | 12 ++- 9 files changed, 124 insertions(+), 49 deletions(-) diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index 0b01e8c5..57a776ab 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -3,16 +3,17 @@ use crate::models::{requests::Request, results::XRPLResponse}; use anyhow::Result; use serde::{Deserialize, Serialize}; -pub trait AsyncClient: Client { +#[allow(async_fn_in_trait)] +pub trait AsyncClient<'a>: Client<'a> { async fn request< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, request: Req, ) -> Result> { self.request_impl(request).await } } -impl AsyncClient for T {} +impl<'a, T: Client<'a>> AsyncClient<'a> for T {} diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index c1ce3c24..86d6e13f 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -6,19 +6,20 @@ use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; -pub(crate) trait Client { +#[allow(async_fn_in_trait)] +pub trait Client<'a> { async fn request_impl< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, request: Req, ) -> Result>; fn set_request_id< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, request: &mut Req, ) -> Cow<'_, str> { let common_fields = request.get_common_fields(); @@ -27,6 +28,8 @@ pub(crate) trait Client { None => { #[cfg(feature = "std")] let mut rng = rand::thread_rng(); + #[cfg(not(feature = "std"))] + unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); Cow::Owned(get_random_id(&mut rng)) } }; diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 746be0ce..591a459f 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -2,4 +2,5 @@ mod async_client; mod client; mod websocket; +pub use async_client::*; pub use websocket::*; diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index a4aebbd5..66bff880 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -9,7 +9,6 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; -use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -44,7 +43,7 @@ pub trait XRPLWebsocketIO { Req: Serialize + for<'de> Deserialize<'de> + Debug, >( &mut self, - ) -> Result>; + ) -> Result>>; } #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] @@ -119,7 +118,7 @@ where Req: Serialize + for<'de> Deserialize<'de> + Debug, >( &mut self, - ) -> Result> { + ) -> Result>> { match self.next().await { Some(Ok(item)) => { self.handle_message(item).await?; @@ -130,7 +129,7 @@ where } } Some(Err(error)) => Err!(error), - None => Err!(XRPLWebsocketException::::Disconnected), + None => Ok(None), } } } diff --git a/src/asynch/clients/websocket/tungstenite.rs b/src/asynch/clients/websocket/tungstenite.rs index cef65c97..0f85ceba 100644 --- a/src/asynch/clients/websocket/tungstenite.rs +++ b/src/asynch/clients/websocket/tungstenite.rs @@ -14,7 +14,7 @@ use core::{pin::Pin, task::Poll}; use embassy_futures::block_on; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; -use futures::{Sink, Stream}; +use futures::{Sink, Stream, StreamExt}; use futures_util::SinkExt; use serde::{Deserialize, Serialize}; use tokio::net::TcpStream; @@ -50,7 +50,6 @@ where cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -60,13 +59,10 @@ where fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> { let mut guard = block_on(self.websocket.lock()); - // let _ = self.websocket.lock().then(|mut guard| async move { match Pin::new(&mut *guard).start_send(TungsteniteMessage::Text(item)) { Ok(()) => Ok(()), Err(error) => Err!(error), } - // }); - // Ok(()) } fn poll_flush( @@ -74,7 +70,6 @@ where cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_flush(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -87,7 +82,6 @@ where cx: &mut core::task::Context<'_>, ) -> core::task::Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_close(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), Poll::Ready(Err(error)) => Poll::Ready(Err!(error)), @@ -107,7 +101,6 @@ where cx: &mut core::task::Context<'_>, ) -> Poll> { let mut guard = block_on(self.websocket.lock()); - // let mut guard = futures::ready!(Box::pin(self.websocket.lock()).poll_unpin(cx)); match Pin::new(&mut *guard).poll_next(cx) { Poll::Ready(Some(item)) => match item { Ok(message) => match message { @@ -125,6 +118,9 @@ where }; Poll::Ready(Some(Ok(response_string))) } + TungsteniteMessage::Close(_) => Poll::Ready(Some(Err!( + XRPLWebsocketException::::Disconnected + ))), _ => Poll::Ready(Some(Err!( XRPLWebsocketException::::UnexpectedMessageType ))), @@ -176,41 +172,81 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> Result { + async fn try_recv_request(&mut self, id: String) -> Result> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.request_impl(id).await + websocket_base.try_recv_request(id).await } } -impl Client for AsyncWebsocketClient +impl<'a, M> Client<'a> for AsyncWebsocketClient where M: RawMutex, { async fn request_impl< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, mut request: Req, ) -> Result> { + // setup request future let request_id = self.set_request_id::(&mut request); + let mut websocket_base = self.websocket_base.lock().await; + websocket_base + .setup_request_future(request_id.to_string()) + .await; + // send request let mut websocket = self.websocket.lock().await; let request_string = match serde_json::to_string(&request) { Ok(request_string) => request_string, Err(error) => return Err!(error), }; - match websocket + if let Err(error) = websocket .send(TungsteniteMessage::Text(request_string)) .await { - Ok(()) => (), - Err(error) => return Err!(error), + return Err!(error); } - let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await?; - match serde_json::from_str(&message) { - Ok(response) => Ok(response), - Err(error) => Err!(error), + // wait for response + loop { + let message = websocket.next().await; + match message { + Some(Ok(TungsteniteMessage::Text(message))) => { + websocket_base.handle_message(message).await?; + let message_opt = websocket_base + .try_recv_request(request_id.to_string()) + .await?; + if let Some(message) = message_opt { + let response = match serde_json::from_str(&message) { + Ok(response) => response, + Err(error) => return Err!(error), + }; + return Ok(response); + } + } + Some(Ok(TungsteniteMessage::Binary(response))) => { + let message = match String::from_utf8(response) { + Ok(string) => string, + Err(error) => { + return Err!(XRPLWebsocketException::::Utf8( + error.utf8_error() + )); + } + }; + match serde_json::from_str(&message) { + Ok(response) => return Ok(response), + Err(error) => return Err!(error), + } + } + Some(Ok(TungsteniteMessage::Close(_))) => { + return Err!(XRPLWebsocketException::::Disconnected) + } + Some(Ok(_)) => { + return Err!(XRPLWebsocketException::::UnexpectedMessageType); + } + Some(Err(error)) => return Err!(error), + None => continue, + } } } diff --git a/src/asynch/clients/websocket/websocket_base.rs b/src/asynch/clients/websocket/websocket_base.rs index a8be7076..ab43e3a8 100644 --- a/src/asynch/clients/websocket/websocket_base.rs +++ b/src/asynch/clients/websocket/websocket_base.rs @@ -3,6 +3,7 @@ use anyhow::Result; use embassy_sync::{blocking_mutex::raw::RawMutex, channel::Channel}; use futures::channel::oneshot::{self, Receiver, Sender}; use hashbrown::HashMap; +use serde_json::Value; use crate::{asynch::clients::exceptions::XRPLWebsocketException, Err}; @@ -31,6 +32,12 @@ where messages: Channel::new(), } } + + pub fn close(&mut self) { + self.pending_requests.clear(); + self.request_senders.clear(); + self.messages.clear(); + } } pub(crate) trait MessageHandler { @@ -38,7 +45,7 @@ pub(crate) trait MessageHandler { async fn setup_request_future(&mut self, id: String); async fn handle_message(&mut self, message: String) -> Result<()>; async fn pop_message(&mut self) -> String; - async fn request_impl(&mut self, id: String) -> Result; + async fn try_recv_request(&mut self, id: String) -> Result>; } impl MessageHandler for WebsocketBase @@ -46,13 +53,16 @@ where M: RawMutex, { async fn setup_request_future(&mut self, id: String) { + if self.pending_requests.contains_key(&id) { + return; + } let (sender, receiver) = oneshot::channel::(); self.pending_requests.insert(id.clone(), receiver); self.request_senders.insert(id, sender); } async fn handle_message(&mut self, message: String) -> Result<()> { - let message_value = match serde_json::to_value(&message) { + let message_value: Value = match serde_json::from_str(&message) { Ok(value) => value, Err(error) => return Err!(error), }; @@ -82,15 +92,21 @@ where self.messages.receive().await } - async fn request_impl(&mut self, id: String) -> Result { - self.setup_request_future(id.clone()).await; - let fut = match self.pending_requests.remove(&id) { + async fn try_recv_request(&mut self, id: String) -> Result> { + let fut = match self.pending_requests.get_mut(&id) { Some(fut) => fut, None => return Err!(XRPLWebsocketException::::MissingRequestReceiver), }; - match fut.await { - Ok(message) => Ok(message), - Err(error) => return Err!(error), + match fut.try_recv() { + Ok(Some(message)) => { + // Remove the future from the hashmap. + self.pending_requests.remove(&id); + Ok(Some(message)) + } + Ok(None) => Ok(None), + Err(error) => { + return Err!(error); + } } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 1c13ded5..25d97d5e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -16,7 +16,7 @@ mod constants; pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_echo( +pub async fn connect_to_wss_tungstinite_test_net( ) -> Result> { match XRPL_WSS_TEST_NET.parse() { Ok(url) => match AsyncWebsocketClient::open(url).await { diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index 390ac84b..d8c1d805 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -1,13 +1,13 @@ use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn test_websocket_tungstenite_echo() -> Result<()> { - use super::common::connect_to_wss_tungstinite_echo; +pub async fn test_websocket_tungstenite_test_net() -> Result<()> { + use super::common::connect_to_wss_tungstinite_test_net; use xrpl::{ asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, }; - let mut websocket = connect_to_wss_tungstinite_echo().await?; + let mut websocket = connect_to_wss_tungstinite_test_net().await?; let fee = Fee::new(None); websocket.xrpl_send(fee).await.unwrap(); @@ -15,6 +15,19 @@ pub async fn test_websocket_tungstenite_echo() -> Result<()> { .xrpl_receive::, Fee<'_>>() .await .unwrap(); + assert!(message.unwrap().result.is_some()); + Ok(()) +} + +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub async fn test_websocket_tungstenite_request() -> Result<()> { + use super::common::connect_to_wss_tungstinite_test_net; + use xrpl::{asynch::clients::AsyncClient, models::requests::Fee, models::results::FeeResult}; + + let websocket = connect_to_wss_tungstinite_test_net().await?; + let fee = Fee::new(None); + + let message = websocket.request::, _>(fee).await.unwrap(); assert!(message.result.is_some()); Ok(()) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 179d20bf..49db24a0 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -8,9 +8,15 @@ use anyhow::Result; #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] - return integration::clients::test_websocket_tungstenite_echo().await; + return integration::clients::test_websocket_tungstenite_test_net().await; + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + return integration::clients::test_embedded_websocket_echo().await; +} + +#[tokio::test] +async fn test_asynch_clients_request() -> Result<()> { + #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + return integration::clients::test_websocket_tungstenite_request().await; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; - #[cfg(all(feature = "tungstenite", feature = "embedded-ws"))] - Ok(()) } From 2475c0272470bbd7fd368834e902ced08f1f30f0 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 18:33:31 +0000 Subject: [PATCH 19/23] implement websocket client request for embedded websocket --- Cargo.toml | 2 +- .../clients/websocket/embedded_websocket.rs | 81 +++++++++++++------ src/asynch/clients/websocket/mod.rs | 4 +- tests/integration/clients/mod.rs | 21 ++++- tests/integration_tests.rs | 2 +- 5 files changed, 82 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 398f49da..322ac67a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "tungstenite"] +default = ["std", "core", "models", "utils", "embedded-ws"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index 53beb6a6..ec5a3707 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -153,6 +153,20 @@ where Err(error) => Err(XRPLWebsocketException::::from(error)), } } + + async fn do_read(&self, buf: &mut [u8]) -> Result::Error> { + let mut inner = self.websocket.lock().await; + let mut tcp = self.tcp.lock().await; + match inner.read(tcp.deref_mut(), buf).await { + Some(Ok(ReadResult::Text(t))) => Ok(t.len()), + Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), + Some(Ok(ReadResult::Ping(_))) => Ok(0), + Some(Ok(ReadResult::Pong(_))) => Ok(0), + Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), + Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), + None => Err(XRPLWebsocketException::::Disconnected), + } + } } impl ErrorType @@ -188,17 +202,7 @@ where Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, { async fn read(&mut self, buf: &mut [u8]) -> Result { - let mut inner = self.websocket.lock().await; - let mut tcp = self.tcp.lock().await; - match inner.read(tcp.deref_mut(), buf).await { - Some(Ok(ReadResult::Text(t))) => Ok(t.len()), - Some(Ok(ReadResult::Binary(b))) => Ok(b.len()), - Some(Ok(ReadResult::Ping(_))) => Ok(0), - Some(Ok(ReadResult::Pong(_))) => Ok(0), - Some(Ok(ReadResult::Close(_))) => Err(XRPLWebsocketException::::Disconnected), - Some(Err(error)) => Err(XRPLWebsocketException::::from(error)), - None => Err(XRPLWebsocketException::::Disconnected), - } + self.do_read(buf).await } } @@ -225,28 +229,34 @@ where websocket_base.pop_message().await } - async fn request_impl(&mut self, id: String) -> Result { + async fn try_recv_request(&mut self, id: String) -> Result> { let mut websocket_base = self.websocket_base.lock().await; - websocket_base.request_impl(id).await + websocket_base.try_recv_request(id).await } } -impl ClientTrait +impl<'a, const BUF: usize, M, Tcp, B, E, Rng: RngCore> ClientTrait<'a> for AsyncWebsocketClient where M: RawMutex, B: Deref + AsRef<[u8]>, E: Debug + Display, - Tcp: Stream> + for<'a> Sink<&'a [u8], Error = E> + Unpin, + Tcp: Stream> + for<'b> Sink<&'b [u8], Error = E> + Unpin, { async fn request_impl< Res: Serialize + for<'de> Deserialize<'de>, - Req: Serialize + for<'de> Deserialize<'de> + for<'a> Request<'a>, + Req: Serialize + for<'de> Deserialize<'de> + Request<'a>, >( - &self, + &'a self, mut request: Req, ) -> Result> { + // setup request future let request_id = self.set_request_id::(&mut request); + let mut websocket_base = self.websocket_base.lock().await; + websocket_base + .setup_request_future(request_id.to_string()) + .await; + // send request let request_string = match serde_json::to_string(&request) { Ok(request_string) => request_string, Err(error) => return Err!(error), @@ -254,12 +264,35 @@ where if let Err(error) = self.do_write(request_string.as_bytes()).await { return Err!(error); } - let mut websocket_base = self.websocket_base.lock().await; - let message = websocket_base.request_impl(request_id.to_string()).await?; - let response = match serde_json::from_str(&message) { - Ok(response) => response, - Err(error) => return Err!(error), - }; - Ok(response) + // wait for response + loop { + let mut rx_buffer = [0; 1024]; + match self.do_read(&mut rx_buffer).await { + Ok(u_size) => { + // If the buffer is empty, continue to the next iteration. + if u_size == 0 { + continue; + } + let message_str = match core::str::from_utf8(&rx_buffer[..u_size]) { + Ok(response_str) => response_str, + Err(error) => return Err!(XRPLWebsocketException::::Utf8(error)), + }; + websocket_base + .handle_message(message_str.to_string()) + .await?; + let message_opt = websocket_base + .try_recv_request(request_id.to_string()) + .await?; + if let Some(message) = message_opt { + let response = match serde_json::from_str(&message) { + Ok(response) => response, + Err(error) => return Err!(error), + }; + return Ok(response); + } + } + Err(error) => return Err!(error), + } + } } } diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index 66bff880..6cc1d398 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -9,6 +9,8 @@ use anyhow::Result; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] use embedded_io_async::{ErrorType, Read as EmbeddedIoRead, Write as EmbeddedIoWrite}; +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +use exceptions::XRPLWebsocketException; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] use futures::{Sink, SinkExt, Stream, StreamExt}; use serde::{Deserialize, Serialize}; @@ -68,7 +70,7 @@ where Req: Serialize + for<'de> Deserialize<'de> + Debug, >( &mut self, - ) -> Result> { + ) -> Result>> { let mut buffer = [0; 1024]; loop { match self.read(&mut buffer).await { diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index d8c1d805..e81de7c0 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -44,7 +44,7 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") .await .unwrap(); - let mut framed = Framed::new(tcp_stream, Codec); + let framed = Framed::new(tcp_stream, Codec); let mut websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; let fee = Fee::new(None); websocket.xrpl_send(fee).await?; @@ -54,3 +54,22 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { .unwrap(); Ok(()) } + +#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +pub async fn test_embedded_websocket_request() -> Result<()> { + use super::common::connect_to_ws_embedded_websocket_tokio_echo; + use tokio_util::codec::Framed; + use xrpl::asynch::clients::codec::Codec; + use xrpl::asynch::clients::AsyncClient; + use xrpl::models::requests::Fee; + use xrpl::models::results::FeeResult; + + let tcp_stream = tokio::net::TcpStream::connect("ws.vi-server.org:80") + .await + .unwrap(); + let framed = Framed::new(tcp_stream, Codec); + let websocket = connect_to_ws_embedded_websocket_tokio_echo(framed).await?; + let fee = Fee::new(None); + let _res = websocket.request::(fee).await?; + Ok(()) +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 49db24a0..19114a74 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -18,5 +18,5 @@ async fn test_asynch_clients_request() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_request().await; #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] - return integration::clients::test_embedded_websocket_echo().await; + return integration::clients::test_embedded_websocket_request().await; } From 2179b2607d69be76282cb9c8fba27c5f622ee3e2 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 18:41:04 +0000 Subject: [PATCH 20/23] update rand trying to get rid of cargo check: OsRng: rand_core::RngCore is not satisfied error --- Cargo.toml | 4 ++-- src/asynch/clients/websocket/embedded_websocket.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 322ac67a..9c651d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ chrono = { version = "0.4.19", default-features = false, features = [ "clock", ] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -rand = { version = "0.8.4", default-features = false, features = ["getrandom"] } +rand = { version = "0.8.5", default-features = false, features = ["getrandom"] } serde = { version = "1.0.130", default-features = false, features = ["derive"] } serde_json = { version = "1.0.68", default-features = false, features = [ "alloc", @@ -81,7 +81,7 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [ ] } tokio = { version = "1.28.0", features = ["full"] } tokio-util = { version = "0.7.7", features = ["codec"] } -rand = { version = "0.8.4", default-features = false, features = [ +rand = { version = "0.8.5", default-features = false, features = [ "getrandom", "std", "std_rng", diff --git a/src/asynch/clients/websocket/embedded_websocket.rs b/src/asynch/clients/websocket/embedded_websocket.rs index ec5a3707..77c48dd8 100644 --- a/src/asynch/clients/websocket/embedded_websocket.rs +++ b/src/asynch/clients/websocket/embedded_websocket.rs @@ -5,7 +5,6 @@ use core::{ }; use alloc::{ - dbg, panic, string::{String, ToString}, sync::Arc, }; @@ -14,7 +13,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_io_async::{ErrorType, Read, Write}; use embedded_websocket::{ - framer_async::{Framer, FramerError, ReadResult}, + framer_async::{Framer, ReadResult}, Client, WebSocketClient, WebSocketOptions, WebSocketSendMessageType, }; use futures_core::Stream; From 400ae6939ea9ac7bd6f83796f79b9af613c5b30f Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 19:24:21 +0000 Subject: [PATCH 21/23] fix github workflow build and tests --- Cargo.toml | 4 +++- src/asynch/clients/client.rs | 6 ++++-- src/asynch/clients/websocket/exceptions.rs | 1 + src/models/transactions/escrow_finish.rs | 8 +++++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c651d92..76853166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,9 @@ thiserror-no-std = "2.0.2" anyhow = { version = "1.0.69", default-features = false } tokio = { version = "1.28.0", default-features = false, optional = true } url = { version = "2.2.2", default-features = false, optional = true } -futures = { version = "0.3.28", default-features = false, optional = true } +futures = { version = "0.3.28", default-features = false, features = [ + "alloc", +], optional = true } rand_core = { version = "0.6.4", default-features = false } tokio-tungstenite = { version = "0.20.0", optional = true } embassy-sync = { version = "0.6.0", default-features = false } diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index 86d6e13f..ff45730e 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -27,10 +27,12 @@ pub trait Client<'a> { Some(id) => id, None => { #[cfg(feature = "std")] - let mut rng = rand::thread_rng(); + { + let mut rng = rand::thread_rng(); + Cow::Owned(get_random_id(&mut rng)) + } #[cfg(not(feature = "std"))] unimplemented!("get_random_id is not yet implemented for no_std. Please provide an `id` in the request."); - Cow::Owned(get_random_id(&mut rng)) } }; request.get_common_fields_mut().id = Some(request_id.clone()); diff --git a/src/asynch/clients/websocket/exceptions.rs b/src/asynch/clients/websocket/exceptions.rs index 664ec36e..88b1161a 100644 --- a/src/asynch/clients/websocket/exceptions.rs +++ b/src/asynch/clients/websocket/exceptions.rs @@ -20,6 +20,7 @@ pub enum XRPLWebsocketException { Utf8(Utf8Error), #[error("Invalid HTTP header")] HttpHeader, + #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] #[error("Websocket error: {0:?}")] WebSocket(embedded_websocket::Error), #[error("Disconnected")] diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 36d4b9a1..2648498a 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -158,6 +158,8 @@ mod test_escrow_finish_errors { #[cfg(test)] mod tests { + use serde_json::Value; + use super::*; #[test] @@ -182,9 +184,9 @@ mod tests { ); let default_json_str = r#"{"Account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","TransactionType":"EscrowFinish","Owner":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn","OfferSequence":7,"Condition":"A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100","Fulfillment":"A0028000"}"#; // Serialize - let default_json_value = serde_json::to_value(default_json_str).unwrap(); - let serialized_string = serde_json::to_string(&default_txn).unwrap(); - let serialized_value = serde_json::to_value(&serialized_string).unwrap(); + let default_json_value: Value = serde_json::from_str(default_json_str).unwrap(); + // let serialized_string = serde_json::to_string(&default_txn).unwrap(); + let serialized_value = serde_json::to_value(&default_txn).unwrap(); assert_eq!(serialized_value, default_json_value); // Deserialize From f17a2e5379088b3d0c93d04f9eeb0e050284c2c4 Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 19:25:10 +0000 Subject: [PATCH 22/23] fix github workflow build and tests --- Cargo.toml | 2 +- src/asynch/clients/client.rs | 7 +-- src/models/transactions/mod.rs | 8 +++ tests/common/mod.rs | 98 +++++++++++++++++--------------- tests/integration/clients/mod.rs | 12 ++-- tests/integration/mod.rs | 2 - tests/integration_tests.rs | 10 +++- 7 files changed, 78 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76853166..6c7b105e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,7 +94,7 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "embedded-ws"] +default = ["std", "core", "models", "utils", "tungstenite"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] diff --git a/src/asynch/clients/client.rs b/src/asynch/clients/client.rs index ff45730e..7b7cd241 100644 --- a/src/asynch/clients/client.rs +++ b/src/asynch/clients/client.rs @@ -1,7 +1,6 @@ -use crate::{ - models::{requests::Request, results::XRPLResponse}, - utils::get_random_id, -}; +use crate::models::{requests::Request, results::XRPLResponse}; +#[cfg(feature = "std")] +use crate::utils::get_random_id; use alloc::borrow::Cow; use anyhow::Result; use serde::{Deserialize, Serialize}; diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 647dc3b5..2ee57b1b 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -153,6 +153,7 @@ where pub fee: Option>, /// Set of bit-flags for this transaction. #[serde(with = "txn_flags")] + #[serde(default = "optional_flag_collection_default")] pub flags: Option>, /// Highest ledger index this transaction can appear in. /// Specifying this field places a strict upper limit on how long @@ -231,6 +232,13 @@ where } } +fn optional_flag_collection_default() -> Option> +where + T: IntoEnumIterator + Serialize + core::fmt::Debug, +{ + None +} + serde_with_tag! { /// An arbitrary piece of data attached to a transaction. A /// transaction can have multiple Memo objects as an array diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 25d97d5e..4c451809 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,56 +1,64 @@ -use anyhow::anyhow; -use anyhow::Result; - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use std::io; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use tokio::net::TcpStream; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use tokio_util::codec::Framed; -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -use xrpl::asynch::clients::codec::Codec; -use xrpl::asynch::clients::AsyncWebsocketClient; -use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; - mod constants; -pub use constants::*; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] -pub async fn connect_to_wss_tungstinite_test_net( -) -> Result> { - match XRPL_WSS_TEST_NET.parse() { - Ok(url) => match AsyncWebsocketClient::open(url).await { +mod tungstenite_clients { + use super::constants::*; + use anyhow::anyhow; + use anyhow::Result; + use xrpl::asynch::clients::AsyncWebsocketClient; + use xrpl::asynch::clients::{SingleExecutorMutex, WebsocketOpen}; + + pub async fn connect_to_wss_tungstinite_test_net( + ) -> Result> { + match XRPL_WSS_TEST_NET.parse() { + Ok(url) => match AsyncWebsocketClient::open(url).await { + Ok(websocket) => { + // assert!(websocket.is_open()); + Ok(websocket) + } + Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), + }, + Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), + } + } +} + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +mod embedded_ws_clients { + use super::constants::*; + use anyhow::anyhow; + use anyhow::Result; + use std::io; + use tokio::net::TcpStream; + use tokio_util::codec::Framed; + use xrpl::asynch::clients::codec::Codec; + + pub async fn connect_to_ws_embedded_websocket_tokio_echo( + stream: Framed, + ) -> Result< + AsyncWebsocketClient< + 4096, + Framed, + Vec, + io::Error, + rand_core::OsRng, + SingleExecutorMutex, + WebsocketOpen, + >, + > { + let rng = rand_core::OsRng; + let url = ECHO_WS_SERVER.parse().unwrap(); + match AsyncWebsocketClient::open(rng, stream, url).await { Ok(websocket) => { // assert!(websocket.is_open()); Ok(websocket) } Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), - }, - Err(err) => Err(anyhow!("Error parsing url: {:?}", err)), - } -} - -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] -pub async fn connect_to_ws_embedded_websocket_tokio_echo( - stream: Framed, -) -> Result< - AsyncWebsocketClient< - 4096, - Framed, - Vec, - io::Error, - rand_core::OsRng, - SingleExecutorMutex, - WebsocketOpen, - >, -> { - let rng = rand_core::OsRng; - let url = ECHO_WS_SERVER.parse().unwrap(); - match AsyncWebsocketClient::open(rng, stream, url).await { - Ok(websocket) => { - // assert!(websocket.is_open()); - Ok(websocket) } - Err(err) => Err(anyhow!("Error connecting to websocket: {:?}", err)), } } + +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] +pub use embedded_ws_clients::*; +#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] +pub use tungstenite_clients::*; diff --git a/tests/integration/clients/mod.rs b/tests/integration/clients/mod.rs index e81de7c0..d66e9d9d 100644 --- a/tests/integration/clients/mod.rs +++ b/tests/integration/clients/mod.rs @@ -2,7 +2,7 @@ use anyhow::Result; #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_test_net() -> Result<()> { - use super::common::connect_to_wss_tungstinite_test_net; + use crate::common::connect_to_wss_tungstinite_test_net; use xrpl::{ asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult, }; @@ -21,7 +21,7 @@ pub async fn test_websocket_tungstenite_test_net() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] pub async fn test_websocket_tungstenite_request() -> Result<()> { - use super::common::connect_to_wss_tungstinite_test_net; + use crate::common::connect_to_wss_tungstinite_test_net; use xrpl::{asynch::clients::AsyncClient, models::requests::Fee, models::results::FeeResult}; let websocket = connect_to_wss_tungstinite_test_net().await?; @@ -32,9 +32,9 @@ pub async fn test_websocket_tungstenite_request() -> Result<()> { Ok(()) } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_echo() -> Result<()> { - use super::common::connect_to_ws_embedded_websocket_tokio_echo; + use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::XRPLWebsocketIO; @@ -55,9 +55,9 @@ pub async fn test_embedded_websocket_echo() -> Result<()> { Ok(()) } -#[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] +#[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] pub async fn test_embedded_websocket_request() -> Result<()> { - use super::common::connect_to_ws_embedded_websocket_tokio_echo; + use crate::common::connect_to_ws_embedded_websocket_tokio_echo; use tokio_util::codec::Framed; use xrpl::asynch::clients::codec::Codec; use xrpl::asynch::clients::AsyncClient; diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index eb5264ce..705f46db 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -1,3 +1 @@ -use super::common; - pub mod clients; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 19114a74..48352339 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,18 +5,22 @@ mod integration; use anyhow::Result; +#[cfg(any(feature = "tungstenite", all(feature = "embedded-ws", feature = "std")))] #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_test_net().await; - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; + Ok(()) } +#[cfg(any(feature = "tungstenite", feature = "embedded-ws", feature = "std"))] #[tokio::test] async fn test_asynch_clients_request() -> Result<()> { - #[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))] + #[cfg(all(feature = "tungstenite", feature = "std", not(feature = "embedded-ws")))] return integration::clients::test_websocket_tungstenite_request().await; - #[cfg(all(feature = "embedded-ws", not(feature = "tungstenite")))] + #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_request().await; + Ok(()) } From c1dfb5555fe02acc3f8a3f691b73553be843f60a Mon Sep 17 00:00:00 2001 From: LimpidCrypto Date: Tue, 11 Jun 2024 19:28:01 +0000 Subject: [PATCH 23/23] run linters --- tests/integration_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 48352339..73d75eeb 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -12,6 +12,7 @@ async fn test_asynch_clients() -> Result<()> { return integration::clients::test_websocket_tungstenite_test_net().await; #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_echo().await; + #[allow(unreachable_code)] Ok(()) } @@ -22,5 +23,6 @@ async fn test_asynch_clients_request() -> Result<()> { return integration::clients::test_websocket_tungstenite_request().await; #[cfg(all(feature = "embedded-ws", feature = "std", not(feature = "tungstenite")))] return integration::clients::test_embedded_websocket_request().await; + #[allow(unreachable_code)] Ok(()) }