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