ethers-rs/ethers-providers/src/rpc/transports/ws/types.rs

309 lines
10 KiB
Rust

use std::fmt;
use ethers_core::types::U256;
use futures_channel::{mpsc, oneshot};
use serde::{de, Deserialize};
use serde_json::value::RawValue;
use crate::{common::Request, JsonRpcError};
// Normal JSON-RPC response
pub type Response = Result<Box<RawValue>, JsonRpcError>;
#[derive(serde::Deserialize, serde::Serialize)]
pub struct SubId(pub U256);
impl SubId {
pub(super) fn serialize_raw(&self) -> Result<Box<RawValue>, serde_json::Error> {
let s = serde_json::to_string(&self)?;
RawValue::from_string(s)
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Notification {
pub subscription: U256,
pub result: Box<RawValue>,
}
#[derive(Debug, Clone)]
pub enum PubSubItem {
Success { id: u64, result: Box<RawValue> },
Error { id: u64, error: JsonRpcError },
Notification { params: Notification },
}
// FIXME: ideally, this could be auto-derived as an untagged enum, but due to
// https://github.com/serde-rs/serde/issues/1183 this currently fails
impl<'de> Deserialize<'de> for PubSubItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ResponseVisitor;
impl<'de> de::Visitor<'de> for ResponseVisitor {
type Value = PubSubItem;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid jsonrpc 2.0 response object")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut jsonrpc = false;
// response & error
let mut id = None;
// only response
let mut result = None;
// only error
let mut error = None;
// only notification
let mut method = None;
let mut params = None;
while let Some(key) = map.next_key()? {
match key {
"jsonrpc" => {
if jsonrpc {
return Err(de::Error::duplicate_field("jsonrpc"))
}
let value = map.next_value()?;
if value != "2.0" {
return Err(de::Error::invalid_value(
de::Unexpected::Str(value),
&"2.0",
))
}
jsonrpc = true;
}
"id" => {
if id.is_some() {
return Err(de::Error::duplicate_field("id"))
}
let value: u64 = map.next_value()?;
id = Some(value);
}
"result" => {
if result.is_some() {
return Err(de::Error::duplicate_field("result"))
}
let value: Box<RawValue> = map.next_value()?;
result = Some(value);
}
"error" => {
if error.is_some() {
return Err(de::Error::duplicate_field("error"))
}
let value: JsonRpcError = map.next_value()?;
error = Some(value);
}
"method" => {
if method.is_some() {
return Err(de::Error::duplicate_field("method"))
}
let value: String = map.next_value()?;
method = Some(value);
}
"params" => {
if params.is_some() {
return Err(de::Error::duplicate_field("params"))
}
let value: Notification = map.next_value()?;
params = Some(value);
}
key => {
return Err(de::Error::unknown_field(
key,
&["id", "jsonrpc", "result", "error", "params", "method"],
))
}
}
}
// jsonrpc version must be present in all responses
if !jsonrpc {
return Err(de::Error::missing_field("jsonrpc"))
}
match (id, result, error, method, params) {
(Some(id), Some(result), None, None, None) => {
Ok(PubSubItem::Success { id, result })
}
(Some(id), None, Some(error), None, None) => {
Ok(PubSubItem::Error { id, error })
}
(None, None, None, Some(_), Some(params)) => {
Ok(PubSubItem::Notification { params })
}
_ => Err(de::Error::custom(
"response must be either a success/error or notification object",
)),
}
}
}
deserializer.deserialize_map(ResponseVisitor)
}
}
impl std::fmt::Display for PubSubItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PubSubItem::Success { id, .. } => write!(f, "Req success. ID: {id}"),
PubSubItem::Error { id, .. } => write!(f, "Req error. ID: {id}"),
PubSubItem::Notification { params } => {
write!(f, "Notification for sub: {:?}", params.subscription)
}
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionDetails {
pub url: String,
#[cfg(not(target_arch = "wasm32"))]
pub auth: Option<crate::Authorization>,
}
impl ConnectionDetails {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(url: impl AsRef<str>, auth: Option<crate::Authorization>) -> Self {
Self { url: url.as_ref().to_string(), auth }
}
#[cfg(target_arch = "wasm32")]
pub fn new(url: impl AsRef<str>) -> Self {
Self { url: url.as_ref().to_string() }
}
}
impl<T> From<T> for ConnectionDetails
where
T: AsRef<str>,
{
#[cfg(not(target_arch = "wasm32"))]
fn from(value: T) -> Self {
ConnectionDetails { url: value.as_ref().to_string(), auth: None }
}
#[cfg(target_arch = "wasm32")]
fn from(value: T) -> Self {
ConnectionDetails { url: value.as_ref().to_string() }
}
}
#[derive(Debug)]
pub(super) struct InFlight {
pub method: String,
pub params: Box<RawValue>,
pub channel: oneshot::Sender<Response>,
}
impl InFlight {
pub(super) fn to_request(&self, id: u64) -> Request<'_, Box<RawValue>> {
Request::new(id, &self.method, self.params.clone())
}
pub(super) fn serialize_raw(&self, id: u64) -> Result<Box<RawValue>, serde_json::Error> {
let s = serde_json::to_string(&self.to_request(id))?;
RawValue::from_string(s)
}
}
#[derive(Debug)]
pub(super) struct ActiveSub {
pub params: Box<RawValue>,
pub channel: mpsc::UnboundedSender<Box<RawValue>>,
pub current_server_id: Option<U256>,
}
impl ActiveSub {
pub(super) fn to_request(&self, id: u64) -> Request<'static, Box<RawValue>> {
Request::new(id, "eth_subscribe", self.params.clone())
}
pub(super) fn serialize_raw(&self, id: u64) -> Result<Box<RawValue>, serde_json::Error> {
let s = serde_json::to_string(&self.to_request(id))?;
RawValue::from_string(s)
}
}
/// Instructions for the `WsServer`.
pub enum Instruction {
/// JSON-RPC request
Request { method: String, params: Box<RawValue>, sender: oneshot::Sender<Response> },
/// Cancel an existing subscription
Unsubscribe { id: U256 },
}
#[cfg(target_arch = "wasm32")]
mod aliases {
pub use wasm_bindgen::prelude::*;
pub use wasm_bindgen_futures::spawn_local;
pub use ws_stream_wasm::*;
pub type Message = WsMessage;
pub type WsError = ws_stream_wasm::WsErr;
pub type WsStreamItem = Message;
pub type InternalStream = futures_util::stream::Fuse<WsStream>;
}
#[cfg(not(target_arch = "wasm32"))]
mod aliases {
pub use tokio_tungstenite::{
connect_async,
tungstenite::{self, protocol::CloseFrame},
};
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
pub type Message = tungstenite::protocol::Message;
pub type WsError = tungstenite::Error;
pub type WsStreamItem = Result<Message, WsError>;
pub use http::Request as HttpRequest;
pub use tracing::{debug, error, trace, warn};
pub use tungstenite::client::IntoClientRequest;
pub use tokio::time::sleep;
pub type InternalStream =
futures_util::stream::Fuse<WebSocketStream<MaybeTlsStream<tokio::net::TcpStream>>>;
impl IntoClientRequest for super::ConnectionDetails {
fn into_client_request(
self,
) -> tungstenite::Result<tungstenite::handshake::client::Request> {
let mut request: HttpRequest<()> = self.url.into_client_request()?;
if let Some(auth) = self.auth {
let mut auth_value = http::HeaderValue::from_str(&auth.to_string())?;
auth_value.set_sensitive(true);
request.headers_mut().insert(http::header::AUTHORIZATION, auth_value);
}
request.into_client_request()
}
}
}
pub use aliases::*;
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_desers_pubsub_items() {
let a = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0xcd0c3e8af590364c09d0fa6a1210faf5\"}";
serde_json::from_str::<PubSubItem>(a).unwrap();
}
}