From 8fd84b610ab0455e3c2061f6f8902bb0f8f38052 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 10:03:36 -0500 Subject: [PATCH 01/10] Terminal Document public functions, replace panics with unreachable --- src/lib.rs | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6f497b8..46daad2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,12 @@ impl< }) } + /// Wait for the next parsed message from the WebSocket connection. + /// + /// # Errors + /// + /// - If the WebSocket connection is closed or otherwise errored + /// - If the message cannot be deserialized #[cfg_attr(feature = "tracing", instrument)] pub async fn next_message(&mut self) -> Result { let Some(message) = self.receiever.recv().await else { @@ -106,13 +112,20 @@ impl< } } + /// Send a message to the WebSocket connection. + /// This function will wait for the message to be sent before returning. + /// + /// # Errors + /// + /// - If the message cannot be serialized + /// - If the WebSocket connection is closed, or otherwise errored #[cfg_attr(feature = "tracing", instrument)] pub async fn send(&self, message: TxMessage) -> Result<(), Error> { #[cfg(feature = "tracing")] debug!("Sending message: {:?}", message); let (tx, rx) = oneshot::channel::>(); - let message = serde_json::to_string(&message).unwrap(); + let message = serde_json::to_string(&message)?; self.sender .send(TxChannelPayload { @@ -122,7 +135,10 @@ impl< .await .map_err(|_| Error::WebsocketClosed)?; // We'll ensure that we always respond before dropping the tx channel - rx.await.unwrap() + match rx.await { + Ok(result) => result, + Err(_) => unreachable!("Socket loop always sends response before dropping one-shot"), + } } /// Consume self, closing down any remaining send/recieve, and return a new Socketeer instance if successful @@ -149,6 +165,11 @@ impl< Self::connect(&url).await } + /// Close the WebSocket connection gracefully. + /// This function will wait for the connection to close before returning. + /// # Errors + /// - If the WebSocket connection is already closed + /// - If the WebSocket connection cannot be closed #[cfg_attr(feature = "tracing", instrument)] pub async fn close_connection(self) -> Result<(), Error> { #[cfg(feature = "tracing")] @@ -164,9 +185,14 @@ impl< }) .await .map_err(|_| Error::WebsocketClosed)?; - rx.await.unwrap()?; - self.socket_handle.await.unwrap()?; - Ok(()) + match rx.await { + Ok(result) => result, + Err(_) => unreachable!("Socket loop always sends response before dropping one-shot"), + }?; + match self.socket_handle.await { + Ok(result) => result, + Err(_) => unreachable!("Socket loop does not panic, and is not cancelled"), + } } } From f2a8bfc2082498c89430ccc67fb2ce4a4c00fa37 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 11:52:39 -0500 Subject: [PATCH 02/10] Ensure we run the doctests too --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f5d293..21b2bdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - name: "Setup Rust Toolchain" uses: actions-rust-lang/setup-rust-toolchain@v1 - name: "Run Tests" - run: cargo test --all-features + run: cargo test --all-features --doc # Check formatting with rustfmt formatting: From 8effb7e566cfc529bd613518f03af54ac87624a1 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:03:16 -0500 Subject: [PATCH 03/10] Make sure everything is documented --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 46daad2..70d2096 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![doc = include_str!("../README.md")] - +#![deny(missing_docs)] mod error; #[cfg(feature = "mocking")] mod mock_server; From 230ba4c836631f19c5b48a11c08f85d272df59e6 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:04:10 -0500 Subject: [PATCH 04/10] Rename `UnexpectedMessage` to `UnexpectedMessageType` --- src/error.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 3c4660d..15e3bc9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,7 +17,7 @@ pub enum Error { #[error("Channel Full")] ChannelFull, #[error("Unexpected Message type: {0}")] - UnexpectedMessage(Message), + UnexpectedMessageType(Message), #[error("Serialization Error: {0}")] SerializationError(#[from] serde_json::Error), #[error("Socketeer dropped without closing")] diff --git a/src/lib.rs b/src/lib.rs index 70d2096..f1831a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,7 @@ impl< let message = serde_json::from_slice(&message)?; Ok(message) } - _ => Err(Error::UnexpectedMessage(message)), + _ => Err(Error::UnexpectedMessageType(message)), } } From f5e0afc5e1d0bef9ea62857dd49c39f85e03add5 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:05:02 -0500 Subject: [PATCH 05/10] Rename `SocketeerDropped` to `SocketeerDroppedWithoutClosing` --- src/error.rs | 2 +- src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index 15e3bc9..c0b7f6c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,5 +21,5 @@ pub enum Error { #[error("Serialization Error: {0}")] SerializationError(#[from] serde_json::Error), #[error("Socketeer dropped without closing")] - SocketeerDropped, + SocketeerDroppedWithoutClosing, } diff --git a/src/lib.rs b/src/lib.rs index f1831a5..e765024 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -245,12 +245,12 @@ async fn send_socket_message( LoopState::Running } } - Err(_) => LoopState::Error(Error::SocketeerDropped), + Err(_) => LoopState::Error(Error::SocketeerDroppedWithoutClosing), } } else { #[cfg(feature = "tracing")] error!("Socketeer dropped without closing connection"); - LoopState::Error(Error::SocketeerDropped) + LoopState::Error(Error::SocketeerDroppedWithoutClosing) } } @@ -290,7 +290,7 @@ async fn socket_message_received( } Message::Text(_) | Message::Binary(_) => match sender.send(message).await { Ok(()) => LoopState::Running, - Err(_) => LoopState::Error(Error::SocketeerDropped), + Err(_) => LoopState::Error(Error::SocketeerDroppedWithoutClosing), }, _ => LoopState::Running, }, From d5fa6bd9eb7ae71de38ab880bac16a7e8226e74b Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:05:25 -0500 Subject: [PATCH 06/10] Document `Socketeer` struct --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e765024..4dce9b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,13 @@ struct TxChannelPayload { response_tx: oneshot::Sender>, } +/// A WebSocket client that manages the connection to a WebSocket server. +/// The client can send and receive messages, and will transparently handle protocol messages. +/// # Type Parameters +/// - `RxMessage`: The type of message that the client will receive from the server. +/// - `TxMessage`: The type of message that the client will send to the server. +/// - `CHANNEL_SIZE`: The size of the internal channels used to communicate between +/// the task managing the WebSocket connection and the client. #[derive(Debug)] pub struct Socketeer< RxMessage: for<'a> Deserialize<'a> + Debug, From 205961c597f38ecf94acbbf03408903abbff405d Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:05:37 -0500 Subject: [PATCH 07/10] Document error and variants --- src/error.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index c0b7f6c..d6687cb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,25 +1,35 @@ use thiserror::Error; use tokio_tungstenite::tungstenite::Message; +/// Error type for the Socketeer library. +/// This type is used to represent all possible external errors that can occur when using the Socketeer library. #[derive(Debug, Error)] pub enum Error { /// Url Parse Error #[error("Failed to parse URL: {}", 0)] UrlParse { + /// The URL that failed to parse url: String, + /// The source of the error, from the [URL crate](https://docs.rs/url/2.2.2/url/enum.ParseError.html) #[source] source: url::ParseError, }, + /// Websocket Error + /// Error thrown by the Tungstenite library when there is an issue with the websocket connection. #[error("Tungstenite error: {0}")] WebsocketError(#[from] tokio_tungstenite::tungstenite::Error), + /// Socketeer error when the websocket connection is closed unexpectedly. #[error("Socket Closed")] WebsocketClosed, - #[error("Channel Full")] - ChannelFull, + /// Error thrown if a message type not handled by `socketeer` is received. #[error("Unexpected Message type: {0}")] UnexpectedMessageType(Message), + /// Error thrown if the message received fails to serialize or deserialize. #[error("Serialization Error: {0}")] SerializationError(#[from] serde_json::Error), + /// Error thrown if socketeer is dropped without closing the connection. + /// This error will be removed once async destructors are stabilized. + /// See [issue](https://github.com/rust-lang/rust/issues/126482) #[error("Socketeer dropped without closing")] SocketeerDroppedWithoutClosing, } From 008e3458a6cd1e915372820676c7396a8ec573d5 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:08:04 -0500 Subject: [PATCH 08/10] Document `EchoServer` control messages --- src/mock_server.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mock_server.rs b/src/mock_server.rs index 2ac49e1..13e926e 100644 --- a/src/mock_server.rs +++ b/src/mock_server.rs @@ -19,8 +19,11 @@ use tracing::debug; /// Control messages for testing with the echo server. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)] pub enum EchoControlMessage { + /// Send a message which the server should echo back Message(String), + /// Request that the server send the client a ping SendPing, + /// Request that the server close the connection Close, } From c1e91fb9acd6c596bd56255b1244b66d1c207c78 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:13:29 -0500 Subject: [PATCH 09/10] Make sure we run the sample --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b2bdc..8989561 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,8 @@ jobs: uses: actions-rust-lang/setup-rust-toolchain@v1 - name: "Run Tests" run: cargo test --all-features --doc + - name: "Run Example" + run: cd examples/echo_chat && cargo run # Check formatting with rustfmt formatting: From 385d4271d67c059c5b9b4dc7e6fb8ddaf28e6ad6 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 5 Jan 2025 15:14:44 -0500 Subject: [PATCH 10/10] Update manifest for 0.1.0 release --- Cargo.lock | 15 ++++++++------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c1f9e2..b0ddde5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "shlex", ] @@ -806,7 +806,7 @@ dependencies = [ [[package]] name = "socketeer" -version = "0.0.3" +version = "0.1.0" dependencies = [ "bytes", "futures", @@ -829,9 +829,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" -version = "2.0.93" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -851,12 +851,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", diff --git a/Cargo.toml b/Cargo.toml index 215f4c7..6e82e63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "socketeer" -version = "0.0.3" +version = "0.1.0" edition = "2021" description = "Simplified websocket client based on Tokio-Tungstenite" authors = ["Zach Heylmun "]