The Code
AsyncWebsocketClient
I'm currently working on an AsyncWebsocketClient
which is using tokio_tungstenite internally, respectively futures::Sink<tokio_tungstenite::Message, Error = tokio_tungstenite::tungstenite::Error>
and futures::Stream<Item = Result<tokio_tungstenite::Message, tokio_tungstenite::tungstenite::Error>>
. AsyncWebsocketClient
also implements futures::Sink
and futures::Stream
but uses String
as Item
.
So the AsyncWebsocketClient
looks like this:
pub struct AsyncWebsocketClient<T, M = SingleExecutorMutex, Status = WebsocketClosed>
where
M: RawMutex,
{
websocket: Arc<Mutex<M, T>>,
websocket_base: Arc<Mutex<M, WebsocketBase<M>>>,
status: PhantomData<Status>,
}
impl<T, M> Sink<String> for AsyncWebsocketClient<T, M, WebsocketOpen>
where
T: Sink<TungsteniteMessage, Error = tokio_tungstenite::tungstenite::Error> + Unpin,
M: RawMutex,
{
type Error = anyhow::Error;
fn poll_ready(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Result<()>> {
...
}
fn start_send(self: core::pin::Pin<&mut Self>, item: String) -> Result<()> {
...
}
fn poll_flush(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Result<()>> {
...
}
fn poll_close(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Result<()>> {
...
}
}
impl<T, M> Stream for AsyncWebsocketClient<T, M, WebsocketOpen>
where
T: Stream<Item = Result<TungsteniteMessage, tokio_tungstenite::tungstenite::Error>> + Unpin,
M: RawMutex,
{
type Item = Result<String>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
...
}
}
impl<T, M> MessageHandler for AsyncWebsocketClient<T, M, WebsocketOpen>
where
M: RawMutex,
{
...
}
trait XRPLWebsocketIO
I now want to have a trait users can import to use the client with our request models. I want it to be similar to futures::SinkExt
and futures::StreamExt
for futures::Sink
and futures::Stream
. So here is the definition of the external trait:
pub trait XRPLWebsocketIO {
async fn xrpl_send<Req: Serialize>(&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<Option<XRPLResponse<'_, Res, Req>>>;
}
An I implement it for T
like this:
#[cfg(all(feature = "tungstenite", not(feature = "embedded-ws")))]
impl<T: ?Sized> XRPLWebsocketIO for T
where
T: Stream<Item = Result<String>> + Sink<String, Error = anyhow::Error> + MessageHandler + Unpin,
{
async fn xrpl_send<Req: Serialize>(&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<Option<XRPLResponse<'_, Res, Req>>> {
...
}
}
Because of the above implementation I expect to be able to use it with AsyncWebsocketClient
. And when compiling the library it compiles just fine:
root ➜ /workspaces/xrpl-rust (dev) $ cargo build
Compiling xrpl-rust v0.2.0 (/workspaces/xrpl-rust)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.95s
And also my tests can successfully send and receive messages:
pub async fn test_websocket_tungstenite_test_net() -> Result<()> {
use crate::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_test_net().await?;
let fee = Fee::new(None);
websocket.xrpl_send(fee).await.unwrap();
let message = websocket
.xrpl_receive::<FeeResult<'_>, Fee<'_>>()
.await
.unwrap();
assert!(message.unwrap().result.is_some());
Ok(())
}
root ➜ /workspaces/xrpl-rust (dev) $ cargo test --test integration_tests
Compiling xrpl-rust v0.2.0 (/workspaces/xrpl-rust)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.28s
Running tests/integration_tests.rs (target/debug/deps/integration_tests-822d2d29a85b8dbe)
running 3 tests
test test_asynch_clients_request ... ok
test test_asynch_clients ... ok
test test_asynch_clients_json_rpc ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.83s
Cargo.toml
For completeness here is the libraries Cargo.toml:
[package]
name = "xrpl-rust"
version = "0.2.0"
edition = "2018"
authors = ["Tanveer Wahid <tan@wahid.email>"]
description = "A 100% Rust library to interact with the XRPL"
readme = "README.md"
license = "ISC"
repository = "https://github.com/589labs/xrpl-rust"
keywords = ["xrpl", "no_std"]
categories = ["no-std"]
[package.metadata.release]
no-dev-version = true
tag-name = "{{version}}"
[lib]
name = "xrpl"
crate-type = ["lib"]
proc-macro = true
[dependencies]
lazy_static = "1.4.0"
sha2 = "0.10.2"
rand_hc = "0.3.1"
ripemd = "0.1.1"
ed25519-dalek = "1.0.1"
secp256k1 = { version = "0.27.0", default-features = false, features = [
"alloc",
] }
bs58 = { version = "0.5.0", default-features = false, features = [
"check",
"alloc",
] }
indexmap = { version = "2.0.0", features = ["serde"] }
regex = { version = "1.5.4", default-features = false }
strum = { version = "0.25.0", default-features = false }
strum_macros = { version = "0.25.2", default-features = false }
crypto-bigint = { version = "0.5.1" }
rust_decimal = { version = "1.17.0", default-features = false, features = [
"serde",
] }
chrono = { version = "0.4.19", default-features = false, features = [
"alloc",
"clock",
] }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
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",
] }
serde_with = "3.2.0"
serde_repr = "0.1"
zeroize = "1.5.7"
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"
anyhow = { version = "1.0.69", default-features = false }
url = { version = "2.2.2", default-features = false, optional = true }
futures = { version = "0.3.30", default-features = false, features = [
"alloc",
], optional = true }
rand_core = { version = "0.6.4", default-features = false }
tokio-tungstenite = { version = "0.23.1", optional = true }
embassy-sync = { version = "0.6.0", default-features = false }
embedded-io-async = "0.6.1"
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 }
reqwless = { version = "0.12.0", optional = true }
embedded-nal-async = { version = "0.7.1", optional = true }
reqwest = { version = "0.12.4", features = ["json"], optional = true }
[dev-dependencies]
criterion = "0.5.1"
tokio = { version = "1.38.0", features = ["full"] }
# cargo-husky = { version = "1.5.0", default-features = false, features = [
# "user-hooks",
# ] }
# tokio-util = { version = "0.7.7", features = ["codec"] }
# rand = { version = "0.8.5", default-features = false, features = [
# "getrandom",
# "std",
# "std_rng",
# ] }
[[bench]]
name = "benchmarks"
harness = false
[features]
default = ["std", "core", "models", "utils", "tungstenite", "json-rpc-std"]
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"]
json-rpc = ["url", "reqwless", "embedded-nal-async"]
json-rpc-std = ["url", "reqwest"]
tungstenite = ["url", "futures", "tokio-tungstenite/native-tls"]
embedded-ws = ["url", "futures", "embedded-websocket"]
core = ["utils"]
utils = []
std = [
"embedded-websocket/std",
"futures/std",
"rand/std",
"regex/std",
"chrono/std",
"rand/std_rng",
"hex/std",
"rust_decimal/std",
"bs58/std",
"serde/std",
"indexmap/std",
"secp256k1/std",
"dep:tokio-util",
]
The Problem
But now comes the problem which cargo check
already hints, is that the traits bounds required by XRPLWebsocketIO
are not met by AsyncWebsocketClient
:
root ➜ /workspaces/xrpl-rust (dev) $ cargo check --test integration_tests
Checking xrpl-rust v0.2.0 (/workspaces/xrpl-rust)
error[E0599]: the method `xrpl_send` exists for struct `AsyncWebsocketClient<WebSocketStream<MaybeTlsStream<TcpStream>>, NoopRawMutex, WebsocketOpen>`, but its trait bounds were not satisfied
--> tests/integration/clients/mod.rs:13:15
|
13 | websocket.xrpl_send(fee).await.unwrap();
| ^^^^^^^^^ method cannot be called due to unsatisfied trait bounds
|
::: /workspaces/xrpl-rust/src/asynch/clients/websocket/tungstenite.rs:22:1
|
22 | pub struct AsyncWebsocketClient<T, M = SingleExecutorMutex, Status = WebsocketClosed>
| ------------------------------------------------------------------------------------- doesn't satisfy 5 bounds
|
= note: the following trait bounds were not satisfied:
`<AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen> as futures_core::stream::Stream>::Item = Result<std::string::String, anyhow::Error>`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
`<AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen> as futures_sink::Sink<std::string::String>>::Error = anyhow::Error`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
`AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: futures_sink::Sink<std::string::String>`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
`AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: futures_core::stream::Stream`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
error[E0599]: the method `xrpl_receive` exists for struct `AsyncWebsocketClient<WebSocketStream<MaybeTlsStream<TcpStream>>, NoopRawMutex, WebsocketOpen>`, but its trait bounds were not satisfied
--> tests/integration/clients/mod.rs:15:10
|
14 | let message = websocket
| ___________________-
15 | | .xrpl_receive::<FeeResult<'_>, Fee<'_>>()
| | -^^^^^^^^^^^^ method cannot be called due to unsatisfied trait bounds
| |_________|
|
|
::: /workspaces/xrpl-rust/src/asynch/clients/websocket/tungstenite.rs:22:1
|
22 | pub struct AsyncWebsocketClient<T, M = SingleExecutorMutex, Status = WebsocketClosed>
| ------------------------------------------------------------------------------------- doesn't satisfy 5 bounds
|
= note: the following trait bounds were not satisfied:
`<AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen> as futures_core::stream::Stream>::Item = Result<std::string::String, anyhow::Error>`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
`<AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen> as futures_sink::Sink<std::string::String>>::Error = anyhow::Error`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
`AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: futures_sink::Sink<std::string::String>`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
`AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: futures_core::stream::Stream`
which is required by `AsyncWebsocketClient<WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>, embassy_sync::blocking_mutex::raw::NoopRawMutex, WebsocketOpen>: XRPLWebsocketIO`
warning: unused import: `asynch::clients::XRPLWebsocketIO`
--> tests/integration/clients/mod.rs:7:9
|
7 | asynch::clients::XRPLWebsocketIO, models::requests::Fee, models::results::FeeResult,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0599`.
warning: `xrpl-rust` (test "integration_tests") generated 2 warnings
error: could not compile `xrpl-rust` (test "integration_tests") due to 2 previous errors; 1 warning emitted
These errors just get relevant when using the library as a dependency for another app, because then the library does not compile successfully anymore due to the above errors. I don't really get why I get this errors because the traits are obviously implemented. I also a similar question with a more simplified, but wrong example for this issue. So I decided to post my real code here. You can also find the code in our repo. I could really use some help here as I'm stuck for a while now. Thank you and let me know if you need any more info.
I tried to implement the XRPLWebsocketIO
trait for AsyncWebsocketClient
directly but I got the same errors.
I also tried using the "total paths" of the types, so instead of Result<String>
I used anyhow::Result<alloc::string::String>
.