event_view.rs (9938B)
1 use crate::errors::{GroupError, GroupErrorKind}; 2 use pocket_types::{Event as PocketEvent, OwnedEvent as PocketOwnedEvent, TagsStringIter}; 3 use std::str; 4 #[cfg(test)] 5 use tangle_protocol::{Event, Tag}; 6 use tangle_protocol::{EventId, Kind, PublicKeyHex, UnixTimestamp}; 7 8 pub trait GroupEventView { 9 fn id_hex(&self) -> String; 10 11 fn pubkey_hex(&self) -> String; 12 13 fn kind_u32(&self) -> u32; 14 15 fn created_at_unix(&self) -> u64; 16 17 fn visit_tags<'a, F>(&'a self, visitor: F) -> Result<(), GroupError> 18 where 19 F: FnMut(GroupEventTag<'a>) -> Result<(), GroupError>; 20 21 fn id(&self) -> Result<EventId, GroupError> { 22 EventId::new(&self.id_hex()).map_err(event_view_scalar_error) 23 } 24 25 fn pubkey(&self) -> Result<PublicKeyHex, GroupError> { 26 PublicKeyHex::new(&self.pubkey_hex()).map_err(event_view_scalar_error) 27 } 28 29 fn kind(&self) -> Result<Kind, GroupError> { 30 Kind::new(u64::from(self.kind_u32())).map_err(event_view_scalar_error) 31 } 32 33 fn created_at(&self) -> UnixTimestamp { 34 UnixTimestamp::new(self.created_at_unix()) 35 } 36 } 37 38 #[derive(Debug, Clone, PartialEq, Eq)] 39 pub struct GroupEventTag<'a> { 40 values: Vec<&'a str>, 41 } 42 43 impl<'a> GroupEventTag<'a> { 44 pub fn first_value(&self) -> Option<&'a str> { 45 self.value(0) 46 } 47 48 pub fn value(&self, index: usize) -> Option<&'a str> { 49 self.values.get(index).copied() 50 } 51 52 pub fn values(&self) -> &[&'a str] { 53 &self.values 54 } 55 56 pub fn indexed_pair(&self) -> Option<(&'a str, &'a str)> { 57 let name = self.first_value()?; 58 if !is_indexable_tag_name(name) { 59 return None; 60 } 61 self.value(1).map(|value| (name, value)) 62 } 63 64 #[cfg(test)] 65 fn from_tangle(tag: &'a Tag) -> Self { 66 Self { 67 values: tag.values().iter().map(String::as_str).collect(), 68 } 69 } 70 71 fn from_pocket(mut values: TagsStringIter<'a>) -> Result<Self, GroupError> { 72 let mut tag_values = Vec::new(); 73 for value in values.by_ref() { 74 tag_values.push(tag_value_utf8(value)?); 75 } 76 Ok(Self { values: tag_values }) 77 } 78 } 79 80 #[cfg(test)] 81 impl GroupEventView for Event { 82 fn id_hex(&self) -> String { 83 self.id().as_str().to_owned() 84 } 85 86 fn pubkey_hex(&self) -> String { 87 self.unsigned().pubkey().as_str().to_owned() 88 } 89 90 fn kind_u32(&self) -> u32 { 91 self.unsigned().kind().as_u32() 92 } 93 94 fn created_at_unix(&self) -> u64 { 95 self.unsigned().created_at().as_u64() 96 } 97 98 fn visit_tags<'a, F>(&'a self, mut visitor: F) -> Result<(), GroupError> 99 where 100 F: FnMut(GroupEventTag<'a>) -> Result<(), GroupError>, 101 { 102 for tag in self.unsigned().tags() { 103 visitor(GroupEventTag::from_tangle(tag))?; 104 } 105 Ok(()) 106 } 107 } 108 109 impl GroupEventView for PocketEvent { 110 fn id_hex(&self) -> String { 111 self.id().as_hex_string() 112 } 113 114 fn pubkey_hex(&self) -> String { 115 self.pubkey().as_hex_string() 116 } 117 118 fn kind_u32(&self) -> u32 { 119 u32::from(self.kind().as_u16()) 120 } 121 122 fn created_at_unix(&self) -> u64 { 123 self.created_at().as_u64() 124 } 125 126 fn visit_tags<'a, F>(&'a self, mut visitor: F) -> Result<(), GroupError> 127 where 128 F: FnMut(GroupEventTag<'a>) -> Result<(), GroupError>, 129 { 130 let tags = self.tags().map_err(pocket_tags_error)?; 131 for tag in tags.iter() { 132 visitor(GroupEventTag::from_pocket(tag)?)?; 133 } 134 Ok(()) 135 } 136 } 137 138 impl GroupEventView for PocketOwnedEvent { 139 fn id_hex(&self) -> String { 140 let event: &PocketEvent = self; 141 event.id_hex() 142 } 143 144 fn pubkey_hex(&self) -> String { 145 let event: &PocketEvent = self; 146 event.pubkey_hex() 147 } 148 149 fn kind_u32(&self) -> u32 { 150 let event: &PocketEvent = self; 151 event.kind_u32() 152 } 153 154 fn created_at_unix(&self) -> u64 { 155 let event: &PocketEvent = self; 156 event.created_at_unix() 157 } 158 159 fn visit_tags<'a, F>(&'a self, visitor: F) -> Result<(), GroupError> 160 where 161 F: FnMut(GroupEventTag<'a>) -> Result<(), GroupError>, 162 { 163 let event: &PocketEvent = self; 164 event.visit_tags(visitor) 165 } 166 } 167 168 fn is_indexable_tag_name(value: &str) -> bool { 169 let mut bytes = value.bytes(); 170 let Some(byte) = bytes.next() else { 171 return false; 172 }; 173 bytes.next().is_none() && byte.is_ascii_alphabetic() 174 } 175 176 fn tag_value_utf8(value: &[u8]) -> Result<&str, GroupError> { 177 str::from_utf8(value).map_err(|_| { 178 GroupError::invalid( 179 GroupErrorKind::MalformedGroupTag, 180 "group event tag is not valid UTF-8", 181 ) 182 }) 183 } 184 185 fn pocket_tags_error(error: pocket_types::Error) -> GroupError { 186 GroupError::invalid( 187 GroupErrorKind::MalformedGroupTag, 188 format!("malformed Pocket event tags: {error}"), 189 ) 190 } 191 192 fn event_view_scalar_error(error: String) -> GroupError { 193 GroupError::internal(error) 194 } 195 196 #[cfg(test)] 197 mod tests { 198 use super::GroupEventView; 199 use pocket_types::{Event as PocketEvent, OwnedEvent as PocketOwnedEvent}; 200 use tangle_protocol::{ 201 Event, EventId, Kind, PublicKeyHex, SignatureHex, Tag, UnixTimestamp, UnsignedEvent, 202 event_to_value, 203 }; 204 205 #[test] 206 fn tangle_event_view_exposes_group_fields() { 207 let event = event(); 208 209 assert_eq!(event.kind_u32(), 1); 210 assert_eq!(event.created_at_unix(), 42); 211 assert_eq!(event.created_at(), UnixTimestamp::new(42)); 212 assert_eq!(event.id_hex(), "0".repeat(64)); 213 assert_eq!(event.pubkey_hex(), "1".repeat(64)); 214 assert_eq!( 215 indexed_pairs(&event), 216 vec![("h".to_owned(), "Farm".to_owned())] 217 ); 218 assert_eq!( 219 tag_values(&event, "role"), 220 vec![vec!["role".to_owned(), "moderator".to_owned()]] 221 ); 222 assert_eq!( 223 tag_values(&event, "supported_kinds"), 224 vec![vec![ 225 "supported_kinds".to_owned(), 226 "1".to_owned(), 227 "9".to_owned() 228 ]] 229 ); 230 } 231 232 #[test] 233 fn pocket_event_view_exposes_group_fields_without_tangle_reparse() { 234 let event = event(); 235 let raw = event_to_value(&event).to_string(); 236 let mut buffer = vec![0; 4096]; 237 let (_, pocket) = PocketEvent::from_json(raw.as_bytes(), &mut buffer).expect("pocket"); 238 239 assert_eq!(pocket.kind_u32(), 1); 240 assert_eq!(pocket.created_at_unix(), 42); 241 assert_eq!( 242 <PocketEvent as GroupEventView>::created_at(pocket), 243 UnixTimestamp::new(42) 244 ); 245 assert_eq!(pocket.id_hex(), "0".repeat(64)); 246 assert_eq!(pocket.pubkey_hex(), "1".repeat(64)); 247 assert_eq!( 248 indexed_pairs(pocket), 249 vec![("h".to_owned(), "Farm".to_owned())] 250 ); 251 assert_eq!( 252 tag_values(pocket, "role"), 253 vec![vec!["role".to_owned(), "moderator".to_owned()]] 254 ); 255 assert_eq!( 256 tag_values(pocket, "supported_kinds"), 257 vec![vec![ 258 "supported_kinds".to_owned(), 259 "1".to_owned(), 260 "9".to_owned() 261 ]] 262 ); 263 } 264 265 #[test] 266 fn owned_pocket_event_view_exposes_group_fields() { 267 let event = event(); 268 let raw = event_to_value(&event).to_string(); 269 let mut buffer = vec![0; 4096]; 270 let (_, pocket) = PocketEvent::from_json(raw.as_bytes(), &mut buffer).expect("pocket"); 271 let owned: PocketOwnedEvent = pocket.to_owned(); 272 273 assert_eq!(owned.kind_u32(), 1); 274 assert_eq!(owned.created_at_unix(), 42); 275 assert_eq!( 276 <PocketOwnedEvent as GroupEventView>::created_at(&owned), 277 UnixTimestamp::new(42) 278 ); 279 assert_eq!(owned.id_hex(), "0".repeat(64)); 280 assert_eq!(owned.pubkey_hex(), "1".repeat(64)); 281 assert_eq!( 282 tag_values(&owned, "supported_kinds"), 283 vec![vec![ 284 "supported_kinds".to_owned(), 285 "1".to_owned(), 286 "9".to_owned() 287 ]] 288 ); 289 } 290 291 fn indexed_pairs<E: GroupEventView + ?Sized>(event: &E) -> Vec<(String, String)> { 292 let mut pairs = Vec::new(); 293 event 294 .visit_tags(|tag| { 295 if let Some((name, value)) = tag.indexed_pair() { 296 pairs.push((name.to_owned(), value.to_owned())); 297 } 298 Ok(()) 299 }) 300 .expect("visit tags"); 301 pairs 302 } 303 304 fn tag_values<E: GroupEventView + ?Sized>(event: &E, name: &str) -> Vec<Vec<String>> { 305 let mut values = Vec::new(); 306 event 307 .visit_tags(|tag| { 308 if tag.first_value().is_some_and(|value| value == name) { 309 values.push( 310 tag.values() 311 .iter() 312 .map(|value| (*value).to_owned()) 313 .collect(), 314 ); 315 } 316 Ok(()) 317 }) 318 .expect("visit tags"); 319 values 320 } 321 322 fn event() -> Event { 323 Event::new( 324 EventId::new(&"0".repeat(64)).expect("id"), 325 UnsignedEvent::new( 326 PublicKeyHex::new(&"1".repeat(64)).expect("pubkey"), 327 UnixTimestamp::new(42), 328 Kind::new(1).expect("kind"), 329 vec![ 330 Tag::from_parts("h", &["Farm"]).expect("h"), 331 Tag::from_parts("role", &["moderator"]).expect("role"), 332 Tag::from_parts("supported_kinds", &["1", "9"]).expect("supported kinds"), 333 Tag::from_parts("summary", &["Harvest"]).expect("summary"), 334 ], 335 "", 336 ), 337 SignatureHex::new(&"2".repeat(128)).expect("sig"), 338 ) 339 } 340 }