use serde::Serialize;
use crate::prelude::*;
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Hash)]
pub struct Event {
pub kind: String,
pub attributes: Vec<EventAttribute>,
}
impl Event {
pub fn new<K, I>(kind: K, attributes: I) -> Self
where
K: Into<String>,
I: IntoIterator,
I::Item: Into<EventAttribute>,
{
Self {
kind: kind.into(),
attributes: attributes.into_iter().map(Into::into).collect(),
}
}
pub fn eq_ignoring_index(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.attributes.len() == other.attributes.len()
&& self
.attributes
.iter()
.zip(other.attributes.iter())
.all(|(a, b)| a.eq_ignoring_index(b))
}
pub fn hash_ignoring_index<H: core::hash::Hasher>(&self, state: &mut H) {
use core::hash::Hash;
self.kind.hash(state);
state.write_usize(self.attributes.len());
for attr in &self.attributes {
attr.hash_ignoring_index(state);
}
}
}
pub trait TypedEvent
where
Self: TryFrom<Event>,
Event: From<Self>,
{
fn into_event(self) -> Event {
self.into()
}
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Hash)]
pub struct EventAttribute {
pub key: String,
pub value: String,
pub index: bool,
}
impl EventAttribute {
pub fn eq_ignoring_index(&self, other: &Self) -> bool {
self.key == other.key && self.value == other.value
}
pub fn hash_ignoring_index<H: core::hash::Hasher>(&self, state: &mut H) {
use core::hash::Hash;
(&self.key, &self.value).hash(state);
}
}
impl<K: Into<String>, V: Into<String>> From<(K, V, bool)> for EventAttribute {
fn from((key, value, index): (K, V, bool)) -> Self {
EventAttribute {
key: key.into(),
value: value.into(),
index,
}
}
}
#[allow(missing_docs)]
pub trait EventAttributeIndexExt: private::Sealed {
type Key;
type Value;
fn index(self) -> (Self::Key, Self::Value, bool);
fn no_index(self) -> (Self::Key, Self::Value, bool);
}
impl<K: Into<String>, V: Into<String>> EventAttributeIndexExt for (K, V) {
type Key = K;
type Value = V;
fn index(self) -> (K, V, bool) {
let (key, value) = self;
(key, value, true)
}
fn no_index(self) -> (K, V, bool) {
let (key, value) = self;
(key, value, false)
}
}
mod private {
use crate::prelude::*;
pub trait Sealed {}
impl<K: Into<String>, V: Into<String>> Sealed for (K, V) {}
}
impl<K: Into<String>, V: Into<String>> From<(K, V)> for EventAttribute {
fn from((key, value): (K, V)) -> Self {
(key, value, false).into()
}
}
mod v0_34 {
use super::{Event, EventAttribute};
use crate::prelude::*;
use core::convert::{TryFrom, TryInto};
use celestia_core_proto::v0_34::abci as pb;
use celestia_core_proto::Protobuf;
impl From<EventAttribute> for pb::EventAttribute {
fn from(event: EventAttribute) -> Self {
Self {
key: event.key.into(),
value: event.value.into(),
index: event.index,
}
}
}
impl TryFrom<pb::EventAttribute> for EventAttribute {
type Error = crate::Error;
fn try_from(event: pb::EventAttribute) -> Result<Self, Self::Error> {
Ok(Self {
key: String::from_utf8(event.key.to_vec())
.map_err(|e| crate::Error::parse(e.to_string()))?,
value: String::from_utf8(event.value.to_vec())
.map_err(|e| crate::Error::parse(e.to_string()))?,
index: event.index,
})
}
}
impl Protobuf<pb::EventAttribute> for EventAttribute {}
impl From<Event> for pb::Event {
fn from(event: Event) -> Self {
Self {
r#type: event.kind,
attributes: event.attributes.into_iter().map(Into::into).collect(),
}
}
}
impl TryFrom<pb::Event> for Event {
type Error = crate::Error;
fn try_from(event: pb::Event) -> Result<Self, Self::Error> {
Ok(Self {
kind: event.r#type,
attributes: event
.attributes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
})
}
}
impl Protobuf<pb::Event> for Event {}
}
#[cfg(test)]
mod tests {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::redundant_clone)]
use serde::Deserialize;
use super::*;
#[test]
fn event_eq_ignoring_index_ignores_index() {
let event_a = Event::new("test", [("foo", "bar").index()]);
let event_b = Event::new("test", [("foo", "bar").no_index()]);
let event_c = Event::new("test", [("foo", "baz").index()]);
assert_eq!(event_a.eq_ignoring_index(&event_b), true);
assert_eq!(event_a.eq_ignoring_index(&event_c), false);
}
#[test]
fn exercise_typed_event() {
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Payload {
x: u32,
y: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MyEvent {
a: Payload,
b: Payload,
}
impl From<MyEvent> for Event {
fn from(event: MyEvent) -> Self {
Event::new(
"my_event",
vec![
("a", serde_json::to_string(&event.a).unwrap()).index(),
("b", serde_json::to_string(&event.b).unwrap()).index(),
],
)
}
}
impl TryFrom<Event> for MyEvent {
type Error = ();
fn try_from(event: Event) -> Result<Self, Self::Error> {
if event.kind != "my_event" {
return Err(());
}
let a = event
.attributes
.iter()
.find(|attr| attr.key == "a")
.ok_or(())
.and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?;
let b = event
.attributes
.iter()
.find(|attr| attr.key == "b")
.ok_or(())
.and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?;
Ok(MyEvent { a, b })
}
}
impl TypedEvent for MyEvent {}
let t = MyEvent {
a: Payload { x: 1, y: 2 },
b: Payload { x: 3, y: 4 },
};
let e1 = Event::from(t.clone());
let e2 = {
let mut e = e1.clone();
e.attributes[0].index = false;
e.attributes[1].index = false;
e
};
assert_eq!(e1.eq_ignoring_index(&e2), true);
assert_eq!(MyEvent::try_from(e1.clone()), Ok(t.clone()));
assert_eq!(MyEvent::try_from(e2.clone()), Ok(t.clone()));
assert_eq!(
Event::from(MyEvent::try_from(e1.clone()).unwrap()).eq_ignoring_index(&e1),
true
);
assert_eq!(
Event::from(MyEvent::try_from(e2.clone()).unwrap()).eq_ignoring_index(&e2),
true
);
}
}