tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

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 }