fix: replace unwraps with errors in fullstack WebSockets (#5248)

* fix: replace unwraps with errors in fullstack WebSockets

* refactor: pull the conversions to fullstack `RequestError` up from the core crate

* no breaking changes

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Matouš VolfandJonathan Kelley authored and GitHub committed 2026-01-23 13:38:32 -08:00
1 parent 4070d0c8bd
commit 7a7f5fb719
1 file changed
+104 -6
+104 -6
View File
@@ -607,7 +607,7 @@ impl IntoRequest<UpgradingWebsocket> for WebSocketOptions {
.map(String::as_str) .map(String::as_str)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.unwrap(); .map_err(|error| RequestError::Connect(error.to_string()))?;
return Ok(UpgradingWebsocket { return Ok(UpgradingWebsocket {
protocol: Some(socket.protocol()), protocol: Some(socket.protocol()),
@@ -619,14 +619,11 @@ impl IntoRequest<UpgradingWebsocket> for WebSocketOptions {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
let response = native::send_request(request, &self.protocols) let response = native::send_request(request, &self.protocols).await?;
.await
.unwrap();
let (inner, protocol) = response let (inner, protocol) = response
.into_stream_and_protocol(self.protocols, None) .into_stream_and_protocol(self.protocols, None)
.await .await?;
.unwrap();
return Ok(UpgradingWebsocket { return Ok(UpgradingWebsocket {
protocol, protocol,
@@ -1139,6 +1136,7 @@ mod native {
use crate::ClientRequest; use crate::ClientRequest;
use super::{CloseCode, Message, WebsocketError}; use super::{CloseCode, Message, WebsocketError};
use dioxus_fullstack_core::RequestError;
use reqwest::{ use reqwest::{
header::{HeaderName, HeaderValue}, header::{HeaderName, HeaderValue},
Response, StatusCode, Version, Response, StatusCode, Version,
@@ -1466,4 +1464,104 @@ mod native {
u16::from(value).into() u16::from(value).into()
} }
} }
impl From<HandshakeError> for RequestError {
fn from(value: HandshakeError) -> Self {
let string = value.to_string();
match value {
HandshakeError::UnexpectedStatusCode(status) => {
Self::Status(string, status.as_u16())
}
HandshakeError::UnsupportedHttpVersion(_)
| HandshakeError::MissingHeader { .. }
| HandshakeError::UnexpectedHeaderValue { .. }
| HandshakeError::ExpectedAProtocol
| HandshakeError::UnexpectedProtocol { .. }
| HandshakeError::ServerRespondedWithDifferentVersion => Self::Connect(string),
}
}
}
trait IntoRequestError {
fn into_request_error(self) -> RequestError;
}
impl IntoRequestError for reqwest::Error {
fn into_request_error(self) -> RequestError {
const DEFAULT_STATUS_CODE: u16 = 0;
let string = self.to_string();
if self.is_builder() {
RequestError::Builder(string)
} else if self.is_redirect() {
RequestError::Redirect(string)
} else if self.is_status() {
RequestError::Status(
string,
self.status()
.as_ref()
.map(StatusCode::as_u16)
.unwrap_or(DEFAULT_STATUS_CODE),
)
} else if self.is_body() {
RequestError::Body(string)
} else if self.is_decode() {
RequestError::Decode(string)
} else if self.is_upgrade() {
RequestError::Connect(string)
} else {
RequestError::Request(string)
}
}
}
impl IntoRequestError for tungstenite::Error {
fn into_request_error(self) -> RequestError {
match self {
tungstenite::Error::ConnectionClosed => {
RequestError::Connect("websocket connection closed".to_owned())
}
tungstenite::Error::AlreadyClosed => {
RequestError::Connect("websocket already closed".to_owned())
}
tungstenite::Error::Io(error) => RequestError::Connect(error.to_string()),
tungstenite::Error::Tls(error) => RequestError::Connect(error.to_string()),
tungstenite::Error::Capacity(error) => RequestError::Body(error.to_string()),
tungstenite::Error::Protocol(error) => RequestError::Request(error.to_string()),
tungstenite::Error::WriteBufferFull(message) => {
RequestError::Body(message.to_string())
}
tungstenite::Error::Utf8(error) => RequestError::Decode(error),
tungstenite::Error::AttackAttempt => {
RequestError::Request("Tungstenite attack attempt detected".to_owned())
}
tungstenite::Error::Url(error) => RequestError::Builder(error.to_string()),
tungstenite::Error::Http(response) => {
let status_code = response.status();
RequestError::Status(format!("HTTP error: {status_code}"), status_code.as_u16())
}
tungstenite::Error::HttpFormat(error) => RequestError::Builder(error.to_string()),
}
}
}
impl From<WebsocketError> for RequestError {
fn from(value: WebsocketError) -> Self {
match value {
WebsocketError::ConnectionClosed { code, description } => {
Self::Connect(format!("connection closed ({code}): {description}"))
}
WebsocketError::AlreadyClosed => Self::Connect(value.to_string()),
WebsocketError::Capacity => Self::Body(value.to_string()),
WebsocketError::Unexpected => Self::Request(value.to_string()),
WebsocketError::Uninitialized => Self::Builder(value.to_string()),
WebsocketError::Handshake(error) => error.into(),
WebsocketError::Reqwest(error) => error.into_request_error(),
WebsocketError::Tungstenite(error) => error.into_request_error(),
WebsocketError::Serialization(error) => Self::Serialization(error.to_string()),
WebsocketError::Deserialization(error) => Self::Decode(error.to_string()),
WebsocketError::Json(error) => Self::Decode(error.to_string()),
WebsocketError::Cbor(error) => Self::Decode(error.to_string()),
}
}
}
} }