solana_dynamic_events/
lib.rs1use std::collections::BTreeMap;
4
5use base64::{engine::general_purpose::STANDARD, Engine};
6use thiserror::Error;
7use wincode_derive::{SchemaRead, SchemaWrite};
8
9#[derive(Debug, Error)]
11pub enum DynamicEventError {
12 #[error(transparent)]
14 Serialize(#[from] wincode::WriteError),
15 #[error(transparent)]
17 Deserialize(#[from] wincode::ReadError),
18 #[error(transparent)]
20 Base64Decode(#[from] base64::DecodeError),
21}
22
23#[derive(SchemaWrite, SchemaRead, PartialEq, Debug, Default)]
25pub struct DynamicEvent {
26 pub data: BTreeMap<String, String>,
28}
29
30impl DynamicEvent {
31 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) -> Option<String> {
38 self.data.insert(key.into(), value.into())
39 }
40
41 pub fn remove(&mut self, key: &str) -> Option<String> {
43 self.data.remove(key)
44 }
45
46 pub fn serialize(&self) -> Result<Vec<u8>, DynamicEventError> {
48 Ok(wincode::serialize(self)?)
49 }
50
51 pub fn deserialize(bytes: &[u8]) -> Result<Self, DynamicEventError> {
53 Ok(wincode::deserialize(bytes)?)
54 }
55
56 pub fn to_base64(&self) -> Result<String, DynamicEventError> {
58 let bytes = self.serialize()?;
59 Ok(STANDARD.encode(&bytes))
60 }
61
62 pub fn from_base64(encoded: &str) -> Result<Self, DynamicEventError> {
64 let bytes = STANDARD.decode(encoded)?;
65 Self::deserialize(&bytes)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn insert_and_remove() -> Result<(), Box<dyn std::error::Error>> {
75 let mut event = DynamicEvent::new();
76
77 let prev = event.insert("hello", "world");
78 assert!(prev.is_none());
79
80 let prev = event.insert("hello", "updated");
81 assert_eq!(prev.as_deref(), Some("world"));
82
83 let removed = event.remove("hello");
84 assert_eq!(removed.as_deref(), Some("updated"));
85
86 let removed = event.remove("hello");
87 assert!(removed.is_none());
88
89 Ok(())
90 }
91
92 #[test]
93 fn serialize_deserialize_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
94 let mut event = DynamicEvent::new();
95 event.insert("hello", "world");
96 event.insert("foo", "bar");
97
98 let bytes = event.serialize()?;
99 let restored = DynamicEvent::deserialize(&bytes)?;
100
101 assert_eq!(event, restored);
102 Ok(())
103 }
104
105 #[test]
106 fn base64_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
107 let mut event = DynamicEvent::new();
108 event.insert("key1", "value1");
109 event.insert("key2", "value2");
110
111 let encoded = event.to_base64()?;
112 let restored = DynamicEvent::from_base64(&encoded)?;
113
114 assert_eq!(event, restored);
115 Ok(())
116 }
117
118 #[test]
119 fn empty_event_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
120 let event = DynamicEvent::new();
121
122 let bytes = event.serialize()?;
123 let restored = DynamicEvent::deserialize(&bytes)?;
124 assert_eq!(event, restored);
125
126 let encoded = event.to_base64()?;
127 let restored = DynamicEvent::from_base64(&encoded)?;
128 assert_eq!(event, restored);
129
130 Ok(())
131 }
132
133 #[test]
134 fn deserialize_invalid_bytes() {
135 let garbage = &[0xFF, 0xFE, 0xFD, 0xFC, 0xFB];
136 let result = DynamicEvent::deserialize(garbage);
137 assert!(result.is_err());
138 }
139
140 #[test]
141 fn from_base64_invalid_encoding() {
142 let result = DynamicEvent::from_base64("not-valid-base64!!!");
143 assert!(result.is_err());
144 }
145
146 #[test]
147 fn numeric_key_value_permutations() -> Result<(), Box<dyn std::error::Error>> {
148 let u64_val: u64 = 1_000_000;
149 let u8_val: u8 = 255;
150 let f64_val: f64 = 3.14159;
151
152 let mut event = DynamicEvent::new();
153
154 event.insert(u64_val.to_string(), u64_val.to_string());
155 event.insert(u8_val.to_string(), u64_val.to_string());
156 event.insert(f64_val.to_string(), u64_val.to_string());
157
158 event.insert(u64_val.to_string(), u8_val.to_string());
159 event.insert(u8_val.to_string(), u8_val.to_string());
160 event.insert(f64_val.to_string(), u8_val.to_string());
161
162 event.insert(u64_val.to_string(), f64_val.to_string());
163 event.insert(u8_val.to_string(), f64_val.to_string());
164 event.insert(f64_val.to_string(), f64_val.to_string());
165
166 assert_eq!(event.data.len(), 3);
167
168 let encoded = event.to_base64()?;
169 let restored = DynamicEvent::from_base64(&encoded)?;
170 assert_eq!(event, restored);
171
172 Ok(())
173 }
174}