Skip to content
This repository was archived by the owner on Feb 22, 2026. It is now read-only.

Commit 9635d93

Browse files
authored
feat: expose ConnectionId newtype (#133)
This will allow us to expose a `fn stop(SubscriptionId)` on the Client.
1 parent 652271e commit 9635d93

6 files changed

Lines changed: 51 additions & 16 deletions

File tree

src/client/actor.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use serde_json::{json, Value};
99
use crate::{
1010
logging::{trace, warning},
1111
protocol::Event,
12-
Error,
12+
Error, SubscriptionId,
1313
};
1414

1515
use super::{
@@ -27,8 +27,8 @@ use super::{
2727
pub struct ConnectionActor {
2828
client: async_channel::Receiver<ConnectionCommand>,
2929
connection: Box<dyn ObjectSafeConnection>,
30-
dropped_ids: async_channel::Receiver<usize>,
31-
operations: HashMap<usize, async_channel::Sender<Value>>,
30+
dropped_ids: async_channel::Receiver<SubscriptionId>,
31+
operations: HashMap<SubscriptionId, async_channel::Sender<Value>>,
3232
keep_alive: KeepAliveSettings,
3333
keep_alive_actor: stream::Boxed<ConnectionCommand>,
3434
}
@@ -37,7 +37,7 @@ impl ConnectionActor {
3737
pub(super) fn new(
3838
connection: Box<dyn ObjectSafeConnection>,
3939
client: async_channel::Receiver<ConnectionCommand>,
40-
dropped_ids: async_channel::Receiver<usize>,
40+
dropped_ids: async_channel::Receiver<SubscriptionId>,
4141
keep_alive: KeepAliveSettings,
4242
) -> Self {
4343
ConnectionActor {
@@ -122,7 +122,7 @@ impl ConnectionActor {
122122

123123
match event {
124124
event @ (Event::Next { .. } | Event::Error { .. }) => {
125-
let Some(id) = event.id().unwrap().parse::<usize>().ok() else {
125+
let Some(id) = event.id().and_then(SubscriptionId::from_str) else {
126126
return Some(Message::close(Reason::UnknownSubscription));
127127
};
128128

@@ -142,7 +142,7 @@ impl ConnectionActor {
142142
None
143143
}
144144
Event::Complete { id } => {
145-
let Some(id) = id.parse::<usize>().ok() else {
145+
let Some(id) = SubscriptionId::from_str(&id) else {
146146
return Some(Message::close(Reason::UnknownSubscription));
147147
};
148148

src/client/conection_id.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::num::NonZero;
2+
3+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
4+
/// An opaque identifier for a subscription
5+
///
6+
/// Currently this wraps a `NonZero<usize>` though that may be subject to change
7+
/// in the future - as a result the underlying type is not exposed publically
8+
pub struct SubscriptionId(NonZero<usize>);
9+
10+
impl SubscriptionId {
11+
pub(super) fn new(id: usize) -> Option<Self> {
12+
Some(SubscriptionId(NonZero::new(id)?))
13+
}
14+
15+
#[expect(clippy::inherent_to_string)] // Don't want this to be public, which implementing Display would make it.
16+
pub(super) fn to_string(self) -> String {
17+
self.0.to_string()
18+
}
19+
20+
pub(super) fn from_str(s: &str) -> Option<Self> {
21+
SubscriptionId::new(s.parse::<usize>().ok()?)
22+
}
23+
}

src/client/connection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::future::Future;
22
use std::pin::Pin;
33

4-
use crate::Error;
4+
use crate::{Error, SubscriptionId};
55

66
/// Abstraction around a websocket connection.
77
///
@@ -63,7 +63,7 @@ impl Message {
6363
Self::Text(serde_json::to_string(&crate::protocol::Message::Ping::<()>).unwrap())
6464
}
6565

66-
pub(crate) fn complete(id: usize) -> Self {
66+
pub(crate) fn complete(id: SubscriptionId) -> Self {
6767
Self::Text(
6868
serde_json::to_string(&crate::protocol::Message::Complete::<()> { id: id.to_string() })
6969
.unwrap(),

src/client/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717

1818
mod actor;
1919
mod builder;
20+
mod conection_id;
2021
mod connection;
2122
mod keepalive;
2223
mod production_future;
@@ -25,6 +26,7 @@ mod stream;
2526
pub use self::{
2627
actor::ConnectionActor,
2728
builder::ClientBuilder,
29+
conection_id::SubscriptionId,
2830
connection::{Connection, Message},
2931
stream::Subscription,
3032
};
@@ -55,22 +57,22 @@ pub use self::{
5557
#[derive(Clone)]
5658
pub struct Client {
5759
actor: async_channel::Sender<ConnectionCommand>,
58-
drop_sender: async_channel::Sender<usize>,
60+
drop_sender: async_channel::Sender<SubscriptionId>,
5961
subscription_buffer_size: usize,
6062
next_id: Arc<AtomicUsize>,
6163
}
6264

6365
impl Client {
6466
pub(super) fn new_internal(
6567
actor: async_channel::Sender<ConnectionCommand>,
66-
drop_sender: async_channel::Sender<usize>,
68+
drop_sender: async_channel::Sender<SubscriptionId>,
6769
subscription_buffer_size: usize,
6870
) -> Self {
6971
Client {
7072
actor,
7173
drop_sender,
7274
subscription_buffer_size,
73-
next_id: Arc::new(AtomicUsize::new(0)),
75+
next_id: Arc::new(AtomicUsize::new(1)),
7476
}
7577
}
7678

@@ -96,6 +98,8 @@ impl Client {
9698
let request = serde_json::to_string(&message)
9799
.map_err(|error| Error::Serializing(error.to_string()))?;
98100

101+
let id = SubscriptionId::new(id).ok_or(Error::ConnectionIdsExhausted)?;
102+
99103
let actor = self.actor.clone();
100104
actor
101105
.send(ConnectionCommand::Subscribe {
@@ -134,10 +138,10 @@ pub(super) enum ConnectionCommand {
134138
/// The full subscribe request as a JSON encoded string.
135139
request: String,
136140
sender: async_channel::Sender<Value>,
137-
id: usize,
141+
id: SubscriptionId,
138142
},
139143
Ping,
140-
Cancel(usize),
144+
Cancel(SubscriptionId),
141145
Close(u16, String),
142146
}
143147

src/client/stream.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use std::{
55

66
use futures_lite::{future, stream, Stream, StreamExt};
77

8-
use crate::{client::production_future::read_from_producer, graphql::GraphqlOperation, Error};
8+
use crate::{
9+
client::production_future::read_from_producer, graphql::GraphqlOperation, Error, SubscriptionId,
10+
};
911

1012
use super::ConnectionCommand;
1113

@@ -17,10 +19,10 @@ pub struct Subscription<Operation>
1719
where
1820
Operation: GraphqlOperation,
1921
{
20-
pub(in crate::client) id: usize,
22+
pub(in crate::client) id: SubscriptionId,
2123
pub(in crate::client) stream: Option<stream::Boxed<Result<Operation::Response, Error>>>,
2224
pub(in crate::client) actor: async_channel::Sender<ConnectionCommand>,
23-
pub(in crate::client) drop_sender: async_channel::Sender<usize>,
25+
pub(in crate::client) drop_sender: async_channel::Sender<SubscriptionId>,
2426
}
2527

2628
#[pin_project::pinned_drop]

src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,10 @@ pub enum Error {
2525
/// Sender shutdown error
2626
#[error("sender shutdown error, reason: {0}")]
2727
SenderShutdown(String),
28+
/// Too many existing connections have been created.
29+
///
30+
/// Note that this would require a usize to be exhausted so is quite
31+
/// unlikely
32+
#[error("connection ID space exhausted. please restart the client")]
33+
ConnectionIdsExhausted,
2834
}

0 commit comments

Comments
 (0)