Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop using #[serde(untagged)] for Message enum #206

Closed
wants to merge 13 commits into from
31 changes: 29 additions & 2 deletions chromiumoxide_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use std::str::FromStr;

use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -199,8 +200,7 @@ pub struct Response {
/// An incoming message read from the web socket can either be a response to a
/// previously submitted `Request`, identified by an identifier `id`, or an
/// `Event` emitted by the server.
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum Message<T = CdpJsonEventMessage> {
/// A response for a request
Expand All @@ -209,6 +209,33 @@ pub enum Message<T = CdpJsonEventMessage> {
Event(T),
}

// `#[serde(untagged)]` does not work correctly with serde_json and some numeric types in the schema,
// without activating `arbitrary_precision` feature of serde_json and using `serde_json::Number` instead of all usage of `f64` or else.
// For now, we use a `FromStr` trait to deserialize the message, instead of `Deserialize` with
// using `serde_json` to deserialize the message to `serde_json::Value` at first and then trying to
// deserialize both `Response` or `Event` from the `serde_json::Value`.
// - https://github.com/serde-rs/serde/issues/2661
// Also, this has a win in error reporting compared to `untagged` because we can return the
// detailed error instead of just "did not match any variant of untagged enum".
impl<T> FromStr for Message<T>
where
T: DeserializeOwned + Debug,
{
type Err = serde_json::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(response) = serde_json::from_str::<Response>(s) {
Ok(Message::Response(response))
} else {
// For now, returns an error only about the event deserialization.
// Ideally, we can return an custom error type that contains errors for both response and event.
// It seems not necessary for now because the response seems to be deserialized correctly in most cases.
let event = serde_json::from_str::<T>(s)?;
Ok(Message::Event(event))
}
}
}

/// A response can either contain the `Command::Response` type in the `result`
/// field of the payload or an `Error` in the `error` field if the request
/// resulted in an error.
Expand Down
33 changes: 23 additions & 10 deletions src/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::marker::PhantomData;
use std::pin::Pin;
use std::task::ready;

use async_tungstenite::tungstenite::Message as WsMessage;
use async_tungstenite::{tungstenite::protocol::WebSocketConfig, WebSocketStream};
use futures::stream::Stream;
use futures::task::{Context, Poll};
Expand Down Expand Up @@ -132,21 +133,33 @@ impl<T: EventMessage + Unpin> Stream for Connection<T> {
pin.pending_flush = Some(call);
}
}

break;
}

// read from the ws
match ready!(pin.ws.poll_next_unpin(cx)) {
Some(Ok(msg)) => match serde_json::from_slice::<Message<T>>(&msg.into_data()) {
Ok(msg) => {
tracing::trace!("Received {:?}", msg);
Poll::Ready(Some(Ok(msg)))
}
Err(err) => {
tracing::error!("Failed to deserialize WS response {}", err);
Poll::Ready(Some(Err(err.into())))
}
},
Some(Ok(WsMessage::Text(text))) => {
let ready = match text.parse::<Message<T>>() {
Ok(msg) => {
tracing::trace!("Received {:?}", msg);
Ok(msg)
}
Err(err) => {
tracing::debug!(target: "chromiumoxide::conn::raw_ws::parse_errors", msg = text, "Failed to parse raw WS message");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tracing::debug!(target: "chromiumoxide::conn::raw_ws::parse_errors", msg = text, "Failed to parse raw WS message");
tracing::trace!(target: "chromiumoxide::conn::raw_ws::parse_errors", msg = text, "Failed to parse raw WS message");

This can potentially be a large steam of data, let's downgrade the log level here?

tracing::error!("Failed to deserialize WS response {}", err);
Err(err.into())
}
};
Poll::Ready(Some(ready))
}
Some(Ok(WsMessage::Close(_))) => Poll::Ready(None),
// ignore ping and pong
Some(Ok(WsMessage::Ping(_))) | Some(Ok(WsMessage::Pong(_))) => {
cx.waker().wake_by_ref();
Poll::Pending
}
Some(Ok(msg)) => Poll::Ready(Some(Err(CdpError::UnexpectedWsMessage(msg)))),
Some(Err(err)) => Poll::Ready(Some(Err(CdpError::Ws(err)))),
None => {
// ws connection closed
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::process::ExitStatus;
use std::time::Instant;

use async_tungstenite::tungstenite;
use async_tungstenite::tungstenite::Message;
use base64::DecodeError;
use futures::channel::mpsc::SendError;
use futures::channel::oneshot::Canceled;
Expand All @@ -28,6 +29,8 @@ pub enum CdpError {
Chrome(#[from] chromiumoxide_types::Error),
#[error("Received no response from the chromium instance.")]
NoResponse,
#[error("Received unexpected ws message: {0:?}")]
UnexpectedWsMessage(Message),
#[error("{0}")]
ChannelSendError(#[from] ChannelError),
#[error("Browser process exited with status {0:?} before websocket URL could be resolved, stderr: {1:?}")]
Expand Down