Skip to content

Commit c76a826

Browse files
authored
fix: remove futures from ws_stream_wasm module (#111)
In #107 I removed futures in favour of futures-lite in various places. However, because ws_stream_wasm is not a default feature I didn't notice that I'd not removed futures from there as well. CI also did not catch this because futures was used for the tungstenite feature, which is often always on. This PR removes futures entirely, by implementing our own copy of StreamExt::send based on the futures impl, and then making sure there's no remaining uses of futures in the crate.
1 parent 681d7e8 commit c76a826

File tree

10 files changed

+93
-35
lines changed

10 files changed

+93
-35
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ members = ["examples", "examples-wasm"]
2121
[features]
2222
default = ["logging"]
2323
logging = ["dep:log"]
24-
tungstenite = ["dep:tungstenite", "dep:futures"]
24+
tungstenite = ["dep:tungstenite"]
2525
client-cynic = ["cynic"]
2626
client-graphql-client = ["graphql_client"]
2727
ws_stream_wasm = ["dep:ws_stream_wasm", "dep:pharos"]
2828

2929
[dependencies]
3030
async-channel = "2"
3131
futures-lite = "2"
32+
futures-sink = "0.3"
3233
futures-timer = "3"
3334
log = { version = "0.4", optional = true }
3435
pin-project = "1"
@@ -39,7 +40,6 @@ thiserror = "1.0"
3940
cynic = { version = "3", optional = true }
4041
tungstenite = { version = "0.23", optional = true }
4142
graphql_client = { version = "0.14.0", optional = true }
42-
futures = { version = "0.3", optional = true }
4343

4444
ws_stream_wasm = { version = "0.7", optional = true }
4545
pharos = { version = "0.5.2", optional = true }

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! ```rust
1515
//! use graphql_ws_client::Client;
1616
//! use std::future::IntoFuture;
17-
//! use futures::StreamExt;
17+
//! use futures_lite::StreamExt;
1818
//! # async fn example() -> Result<(), graphql_ws_client::Error> {
1919
//! # let connection = graphql_ws_client::__doc_utils::Conn;
2020
//! # let subscription = graphql_ws_client::__doc_utils::Subscription;
@@ -44,6 +44,7 @@
4444
mod error;
4545
mod logging;
4646
mod protocol;
47+
mod sink_ext;
4748

4849
#[doc(hidden)]
4950
#[path = "doc_utils.rs"]

src/native.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use futures::{Sink, SinkExt};
21
use futures_lite::{Stream, StreamExt};
2+
use futures_sink::Sink;
33
use tungstenite::{self, protocol::CloseFrame};
44

5-
use crate::{Error, Message};
5+
use crate::{sink_ext::SinkExt, Error, Message};
66

77
#[cfg_attr(docsrs, doc(cfg(feature = "tungstenite")))]
88
impl<T> crate::next::Connection for T

src/next/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub use self::{
3434
/// ```rust,no_run
3535
/// use graphql_ws_client::Client;
3636
/// use std::future::IntoFuture;
37-
/// use futures::StreamExt;
37+
/// use futures_lite::StreamExt;
3838
/// # use graphql_ws_client::__doc_utils::spawn;
3939
/// # async fn example() -> Result<(), graphql_ws_client::Error> {
4040
/// # let connection = graphql_ws_client::__doc_utils::Conn;

src/sink_ext.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::{
2+
future::Future,
3+
pin::Pin,
4+
task::{Context, Poll},
5+
};
6+
7+
use futures_lite::ready;
8+
use futures_sink::Sink;
9+
10+
/// A very limited clone of futures::SinkExt to avoid having to pull the original in
11+
pub trait SinkExt<Item>: Sink<Item> {
12+
fn send(&mut self, item: Item) -> Send<'_, Self, Item>
13+
where
14+
Self: Unpin,
15+
{
16+
Send::new(self, item)
17+
}
18+
}
19+
20+
impl<Item, T> SinkExt<Item> for T where T: Sink<Item> {}
21+
22+
#[derive(Debug)]
23+
#[must_use = "futures do nothing unless you `.await` or poll them"]
24+
pub struct Send<'a, Si: ?Sized, Item> {
25+
sink: &'a mut Si,
26+
item: Option<Item>,
27+
}
28+
29+
// Pinning is never projected to children
30+
impl<Si: Unpin + ?Sized, Item> Unpin for Send<'_, Si, Item> {}
31+
32+
impl<'a, Si: Sink<Item> + Unpin + ?Sized, Item> Send<'a, Si, Item> {
33+
pub(super) fn new(sink: &'a mut Si, item: Item) -> Self {
34+
Self {
35+
sink,
36+
item: Some(item),
37+
}
38+
}
39+
}
40+
41+
impl<Si: Sink<Item> + Unpin + ?Sized, Item> Future for Send<'_, Si, Item> {
42+
type Output = Result<(), Si::Error>;
43+
44+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
45+
let this = self.get_mut();
46+
let mut sink = Pin::new(&mut this.sink);
47+
48+
if let Some(item) = this.item.take() {
49+
ready!(sink.as_mut().poll_ready(cx))?;
50+
sink.as_mut().start_send(item)?;
51+
}
52+
53+
// we're done sending the item, but want to block on flushing the
54+
// sink
55+
ready!(sink.poll_flush(cx))?;
56+
57+
Poll::Ready(Ok(()))
58+
}
59+
}

src/ws_stream_wasm.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use futures::{FutureExt, SinkExt, StreamExt};
1+
use futures_lite::{FutureExt, StreamExt};
22
use pharos::{Observable, ObserveConfig};
33
use ws_stream_wasm::{WsEvent, WsMessage, WsMeta, WsStream};
44

5-
use crate::Error;
5+
use crate::{sink_ext::SinkExt, Error};
66

77
/// A websocket connection for ws_stream_wasm
88
#[cfg_attr(docsrs, doc(cfg(feature = "ws_stream_wasm")))]
@@ -72,14 +72,10 @@ impl crate::next::Connection for Connection {
7272

7373
impl Connection {
7474
async fn next(&mut self) -> Option<EventOrMessage> {
75-
futures::select! {
76-
event = self.event_stream.next().fuse() => {
77-
event.map(EventOrMessage::Event)
78-
}
79-
message = self.messages.next().fuse() => {
80-
message.map(EventOrMessage::Message)
81-
}
82-
}
75+
let event = async { self.event_stream.next().await.map(EventOrMessage::Event) };
76+
let message = async { self.messages.next().await.map(EventOrMessage::Message) };
77+
78+
event.race(message).await
8379
}
8480
}
8581

tests/cynic-tests.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{future::IntoFuture, time::Duration};
22

33
use assert_matches::assert_matches;
4+
use futures_lite::{future, StreamExt};
45
use subscription_server::SubscriptionServer;
56
use tokio::time::sleep;
67

@@ -54,7 +55,6 @@ struct BooksChangedSubscription {
5455
#[tokio::test]
5556
async fn main_test() {
5657
use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue};
57-
use futures::StreamExt;
5858

5959
let server = SubscriptionServer::start().await;
6060

@@ -95,7 +95,7 @@ async fn main_test() {
9595
},
9696
];
9797

98-
futures::join!(
98+
future::zip(
9999
async {
100100
for update in &updates {
101101
server.send(update.to_owned()).unwrap();
@@ -110,14 +110,14 @@ async fn main_test() {
110110
let data = update.data.unwrap();
111111
assert_eq!(data.books.id.inner(), expected.id.0);
112112
}
113-
}
114-
);
113+
},
114+
)
115+
.await;
115116
}
116117

117118
#[tokio::test]
118119
async fn oneshot_operation_test() {
119120
use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue};
120-
use futures::StreamExt;
121121

122122
let server = SubscriptionServer::start().await;
123123

@@ -155,7 +155,7 @@ async fn oneshot_operation_test() {
155155
},
156156
];
157157

158-
futures::join!(
158+
future::zip(
159159
async {
160160
sleep(Duration::from_millis(10)).await;
161161
for update in &updates {
@@ -171,8 +171,9 @@ async fn oneshot_operation_test() {
171171
let data = update.data.unwrap();
172172
assert_eq!(data.books.id.inner(), expected.id.0);
173173
}
174-
}
175-
);
174+
},
175+
)
176+
.await;
176177
}
177178

178179
fn build_query() -> cynic::StreamingOperation<BooksChangedSubscription, BooksChangedVariables> {

tests/graphql-client-tests.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{future::IntoFuture, time::Duration};
22

33
use assert_matches::assert_matches;
4+
use futures_lite::{future, StreamExt};
45
use graphql_client::GraphQLQuery;
56
use graphql_ws_client::graphql::StreamingOperation;
67
use subscription_server::SubscriptionServer;
@@ -19,7 +20,6 @@ struct BooksChanged;
1920
#[tokio::test]
2021
async fn main_test() {
2122
use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue};
22-
use futures::StreamExt;
2323

2424
let server = SubscriptionServer::start().await;
2525

@@ -60,7 +60,7 @@ async fn main_test() {
6060
},
6161
];
6262

63-
futures::join!(
63+
future::zip(
6464
async {
6565
for update in &updates {
6666
server.send(update.to_owned()).unwrap();
@@ -75,14 +75,14 @@ async fn main_test() {
7575
let data = update.data.unwrap();
7676
assert_eq!(data.books.id, expected.id.0);
7777
}
78-
}
79-
);
78+
},
79+
)
80+
.await;
8081
}
8182

8283
#[tokio::test]
8384
async fn oneshot_operation_test() {
8485
use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue};
85-
use futures::StreamExt;
8686

8787
let server = SubscriptionServer::start().await;
8888

@@ -120,7 +120,7 @@ async fn oneshot_operation_test() {
120120
},
121121
];
122122

123-
futures::join!(
123+
future::zip(
124124
async {
125125
sleep(Duration::from_millis(10)).await;
126126
for update in &updates {
@@ -136,8 +136,9 @@ async fn oneshot_operation_test() {
136136
let data = update.data.unwrap();
137137
assert_eq!(data.books.id, expected.id.0);
138138
}
139-
}
140-
);
139+
},
140+
)
141+
.await;
141142
}
142143

143144
fn build_query() -> graphql_ws_client::graphql::StreamingOperation<BooksChanged> {

tests/subscription_server/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use async_graphql::{EmptyMutation, Object, Schema, SimpleObject, Subscription, ID};
55
use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription};
66
use axum::{extract::Extension, routing::post, Router};
7-
use futures::{Stream, StreamExt};
7+
use futures_lite::{Stream, StreamExt};
88
use tokio::sync::broadcast::Sender;
99
use tokio_stream::wrappers::BroadcastStream;
1010

@@ -116,6 +116,6 @@ pub struct SubscriptionRoot {
116116
impl SubscriptionRoot {
117117
async fn books(&self, _mutation_type: MutationType) -> impl Stream<Item = BookChanged> {
118118
println!("Subscription received");
119-
BroadcastStream::new(self.channel.subscribe()).filter_map(|r| async move { r.ok() })
119+
BroadcastStream::new(self.channel.subscribe()).filter_map(|r| r.ok())
120120
}
121121
}

0 commit comments

Comments
 (0)