nostrdb-rs

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

block.rs (11629B)


      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 
     76 impl bindings::bech32_npub {
     77     pub fn pubkey(&self) -> &[u8; 32] {
     78         unsafe { &*(self.pubkey as *const [u8; 32]) }
     79     }
     80 }
     81 
     82 impl bindings::bech32_note {
     83     pub fn id(&self) -> &[u8; 32] {
     84         unsafe { &*(self.event_id as *const [u8; 32]) }
     85     }
     86 }
     87 
     88 impl bindings::bech32_nevent {
     89     pub fn id(&self) -> &[u8; 32] {
     90         unsafe { &*(self.event_id as *const [u8; 32]) }
     91     }
     92 
     93     pub fn pubkey(&self) -> Option<&[u8; 32]> {
     94         unsafe {
     95             if self.pubkey.is_null() {
     96                 return None;
     97             }
     98             Some(&*(self.pubkey as *const [u8; 32]))
     99         }
    100     }
    101 }
    102 
    103 impl<'a> Mention<'a> {
    104     pub fn new(bech32: &'a bindings::nostr_bech32) -> Self {
    105         unsafe {
    106             match Bech32Type::from_ctype(bech32.type_) {
    107                 Bech32Type::Event => Mention::Event(&bech32.__bindgen_anon_1.nevent),
    108                 Bech32Type::Pubkey => Mention::Pubkey(&bech32.__bindgen_anon_1.npub),
    109                 Bech32Type::Profile => Mention::Profile(&bech32.__bindgen_anon_1.nprofile),
    110                 Bech32Type::Note => Mention::Note(&bech32.__bindgen_anon_1.note),
    111                 Bech32Type::Relay => Mention::Relay(&bech32.__bindgen_anon_1.nrelay),
    112                 Bech32Type::Secret => Mention::Secret(&bech32.__bindgen_anon_1.nsec),
    113                 Bech32Type::Addr => Mention::Addr(&bech32.__bindgen_anon_1.naddr),
    114             }
    115         }
    116     }
    117 }
    118 
    119 impl Bech32Type {
    120     pub(crate) fn from_ctype(typ: bindings::nostr_bech32_type) -> Bech32Type {
    121         match typ {
    122             1 => Bech32Type::Note,
    123             2 => Bech32Type::Pubkey,
    124             3 => Bech32Type::Profile,
    125             4 => Bech32Type::Event,
    126             5 => Bech32Type::Relay,
    127             6 => Bech32Type::Addr,
    128             7 => Bech32Type::Secret,
    129             _ => panic!("Invalid bech32 type"),
    130         }
    131     }
    132 }
    133 
    134 impl<'a> Block<'a> {
    135     #[allow(dead_code)]
    136     pub(crate) fn new_transactional(
    137         ptr: *mut bindings::ndb_block,
    138         txn: &'a Transaction,
    139     ) -> Block<'a> {
    140         Block {
    141             ptr,
    142             txn: Some(txn),
    143         }
    144     }
    145 
    146     #[allow(dead_code)]
    147     pub(crate) fn new_owned(ptr: *mut bindings::ndb_block) -> Block<'static> {
    148         Block { ptr, txn: None }
    149     }
    150 
    151     pub(crate) fn new(ptr: *mut bindings::ndb_block, txn: Option<&'a Transaction>) -> Block<'a> {
    152         Block { ptr, txn }
    153     }
    154 
    155     pub fn as_ptr(&self) -> *mut bindings::ndb_block {
    156         self.ptr
    157     }
    158 
    159     pub fn as_mention(&self) -> Option<Mention<'a>> {
    160         if self.blocktype() != BlockType::MentionBech32 {
    161             return None;
    162         }
    163         Some(Mention::new(self.c_bech32()))
    164     }
    165 
    166     pub fn as_str(&self) -> &'a str {
    167         unsafe {
    168             let str_block = bindings::ndb_block_str(self.as_ptr());
    169             if str_block.is_null() {
    170                 return "";
    171             }
    172 
    173             (*str_block).as_str()
    174         }
    175     }
    176 
    177     fn c_bech32(&self) -> &'a bindings::nostr_bech32 {
    178         unsafe { &(*self.as_ptr()).block.mention_bech32.bech32 }
    179     }
    180 
    181     pub fn blocktype(&self) -> BlockType {
    182         let typ = unsafe { bindings::ndb_get_block_type(self.as_ptr()) };
    183         match typ {
    184             1 => BlockType::Hashtag,
    185             2 => BlockType::Text,
    186             3 => BlockType::MentionIndex,
    187             4 => BlockType::MentionBech32,
    188             5 => BlockType::Url,
    189             6 => BlockType::Invoice,
    190             _ => panic!("Invalid blocktype {}", typ),
    191         }
    192     }
    193 }
    194 
    195 impl<'a> Blocks<'a> {
    196     pub(crate) fn new_transactional(
    197         ptr: *mut bindings::ndb_blocks,
    198         txn: &'a Transaction,
    199     ) -> Blocks<'a> {
    200         Blocks {
    201             ptr,
    202             txn: Some(txn),
    203         }
    204     }
    205 
    206     #[allow(dead_code)]
    207     pub(crate) fn new_owned(ptr: *mut bindings::ndb_blocks) -> Blocks<'static> {
    208         Blocks { ptr, txn: None }
    209     }
    210 
    211     pub fn iter(&self, note: &Note<'a>) -> BlockIter<'a> {
    212         let content = note.content_ptr();
    213         match self.txn {
    214             Some(txn) => BlockIter::new_transactional(content, self.as_ptr(), txn),
    215             None => BlockIter::new_owned(content, self.as_ptr()),
    216         }
    217     }
    218 
    219     pub fn as_ptr(&self) -> *mut bindings::ndb_blocks {
    220         self.ptr
    221     }
    222 }
    223 
    224 impl Drop for Blocks<'_> {
    225     fn drop(&mut self) {
    226         unsafe { bindings::ndb_blocks_free(self.as_ptr()) };
    227     }
    228 }
    229 
    230 impl<'a> BlockIter<'a> {
    231     pub(crate) fn new_transactional(
    232         content: *const ::std::os::raw::c_char,
    233         blocks: *mut bindings::ndb_blocks,
    234         txn: &'a Transaction,
    235     ) -> BlockIter<'a> {
    236         let type_ = bindings::ndb_block_type_BLOCK_TEXT;
    237         let mention_index: u32 = 1;
    238         let block = bindings::ndb_block__bindgen_ty_1 { mention_index };
    239         let block = bindings::ndb_block { type_, block };
    240         let p = blocks as *mut ::std::os::raw::c_uchar;
    241         let iter = bindings::ndb_block_iterator {
    242             content,
    243             blocks,
    244             p,
    245             block,
    246         };
    247         let mut block_iter = BlockIter {
    248             iter,
    249             txn: Some(txn),
    250         };
    251         unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut block_iter.iter) };
    252         block_iter
    253     }
    254 
    255     pub(crate) fn new_owned(
    256         content: *const ::std::os::raw::c_char,
    257         blocks: *mut bindings::ndb_blocks,
    258     ) -> BlockIter<'static> {
    259         let type_ = bindings::ndb_block_type_BLOCK_TEXT;
    260         let mention_index: u32 = 1;
    261         let block = bindings::ndb_block__bindgen_ty_1 { mention_index };
    262         let block = bindings::ndb_block { type_, block };
    263         let p = blocks as *mut ::std::os::raw::c_uchar;
    264         let mut iter = bindings::ndb_block_iterator {
    265             content,
    266             blocks,
    267             p,
    268             block,
    269         };
    270         unsafe { bindings::ndb_blocks_iterate_start(content, blocks, &mut iter) };
    271         BlockIter { iter, txn: None }
    272     }
    273 
    274     pub fn as_ptr(&self) -> *const bindings::ndb_block_iterator {
    275         &self.iter
    276     }
    277 
    278     pub fn as_mut_ptr(&self) -> *mut bindings::ndb_block_iterator {
    279         self.as_ptr() as *mut bindings::ndb_block_iterator
    280     }
    281 }
    282 
    283 impl<'a> Iterator for BlockIter<'a> {
    284     type Item = Block<'a>;
    285 
    286     fn next(&mut self) -> Option<Self::Item> {
    287         let block = unsafe { bindings::ndb_blocks_iterate_next(self.as_mut_ptr()) };
    288         if block.is_null() {
    289             return None;
    290         }
    291 
    292         Some(Block::new(block, self.txn))
    293     }
    294 }
    295 
    296 /*
    297 impl<'a> IntoIterator for Blocks<'a> {
    298     type Item = Block<'a>;
    299     type IntoIter = BlockIter<'a>;
    300 
    301     fn into_iter(self) -> Self::IntoIter {
    302         match self.txn {
    303             Some(txn) => BlockIter::new_transactional(self.as_ptr(), txn),
    304             None => BlockIter::new_owned(self.as_ptr()),
    305         }
    306     }
    307 }
    308 */
    309 
    310 #[cfg(test)]
    311 mod tests {
    312     use super::*;
    313     use crate::test_util;
    314     use crate::{Config, Ndb};
    315 
    316     #[test]
    317     fn note_blocks_work() {
    318         let db = "target/testdbs/note_blocks";
    319         test_util::cleanup_db(&db);
    320 
    321         {
    322             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    323             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");
    324         }
    325 
    326         {
    327             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    328             let id =
    329                 hex::decode("d28ac02e277c3cf2744b562a414fd92d5fea554a737901364735bfe74577f304")
    330                     .expect("hex id");
    331             let pubkey =
    332                 hex::decode("32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")
    333                     .expect("jb55 pubkey");
    334             let txn = Transaction::new(&ndb).expect("txn");
    335             let id_bytes: [u8; 32] = id.try_into().expect("id bytes");
    336             let pubkey_bytes: [u8; 32] = pubkey.try_into().expect("pubkey bytes");
    337             let note = ndb.get_note_by_id(&txn, &id_bytes).unwrap();
    338             let blocks = ndb
    339                 .get_blocks_by_key(&txn, note.key().unwrap())
    340                 .expect("note");
    341             let mut c = 0;
    342             for block in blocks.iter(&note) {
    343                 match c {
    344                     0 => {
    345                         assert_eq!(block.blocktype(), BlockType::Hashtag);
    346                         assert_eq!(block.as_str(), "hashtags");
    347                     }
    348 
    349                     1 => {
    350                         assert_eq!(block.blocktype(), BlockType::Text);
    351                         assert_eq!(block.as_str(), ", are neat ");
    352                     }
    353 
    354                     2 => {
    355                         assert_eq!(block.blocktype(), BlockType::MentionBech32);
    356                         assert_eq!(block.as_str(), "nprofile1qqsr9cvzwc652r4m83d86ykplrnm9dg5gwdvzzn8ameanlvut35wy3gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduyu75sw");
    357                         match block.as_mention().unwrap() {
    358                             Mention::Profile(p) => assert_eq!(p.pubkey(), &pubkey_bytes),
    359                             _ => assert!(false),
    360                         };
    361                     }
    362 
    363                     3 => {
    364                         assert_eq!(block.blocktype(), BlockType::Text);
    365                         assert_eq!(block.as_str(), " ");
    366                     }
    367 
    368                     4 => {
    369                         assert_eq!(block.blocktype(), BlockType::Url);
    370                         assert_eq!(block.as_str(), "https://github.com/damus-io");
    371                     }
    372 
    373                     _ => assert!(false),
    374                 }
    375 
    376                 c += 1;
    377             }
    378         }
    379 
    380         test_util::cleanup_db(&db);
    381     }
    382 }