use std::{collections::HashMap, fmt};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::str::FromStr;
use super::Status;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum EventTopic {
#[serde(rename = "block")]
Block,
#[serde(rename = "transaction")]
Transaction,
#[serde(rename = "account_update")]
AccountUpdate,
#[serde(rename = "rolledback_transactions")]
RolledbackTransactions,
#[serde(rename = "reapplied_transactions")]
ReappliedTransactions,
#[serde(rename = "dkg")]
DKG,
}
impl fmt::Display for EventTopic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EventTopic::Block => write!(f, "block"),
EventTopic::Transaction => write!(f, "transaction"),
EventTopic::AccountUpdate => write!(f, "account_update"),
EventTopic::RolledbackTransactions => write!(f, "rolledback_transactions"),
EventTopic::ReappliedTransactions => write!(f, "reapplied_transactions"),
EventTopic::DKG => write!(f, "dkg"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "topic", content = "data")]
pub enum Event {
#[serde(rename = "block")]
Block(BlockEvent),
#[serde(rename = "transaction")]
Transaction(TransactionEvent),
#[serde(rename = "account_update")]
AccountUpdate(AccountUpdateEvent),
#[serde(rename = "rolledback_transactions")]
RolledbackTransactions(RolledbackTransactionsEvent),
#[serde(rename = "reapplied_transactions")]
ReappliedTransactions(ReappliedTransactionsEvent),
#[serde(rename = "dkg")]
DKG(DKGEvent),
}
impl Event {
pub fn topic(&self) -> EventTopic {
match self {
Event::Block(_) => EventTopic::Block,
Event::Transaction(_) => EventTopic::Transaction,
Event::AccountUpdate(_) => EventTopic::AccountUpdate,
Event::RolledbackTransactions(_) => EventTopic::RolledbackTransactions,
Event::ReappliedTransactions(_) => EventTopic::ReappliedTransactions,
Event::DKG(_) => EventTopic::DKG,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockEvent {
pub hash: String,
#[serde(
serialize_with = "serialize_u128_as_string",
deserialize_with = "deserialize_u128_from_string"
)]
pub timestamp: u128,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionEvent {
pub hash: String,
pub status: Status,
pub program_ids: Vec<String>,
pub block_height: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountUpdateEvent {
pub account: String,
pub transaction_hash: String,
pub block_height: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RolledbackTransactionsEvent {
pub block_height: u64,
pub transaction_hashes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReappliedTransactionsEvent {
pub block_height: u64,
pub transaction_hashes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DKGEvent {
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct EventFilter {
#[serde(flatten)]
pub criteria: HashMap<String, Value>,
}
impl EventFilter {
pub fn new() -> Self {
EventFilter {
criteria: HashMap::new(),
}
}
pub fn matches(&self, event_data: &Value) -> bool {
if self.criteria.is_empty() {
return true;
}
for (key, filter_value) in &self.criteria {
match event_data.get(key) {
Some(data_value) => {
if !Self::check_filter(data_value, filter_value) {
return false;
}
}
None => return false, }
}
true
}
fn check_filter(value: &Value, filter: &Value) -> bool {
match filter {
Value::Array(arr) => arr
.iter()
.any(|v| value.as_array().is_some_and(|arr| arr.contains(v))),
_ => value == filter,
}
}
pub fn from_value(value: Value) -> Self {
match value {
Value::Object(map) => {
let criteria = map.into_iter().collect();
EventFilter { criteria }
}
_ => EventFilter::new(),
}
}
}
fn serialize_u128_as_string<S>(value: &u128, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}
fn deserialize_u128_from_string<'de, D>(deserializer: D) -> Result<u128, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
u128::from_str(&s).map_err(serde::de::Error::custom)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_event_timestamp_serializes_as_string() {
let event = Event::Block(BlockEvent {
hash: "h".to_string(),
timestamp: 12345678901234567890u128,
});
let json = serde_json::to_string(&event).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(v.get("topic").unwrap(), "block");
let ts = v.get("data").unwrap().get("timestamp").unwrap();
assert!(ts.is_string());
assert_eq!(ts.as_str().unwrap(), "12345678901234567890");
let back: Event = serde_json::from_str(&json).unwrap();
match back {
Event::Block(be) => assert_eq!(be.timestamp, 12345678901234567890u128),
_ => panic!("expected block event"),
}
}
#[test]
fn block_event_timestamp_handles_u128_max() {
let max_val = u128::MAX;
let event = Event::Block(BlockEvent {
hash: "max".to_string(),
timestamp: max_val,
});
let json = serde_json::to_string(&event).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
let ts = v.get("data").unwrap().get("timestamp").unwrap();
assert!(ts.is_string());
assert_eq!(ts.as_str().unwrap(), max_val.to_string());
let back: Event = serde_json::from_str(&json).unwrap();
match back {
Event::Block(be) => assert_eq!(be.timestamp, max_val),
_ => panic!("expected block event"),
}
}
}