cosmwasm_std/results/
events.rs

1use crate::__internal::forward_ref_partial_eq;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use crate::prelude::*;
6
7/// A full [*Cosmos SDK* event].
8///
9/// This version uses string attributes (similar to [*Cosmos SDK* StringEvent]),
10/// which then get magically converted to bytes for Tendermint somewhere between
11/// the Rust-Go interface, JSON deserialization and the `NewEvent` call in Cosmos SDK.
12///
13/// [*Cosmos SDK* event]: https://docs.cosmos.network/main/learn/advanced/events
14/// [*Cosmos SDK* StringEvent]: https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/base/abci/v1beta1/abci.proto#L56-L70
15#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
16#[non_exhaustive]
17pub struct Event {
18    /// The event type. This is renamed to "ty" because "type" is reserved in Rust. This sucks, we know.
19    #[serde(rename = "type")]
20    pub ty: String,
21    /// The attributes to be included in the event.
22    ///
23    /// You can learn more about these from [*Cosmos SDK* docs].
24    ///
25    /// [*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events
26    pub attributes: Vec<Attribute>,
27}
28
29forward_ref_partial_eq!(Event, Event);
30
31impl Event {
32    /// Create a new event with the given type and an empty list of attributes.
33    pub fn new(ty: impl Into<String>) -> Self {
34        Event {
35            ty: ty.into(),
36            attributes: Vec::with_capacity(10),
37        }
38    }
39
40    /// Add an attribute to the event.
41    pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
42        self.attributes.push(Attribute {
43            key: key.into(),
44            value: value.into(),
45        });
46        self
47    }
48
49    /// Bulk add attributes to the event.
50    ///
51    /// Anything that can be turned into an iterator and yields something
52    /// that can be converted into an `Attribute` is accepted.
53    pub fn add_attributes<A: Into<Attribute>>(
54        mut self,
55        attrs: impl IntoIterator<Item = A>,
56    ) -> Self {
57        self.attributes.extend(attrs.into_iter().map(A::into));
58        self
59    }
60}
61
62/// An key value pair that is used in the context of event attributes in logs
63#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema)]
64pub struct Attribute {
65    pub key: String,
66    pub value: String,
67}
68
69forward_ref_partial_eq!(Attribute, Attribute);
70
71impl Attribute {
72    /// Creates a new Attribute. `attr` is just an alias for this.
73    pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
74        let key = key.into();
75
76        #[cfg(debug_assertions)]
77        if key.starts_with('_') {
78            panic!(
79                "attribute key `{key}` is invalid - keys starting with an underscore are reserved"
80            );
81        }
82
83        Self {
84            key,
85            value: value.into(),
86        }
87    }
88}
89
90impl<K: Into<String>, V: Into<String>> From<(K, V)> for Attribute {
91    fn from((k, v): (K, V)) -> Self {
92        Attribute::new(k, v)
93    }
94}
95
96impl<K: AsRef<str>, V: AsRef<str>> PartialEq<(K, V)> for Attribute {
97    fn eq(&self, (k, v): &(K, V)) -> bool {
98        (self.key.as_str(), self.value.as_str()) == (k.as_ref(), v.as_ref())
99    }
100}
101
102impl<K: AsRef<str>, V: AsRef<str>> PartialEq<Attribute> for (K, V) {
103    fn eq(&self, attr: &Attribute) -> bool {
104        attr == self
105    }
106}
107
108impl<K: AsRef<str>, V: AsRef<str>> PartialEq<(K, V)> for &Attribute {
109    fn eq(&self, (k, v): &(K, V)) -> bool {
110        (self.key.as_str(), self.value.as_str()) == (k.as_ref(), v.as_ref())
111    }
112}
113
114impl<K: AsRef<str>, V: AsRef<str>> PartialEq<&Attribute> for (K, V) {
115    fn eq(&self, attr: &&Attribute) -> bool {
116        attr == self
117    }
118}
119
120/// Creates a new Attribute. `Attribute::new` is an alias for this.
121#[inline]
122pub fn attr(key: impl Into<String>, value: impl Into<String>) -> Attribute {
123    Attribute::new(key, value)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::Uint128;
130
131    #[test]
132    fn event_construction() {
133        let event_direct = Event {
134            ty: "test".to_string(),
135            attributes: vec![attr("foo", "bar"), attr("bar", "baz")],
136        };
137        let event_builder = Event::new("test").add_attributes(vec![("foo", "bar"), ("bar", "baz")]);
138
139        assert_eq!(event_direct, event_builder);
140    }
141
142    #[test]
143    #[should_panic]
144    fn attribute_new_reserved_key_panicks() {
145        Attribute::new("_invalid", "value");
146    }
147
148    #[test]
149    #[should_panic]
150    fn attribute_new_reserved_key_panicks2() {
151        Attribute::new("_", "value");
152    }
153
154    #[test]
155    fn attr_works_for_different_types() {
156        let expected = ("foo", "42");
157
158        assert_eq!(attr("foo", "42"), expected);
159        assert_eq!(attr("foo", "42"), expected);
160        assert_eq!(attr("foo", "42"), expected);
161        assert_eq!(attr("foo", Uint128::new(42)), expected);
162    }
163}