nostrdb-rs

nostrdb in rust!
git clone git://jb55.com/nostrdb-rs
Log | Files | Refs | Submodules | README | LICENSE

block.rs (15347B)


      1 use crate::{bindings, Note, Transaction};
      2 
      3 #[derive(Debug)]
      4 pub struct Blocks<'a> {
      5     ptr: *mut bindings::ndb_blocks,
      6     txn: Option<&'a Transaction>,
      7 }
      8 
      9 #[derive(Debug)]
     10 pub struct Block<'a> {
     11     ptr: *mut bindings::ndb_block,
     12 
     13     #[allow(dead_code)]
     14     txn: Option<&'a Transaction>,
     15 }
     16 
     17 pub struct BlockIter<'a> {
     18     iter: bindings::ndb_block_iterator,
     19     txn: Option<&'a Transaction>,
     20 }
     21 
     22 #[derive(Debug, Eq, PartialEq)]
     23 pub enum BlockType {
     24     Hashtag,
     25     Text,
     26     MentionIndex,
     27     MentionBech32,
     28     Url,
     29     Invoice,
     30 }
     31 
     32 #[derive(Debug, Eq, PartialEq)]
     33 pub enum Bech32Type {
     34     Event,
     35     Pubkey,
     36     Profile,
     37     Note,
     38     Relay,
     39     Addr,
     40     Secret,
     41 }
     42 
     43 pub enum Mention<'a> {
     44     Pubkey(&'a bindings::bech32_npub),
     45     Event(&'a bindings::bech32_nevent),
     46     Profile(&'a bindings::bech32_nprofile),
     47     Note(&'a bindings::bech32_note),
     48     Relay(&'a bindings::bech32_nrelay),
     49     Secret(&'a bindings::bech32_nsec),
     50     Addr(&'a bindings::bech32_naddr),
     51 }
     52 
     53 impl bindings::ndb_str_block {
     54     pub fn as_str(&self) -> &str {
     55         unsafe {
     56             let ptr = bindings::ndb_str_block_ptr(self as *const Self as *mut Self) as *const u8;
     57             let len = bindings::ndb_str_block_len(self as *const Self as *mut Self);
     58             let byte_slice = std::slice::from_raw_parts(ptr, len.try_into().unwrap());
     59             std::str::from_utf8_unchecked(byte_slice)
     60         }
     61     }
     62 }
     63 
     64 impl bindings::bech32_nrelay {
     65     pub fn as_str(&self) -> &str {
     66         self.relay.as_str()
     67     }
     68 }
     69 
     70 impl bindings::bech32_nprofile {
     71     pub fn pubkey(&self) -> &[u8; 32] {
     72         unsafe { &*(self.pubkey as *const [u8; 32]) }
     73     }
     74 
     75     pub fn relays_iter(&self) -> impl Iterator<Item = &str> {
     76         self.relays.relays[0..(self.relays.num_relays as usize)]
     77             .iter()
     78             .map(|block| block.as_str())
     79     }
     80 }
     81 
     82 impl bindings::bech32_npub {
     83     pub fn pubkey(&self) -> &[u8; 32] {
     84         unsafe { &*(self.pubkey as *const [u8; 32]) }
     85     }
     86 }
     87 
     88 impl bindings::bech32_note {
     89     pub fn id(&self) -> &[u8; 32] {
     90         unsafe { &*(self.event_id as *const [u8; 32]) }
     91     }
     92 }
     93 
     94 impl bindings::bech32_nevent {
     95     pub fn id(&self) -> &[u8; 32] {
     96         unsafe { &*(self.event_id as *const [u8; 32]) }
     97     }
     98 
     99     pub fn pubkey(&self) -> Option<&[u8; 32]> {
    100         unsafe {
    101             if self.pubkey.is_null() {
    102                 return None;
    103             }
    104             Some(&*(self.pubkey as *const [u8; 32]))
    105         }
    106     }
    107 
    108     pub fn relays_iter(&self) -> impl Iterator<Item = &str> {
    109         self.relays.relays[0..(self.relays.num_relays as usize)]
    110             .iter()
    111             .map(|block| block.as_str())
    112     }
    113 }
    114 
    115 impl bindings::bech32_naddr {
    116     pub fn relays_iter(&self) -> impl Iterator<Item = &str> {
    117         self.relays.relays[0..(self.relays.num_relays as usize)]
    118             .iter()
    119             .map(|block| block.as_str())
    120     }
    121 }
    122 
    123 impl<'a> Mention<'a> {
    124     pub fn new(bech32: &'a bindings::nostr_bech32) -> Self {
    125         unsafe {
    126             match Bech32Type::from_ctype(bech32.type_) {
    127                 Bech32Type::Event => Mention::Event(&bech32.__bindgen_anon_1.nevent),
    128                 Bech32Type::Pubkey => Mention::Pubkey(&bech32.__bindgen_anon_1.npub),
    129                 Bech32Type::Profile => Mention::Profile(&bech32.__bindgen_anon_1.nprofile),
    130                 Bech32Type::Note => Mention::Note(&bech32.__bindgen_anon_1.note),
    131                 Bech32Type::Relay => Mention::Relay(&bech32.__bindgen_anon_1.nrelay),
    132                 Bech32Type::Secret => Mention::Secret(&bech32.__bindgen_anon_1.nsec),
    133                 Bech32Type::Addr => Mention::Addr(&bech32.__bindgen_anon_1.naddr),
    134             }
    135         }
    136     }
    137 }
    138 
    139 impl Bech32Type {
    140     pub(crate) fn from_ctype(typ: bindings::nostr_bech32_type) -> Bech32Type {
    141         match typ {
    142             1 => Bech32Type::Note,
    143             2 => Bech32Type::Pubkey,
    144             3 => Bech32Type::Profile,
    145             4 => Bech32Type::Event,
    146             5 => Bech32Type::Relay,
    147             6 => Bech32Type::Addr,
    148             7 => Bech32Type::Secret,
    149             _ => panic!("Invalid bech32 type"),
    150         }
    151     }
    152 }
    153 
    154 impl<'a> Block<'a> {
    155     #[allow(dead_code)]
    156     pub(crate) fn new_transactional(
    157         ptr: *mut bindings::ndb_block,
    158         txn: &'a Transaction,
    159     ) -> Block<'a> {
    160         Block {
    161             ptr,
    162             txn: Some(txn),
    163         }
    164     }
    165 
    166     #[allow(dead_code)]
    167     pub(crate) fn new_owned(ptr: *mut bindings::ndb_block) -> Block<'static> {
    168         Block { ptr, txn: None }
    169     }
    170 
    171     pub(crate) fn new(ptr: *mut bindings::ndb_block, txn: Option<&'a Transaction>) -> Block<'a> {
    172         Block { ptr, txn }
    173     }
    174 
    175     pub fn as_ptr(&self) -> *mut bindings::ndb_block {
    176         self.ptr
    177     }
    178 
    179     pub fn as_mention(&self) -> Option<Mention<'a>> {
    180         if self.blocktype() != BlockType::MentionBech32 {
    181             return None;
    182         }
    183         Some(Mention::new(self.c_bech32()))
    184     }
    185 
    186     pub fn as_str(&self) -> &'a str {
    187         unsafe {
    188             let str_block = bindings::ndb_block_str(self.as_ptr());
    189             if str_block.is_null() {
    190                 return "";
    191             }
    192 
    193             (*str_block).as_str()
    194         }
    195     }
    196 
    197     fn c_bech32(&self) -> &'a bindings::nostr_bech32 {
    198         unsafe { &(*self.as_ptr()).block.mention_bech32.bech32 }
    199     }
    200 
    201     pub fn blocktype(&self) -> BlockType {
    202         let typ = unsafe { bindings::ndb_get_block_type(self.as_ptr()) };
    203         match typ {
    204             1 => BlockType::Hashtag,
    205             2 => BlockType::Text,
    206             3 => BlockType::MentionIndex,
    207             4 => BlockType::MentionBech32,
    208             5 => BlockType::Url,
    209             6 => BlockType::Invoice,
    210             _ => panic!("Invalid blocktype {}", typ),
    211         }
    212     }
    213 }
    214 
    215 impl<'a> Blocks<'a> {
    216     pub(crate) fn new_transactional(
    217         ptr: *mut bindings::ndb_blocks,
    218         txn: &'a Transaction,
    219     ) -> Blocks<'a> {
    220         Blocks {
    221             ptr,
    222             txn: Some(txn),
    223         }
    224     }
    225 
    226     #[allow(dead_code)]
    227     pub(crate) fn new_owned(ptr: *mut bindings::ndb_blocks) -> Blocks<'static> {
    228         Blocks { ptr, txn: None }
    229     }
    230 
    231     pub fn iter(&self, note: &Note<'a>) -> BlockIter<'a> {
    232         let content = note.content_ptr();
    233         match self.txn {
    234             Some(txn) => BlockIter::new_transactional(content, self.as_ptr(), txn),
    235             None => BlockIter::new_owned(content, self.as_ptr()),
    236         }
    237     }
    238 
    239     pub fn as_ptr(&self) -> *mut bindings::ndb_blocks {
    240         self.ptr
    241     }
    242 }
    243 
    244 impl Drop for Blocks<'_> {
    245     fn drop(&mut self) {
    246         unsafe { bindings::ndb_blocks_free(self.as_ptr()) };
    247     }
    248 }
    249 
    250 impl<'a> BlockIter<'a> {
    251     pub(crate) fn new_transactional(
    252         content: *const ::std::os::raw::c_char,
    253         blocks: *mut bindings::ndb_blocks,
    254         txn: &'a Transaction,
    255     ) -> BlockIter<'a> {
    256         let type_ = bindings::ndb_block_type_BLOCK_TEXT;
    257         let mention_index: u32 = 1;
    258         let block = bindings::ndb_block__bindgen_ty_1 { mention_index };
    259         let block = bindings::ndb_block { type_, block };
    260         let p = blocks as *mut ::std::os::raw::c_uchar;
    261         let iter = bindings::ndb_block_iterator {
    262             content,
    263             blocks,
    264             p,
    265             block,
    266         };
    267         let mut block_iter = BlockIter {
    268             iter,
    269             txn: Some(txn),
    270         };
    271         unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut block_iter.iter) };
    272         block_iter
    273     }
    274 
    275     pub(crate) fn new_owned(
    276         content: *const ::std::os::raw::c_char,
    277         blocks: *mut bindings::ndb_blocks,
    278     ) -> BlockIter<'static> {
    279         let type_ = bindings::ndb_block_type_BLOCK_TEXT;
    280         let mention_index: u32 = 1;
    281         let block = bindings::ndb_block__bindgen_ty_1 { mention_index };
    282         let block = bindings::ndb_block { type_, block };
    283         let p = blocks as *mut ::std::os::raw::c_uchar;
    284         let mut iter = bindings::ndb_block_iterator {
    285             content,
    286             blocks,
    287             p,
    288             block,
    289         };
    290         unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut iter) };
    291         BlockIter { iter, txn: None }
    292     }
    293 
    294     pub fn as_ptr(&self) -> *const bindings::ndb_block_iterator {
    295         &self.iter
    296     }
    297 
    298     pub fn as_mut_ptr(&self) -> *mut bindings::ndb_block_iterator {
    299         self.as_ptr() as *mut bindings::ndb_block_iterator
    300     }
    301 }
    302 
    303 impl<'a> Iterator for BlockIter<'a> {
    304     type Item = Block<'a>;
    305 
    306     fn next(&mut self) -> Option<Self::Item> {
    307         let block = unsafe { bindings::ndb_blocks_iterate_next(self.as_mut_ptr()) };
    308         if block.is_null() {
    309             return None;
    310         }
    311 
    312         Some(Block::new(block, self.txn))
    313     }
    314 }
    315 
    316 /*
    317 impl<'a> IntoIterator for Blocks<'a> {
    318     type Item = Block<'a>;
    319     type IntoIter = BlockIter<'a>;
    320 
    321     fn into_iter(self) -> Self::IntoIter {
    322         match self.txn {
    323             Some(txn) => BlockIter::new_transactional(self.as_ptr(), txn),
    324             None => BlockIter::new_owned(self.as_ptr()),
    325         }
    326     }
    327 }
    328 */
    329 
    330 #[cfg(test)]
    331 mod tests {
    332     use super::*;
    333     use crate::test_util;
    334     use crate::{Config, Ndb};
    335 
    336     #[test]
    337     fn note_blocks_work() {
    338         let db = "target/testdbs/note_blocks";
    339         test_util::cleanup_db(&db);
    340 
    341         {
    342             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    343             ndb.process_event("[\"EVENT\",\"s\",{\"id\":\"d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304\",\"pubkey\":\"b5b1b5d2914daa2eda99af22ae828effe98730bf69dcca000fa37bfb9e395e32\",\"created_at\": 1703989205,\"kind\": 1,\"tags\": [],\"content\": \"#hashtags, are neat nostr:nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw https://github.com/damus-io\",\"sig\": \"07af3062616a17ef392769cadb170ac855c817c103e007c72374499bbadb2fe8917a0cc5b3fdc5aa5d56de086e128b3aeaa8868f6fe42a409767241b6a29cc94\"}]").expect("process ok");
    344         }
    345 
    346         {
    347             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    348             let id =
    349                 hex::decode("d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304")
    350                     .expect("hex id");
    351             let pubkey =
    352                 hex::decode("32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")
    353                     .expect("jb55 pubkey");
    354             let txn = Transaction::new(&ndb).expect("txn");
    355             let id_bytes: [u8; 32] = id.try_into().expect("id bytes");
    356             let pubkey_bytes: [u8; 32] = pubkey.try_into().expect("pubkey bytes");
    357             let note = ndb.get_note_by_id(&txn, &id_bytes).unwrap();
    358             let blocks = ndb
    359                 .get_blocks_by_key(&txn, note.key().unwrap())
    360                 .expect("note");
    361             let mut c = 0;
    362             for block in blocks.iter(&note) {
    363                 match c {
    364                     0 => {
    365                         assert_eq!(block.blocktype(), BlockType::Hashtag);
    366                         assert_eq!(block.as_str(), "hashtags");
    367                     }
    368 
    369                     1 => {
    370                         assert_eq!(block.blocktype(), BlockType::Text);
    371                         assert_eq!(block.as_str(), ", are neat ");
    372                     }
    373 
    374                     2 => {
    375                         assert_eq!(block.blocktype(), BlockType::MentionBech32);
    376                         assert_eq!(block.as_str(), "nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw");
    377                         match block.as_mention().unwrap() {
    378                             Mention::Profile(p) => assert_eq!(p.pubkey(), &pubkey_bytes),
    379                             _ => assert!(false),
    380                         };
    381                     }
    382 
    383                     3 => {
    384                         assert_eq!(block.blocktype(), BlockType::Text);
    385                         assert_eq!(block.as_str(), " ");
    386                     }
    387 
    388                     4 => {
    389                         assert_eq!(block.blocktype(), BlockType::Url);
    390                         assert_eq!(block.as_str(), "https://github.com/damus-io");
    391                     }
    392 
    393                     _ => assert!(false),
    394                 }
    395 
    396                 c += 1;
    397             }
    398         }
    399 
    400         test_util::cleanup_db(&db);
    401     }
    402 
    403     #[test]
    404     fn nprofile_relays_work() {
    405         let db = "target/testdbs/nprofile_relays";
    406         test_util::cleanup_db(&db);
    407 
    408         {
    409             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    410             ndb.process_event("[\"EVENT\",\"s\",{\"kind\":1,\"id\":\"06449981af8f0e00503ea88deb6c2d3e9d65e1e4f032744d9ca4949dce1ce296\",\"pubkey\":\"850605096dbfb50b929e38a6c26c3d56c425325c85e05de29b759bc0e5d6cebc\",\"created_at\":1737747111,\"tags\":[[\"p\",\"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d\"]],\"content\":\"This sample event has the mention example from NIP-19: nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p\nPls ignore ...\",\"sig\":\"26a4d8f51af0ca7f6c7140ba2487ed4a99edc03e95692e01e636917417dac78989e735559a3a05e1805f242aa634c545fd4d820b962c19301faa55b05bea5da7\"}]").expect("process ok");
    411         }
    412         {
    413             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    414             let id =
    415                 hex::decode("06449981af8f0e00503ea88deb6c2d3e9d65e1e4f032744d9ca4949dce1ce296")
    416                     .expect("hex id");
    417             let pubkey =
    418                 hex::decode("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")
    419                     .expect("fiatjaf pubkey");
    420             let txn = Transaction::new(&ndb).expect("txn");
    421             let id_bytes: [u8; 32] = id.try_into().expect("id bytes");
    422             let pubkey_bytes: [u8; 32] = pubkey.try_into().expect("pubkey bytes");
    423 
    424             let note = ndb.get_note_by_id(&txn, &id_bytes).unwrap();
    425             let blocks = ndb
    426                 .get_blocks_by_key(&txn, note.key().unwrap())
    427                 .expect("note");
    428 
    429             let mut nprofile_check = false;
    430 
    431             for (ndx, block) in blocks.iter(&note).enumerate() {
    432                 println!("block {}: {:?}: {:?}", ndx, block.blocktype(), &block);
    433                 if block.blocktype() == BlockType::MentionBech32 {
    434                     // https://github.com/nostr-protocol/nips/blob/master/19.md#examples
    435                     assert_eq!(
    436                         block.as_str(), "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
    437                     );
    438 
    439                     if let Mention::Profile(p) = block.as_mention().unwrap() {
    440                         assert_eq!(p.pubkey(), &pubkey_bytes);
    441                         assert_eq!(p.relays.num_relays, 2);
    442                         assert_eq!(p.relays.relays[0].as_str(), "wss://r.x.com");
    443                         assert_eq!(p.relays.relays[1].as_str(), "wss://djbas.sadkb.com");
    444                         let relays: Vec<&str> = p.relays_iter().collect();
    445                         assert_eq!(relays, vec!["wss://r.x.com", "wss://djbas.sadkb.com"]);
    446                         nprofile_check = true;
    447                     }
    448                 }
    449             }
    450             assert!(nprofile_check, "Expected nprofile block was not found");
    451         }
    452 
    453         test_util::cleanup_db(&db);
    454     }
    455 }