use crate::{Filter, Header, Log, Transaction};
use alloc::{boxed::Box, format};
use alloy_primitives::B256;
use alloy_serde::WithOtherFields;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum SubscriptionResult<T = Transaction> {
Header(Box<WithOtherFields<Header>>),
Log(Box<Log>),
TransactionHash(B256),
FullTransaction(Box<T>),
SyncState(PubSubSyncStatus),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum PubSubSyncStatus {
Simple(bool),
Detailed(SyncStatusMetadata),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct SyncStatusMetadata {
pub syncing: bool,
pub starting_block: u64,
pub current_block: u64,
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
pub highest_block: Option<u64>,
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for SubscriptionResult<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *self {
Self::Header(ref header) => header.serialize(serializer),
Self::Log(ref log) => log.serialize(serializer),
Self::TransactionHash(ref hash) => hash.serialize(serializer),
Self::FullTransaction(ref tx) => tx.serialize(serializer),
Self::SyncState(ref sync) => sync.serialize(serializer),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub enum SubscriptionKind {
NewHeads,
Logs,
NewPendingTransactions,
Syncing,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Params {
#[default]
None,
Logs(Box<Filter>),
Bool(bool),
}
impl Params {
#[inline]
pub const fn is_bool(&self) -> bool {
matches!(self, Self::Bool(_))
}
#[inline]
pub const fn is_logs(&self) -> bool {
matches!(self, Self::Logs(_))
}
}
impl From<Filter> for Params {
fn from(filter: Filter) -> Self {
Self::Logs(Box::new(filter))
}
}
impl From<bool> for Params {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Params {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::None => (&[] as &[serde_json::Value]).serialize(serializer),
Self::Logs(logs) => logs.serialize(serializer),
Self::Bool(full) => full.serialize(serializer),
}
}
}
#[cfg(feature = "serde")]
impl<'a> serde::Deserialize<'a> for Params {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
use serde::de::Error;
let v = serde_json::Value::deserialize(deserializer)?;
if v.is_null() {
return Ok(Self::None);
}
if let Some(val) = v.as_bool() {
return Ok(val.into());
}
serde_json::from_value::<Filter>(v)
.map(Into::into)
.map_err(|e| D::Error::custom(format!("Invalid Pub-Sub parameters: {e}")))
}
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
#[test]
#[cfg(feature = "serde")]
fn params_serde() {
let s: Params = serde_json::from_str("true").unwrap();
assert_eq!(s, Params::Bool(true));
let s: Params = serde_json::from_str("null").unwrap();
assert_eq!(s, Params::None);
let filter = Filter::default();
let s: Params = serde_json::from_str(&serde_json::to_string(&filter).unwrap()).unwrap();
assert_eq!(s, Params::Logs(Box::new(filter)));
}
#[test]
fn params_is_bool() {
let param = Params::Bool(true);
assert!(param.is_bool());
let param = Params::None;
assert!(!param.is_bool());
let param = Params::Logs(Box::default());
assert!(!param.is_bool());
}
#[test]
fn params_is_logs() {
let param = Params::Logs(Box::default());
assert!(param.is_logs());
let param = Params::None;
assert!(!param.is_logs());
let param = Params::Bool(true);
assert!(!param.is_logs());
}
#[test]
fn params_from_filter() {
let filter = Filter::default();
let param: Params = filter.clone().into();
assert_eq!(param, Params::Logs(Box::new(filter)));
}
#[test]
fn params_from_bool() {
let param: Params = true.into();
assert_eq!(param, Params::Bool(true));
let param: Params = false.into();
assert_eq!(param, Params::Bool(false));
}
#[test]
#[cfg(feature = "serde")]
fn params_serialize_none() {
let param = Params::None;
let serialized = serde_json::to_string(¶m).unwrap();
assert_eq!(serialized, "[]");
}
#[test]
#[cfg(feature = "serde")]
fn params_serialize_bool() {
let param = Params::Bool(true);
let serialized = serde_json::to_string(¶m).unwrap();
assert_eq!(serialized, "true");
let param = Params::Bool(false);
let serialized = serde_json::to_string(¶m).unwrap();
assert_eq!(serialized, "false");
}
#[test]
#[cfg(feature = "serde")]
fn params_serialize_logs() {
let filter = Filter::default();
let param = Params::Logs(Box::new(filter.clone()));
let serialized = serde_json::to_string(¶m).unwrap();
let expected = serde_json::to_string(&filter).unwrap();
assert_eq!(serialized, expected);
}
}