309 lines
10 KiB
Rust
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();
|
|
}
|
|
}
|