nostrdb-rs

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

nip10.rs (21838B)


      1 use crate::{Error, Tag, Tags};
      2 
      3 #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
      4 pub enum Marker {
      5     Reply,
      6     Root,
      7     Mention,
      8 }
      9 
     10 /// Parsed `e` tags
     11 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     12 pub struct NoteIdRef<'a> {
     13     pub index: u16,
     14     pub id: &'a [u8; 32],
     15     pub relay: Option<&'a str>,
     16     pub marker: Option<Marker>,
     17 }
     18 
     19 impl NoteIdRef<'_> {
     20     pub fn to_owned(&self) -> NoteIdRefBuf {
     21         NoteIdRefBuf {
     22             index: self.index,
     23             marker: self.marker,
     24         }
     25     }
     26 }
     27 
     28 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     29 pub struct NoteIdRefBuf {
     30     pub index: u16,
     31     pub marker: Option<Marker>,
     32 }
     33 
     34 fn tag_to_note_id_ref(tag: Tag<'_>, marker: Option<Marker>, index: i32) -> NoteIdRef<'_> {
     35     let id = tag
     36         .get_unchecked(1)
     37         .variant()
     38         .id()
     39         .expect("expected id at index, do you have the correct note?");
     40     let relay = tag.get(2).and_then(|t| t.variant().str());
     41     NoteIdRef {
     42         index: index as u16,
     43         id,
     44         relay,
     45         marker,
     46     }
     47 }
     48 
     49 impl NoteReplyBuf {
     50     // TODO(jb55): optimize this function. It is not the nicest code.
     51     // We could simplify the index lookup by offsets into the Note's
     52     // string table
     53     pub fn borrow<'a>(&self, tags: Tags<'a>) -> NoteReply<'a> {
     54         let mut root: Option<NoteIdRef<'a>> = None;
     55         let mut reply: Option<NoteIdRef<'a>> = None;
     56         let mut mention: Option<NoteIdRef<'a>> = None;
     57 
     58         let mut index: i32 = -1;
     59         for tag in tags {
     60             index += 1;
     61             if tag.count() < 2 && tag.get_unchecked(0).variant().str() != Some("e") {
     62                 continue;
     63             }
     64 
     65             if self.root.as_ref().is_some_and(|x| x.index == index as u16) {
     66                 root = Some(tag_to_note_id_ref(
     67                     tag,
     68                     self.root.as_ref().unwrap().marker,
     69                     index,
     70                 ))
     71             } else if self.reply.as_ref().is_some_and(|x| x.index == index as u16) {
     72                 reply = Some(tag_to_note_id_ref(
     73                     tag,
     74                     self.reply.as_ref().unwrap().marker,
     75                     index,
     76                 ))
     77             } else if self
     78                 .mention
     79                 .as_ref()
     80                 .is_some_and(|x| x.index == index as u16)
     81             {
     82                 mention = Some(tag_to_note_id_ref(
     83                     tag,
     84                     self.mention.as_ref().unwrap().marker,
     85                     index,
     86                 ))
     87             }
     88         }
     89 
     90         NoteReply {
     91             root,
     92             reply,
     93             mention,
     94         }
     95     }
     96 }
     97 
     98 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     99 pub struct NoteReply<'a> {
    100     root: Option<NoteIdRef<'a>>,
    101     reply: Option<NoteIdRef<'a>>,
    102     mention: Option<NoteIdRef<'a>>,
    103 }
    104 
    105 /// Owned version of NoteReply, stores tag indices
    106 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    107 pub struct NoteReplyBuf {
    108     pub root: Option<NoteIdRefBuf>,
    109     pub reply: Option<NoteIdRefBuf>,
    110     pub mention: Option<NoteIdRefBuf>,
    111 }
    112 
    113 impl<'a> NoteReply<'a> {
    114     pub fn reply_to_root(self) -> Option<NoteIdRef<'a>> {
    115         if self.is_reply_to_root() {
    116             self.root
    117         } else {
    118             None
    119         }
    120     }
    121 
    122     pub fn to_owned(&self) -> NoteReplyBuf {
    123         NoteReplyBuf {
    124             root: self.root.map(|x| x.to_owned()),
    125             reply: self.reply.map(|x| x.to_owned()),
    126             mention: self.mention.map(|x| x.to_owned()),
    127         }
    128     }
    129 
    130     pub fn new(tags: Tags<'a>) -> NoteReply<'a> {
    131         tags_to_note_reply(tags)
    132     }
    133 
    134     pub fn is_reply_to_root(&self) -> bool {
    135         match (&self.root, &self.reply) {
    136             (Some(_root), None) => true,
    137             (Some(root), Some(reply)) if root.id == reply.id => true,
    138             _ => false,
    139         }
    140     }
    141 
    142     pub fn root(self) -> Option<NoteIdRef<'a>> {
    143         self.root
    144     }
    145 
    146     pub fn is_reply(&self) -> bool {
    147         self.reply().is_some()
    148     }
    149 
    150     pub fn reply(self) -> Option<NoteIdRef<'a>> {
    151         if self.reply.is_some() {
    152             self.reply
    153         } else if self.root.is_some() {
    154             self.root
    155         } else {
    156             None
    157         }
    158     }
    159 
    160     pub fn mention(self) -> Option<NoteIdRef<'a>> {
    161         self.mention
    162     }
    163 }
    164 
    165 impl Marker {
    166     pub fn new(s: &str) -> Option<Self> {
    167         if s == "reply" {
    168             Some(Marker::Reply)
    169         } else if s == "root" {
    170             Some(Marker::Root)
    171         } else if s == "mention" {
    172             Some(Marker::Mention)
    173         } else {
    174             None
    175         }
    176     }
    177 }
    178 
    179 fn tags_to_note_reply<'a>(tags: Tags<'a>) -> NoteReply<'a> {
    180     let mut root: Option<NoteIdRef<'a>> = None;
    181     let mut reply: Option<NoteIdRef<'a>> = None;
    182     let mut mention: Option<NoteIdRef<'a>> = None;
    183     let mut first: bool = true;
    184     let mut index: i32 = -1;
    185     let mut any_marker: bool = false;
    186 
    187     for tag in tags {
    188         index += 1;
    189 
    190         if root.is_some() && reply.is_some() && mention.is_some() {
    191             break;
    192         }
    193 
    194         let Ok(note_ref) = tag_to_noteid_ref(tag, index as u16) else {
    195             continue;
    196         };
    197 
    198         if let Some(marker) = note_ref.marker {
    199             any_marker = true;
    200             match marker {
    201                 Marker::Root => root = Some(note_ref),
    202                 Marker::Reply => reply = Some(note_ref),
    203                 Marker::Mention => mention = Some(note_ref),
    204             }
    205         } else if !any_marker && first {
    206             root = Some(note_ref);
    207             first = false;
    208         } else if !any_marker && reply.is_none() {
    209             reply = Some(note_ref)
    210         }
    211     }
    212 
    213     NoteReply {
    214         root,
    215         reply,
    216         mention,
    217     }
    218 }
    219 
    220 pub fn tag_to_noteid_ref(tag: Tag<'_>, index: u16) -> Result<NoteIdRef<'_>, Error> {
    221     if tag.count() < 2 {
    222         return Err(Error::DecodeError);
    223     }
    224 
    225     if tag.get_str(0) != Some("e") {
    226         return Err(Error::DecodeError);
    227     }
    228 
    229     let id = tag.get_id(1).ok_or(Error::DecodeError)?;
    230     let relay = tag.get_str(2).filter(|x| !x.is_empty());
    231     let marker = tag.get_str(3).and_then(Marker::new);
    232 
    233     Ok(NoteIdRef {
    234         index,
    235         id,
    236         relay,
    237         marker,
    238     })
    239 }
    240 
    241 #[cfg(test)]
    242 mod test {
    243     use crate::*;
    244 
    245     #[tokio::test]
    246     async fn nip10_marker() {
    247         let db = "target/testdbs/nip10_marker";
    248         test_util::cleanup_db(&db);
    249 
    250         {
    251             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    252             let filter = Filter::new().kinds(vec![1]).build();
    253             let root_id: [u8; 32] =
    254                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d4")
    255                     .unwrap()
    256                     .try_into()
    257                     .unwrap();
    258             let reply_id: [u8; 32] =
    259                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3")
    260                     .unwrap()
    261                     .try_into()
    262                     .unwrap();
    263             let sub = ndb.subscribe(&[filter.clone()]).expect("sub_id");
    264             let waiter = ndb.wait_for_notes(sub, 1);
    265 
    266             ndb.process_event(r#"
    267             [
    268               "EVENT",
    269               "huh",
    270               {
    271                 "id": "19377cb4b9b807561830ab6d4c1fae7b9c9f1b623c15d10590cacc859cf19d76",
    272                 "pubkey": "4871687b7b0aee3f1649c866e61724d79d51e673936a5378f5ed90bf7580791f",
    273                 "created_at": 1714170678,
    274                 "kind": 1,
    275                 "tags": [
    276                   ["e", "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3", "", "reply" ],
    277                   ["e", "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d4", "wss://relay.damus.io", "root" ]
    278                 ],
    279                 "content": "hi",
    280                 "sig": "53921b1572c2e4373180a9f71513a0dee286cba6193d983052f96285c08f0e0158773d82ac97991ba8d390f6f54f84d5272c2e945f2e854a750f9cf038c0f759"
    281               }
    282             ]"#).expect("process ok");
    283 
    284             let res = waiter.await.expect("await ok");
    285             assert_eq!(res, vec![NoteKey::new(1)]);
    286             let txn = Transaction::new(&ndb).unwrap();
    287             let res = ndb.query(&txn, &[filter], 1).expect("note");
    288             let note_reply = NoteReply::new(res[0].note.tags());
    289 
    290             assert_eq!(*note_reply.root.unwrap().id, root_id);
    291             assert_eq!(*note_reply.reply.unwrap().id, reply_id);
    292             assert_eq!(
    293                 note_reply.root.unwrap().relay.unwrap(),
    294                 "wss://relay.damus.io"
    295             );
    296         }
    297     }
    298 
    299     #[tokio::test]
    300     async fn nip10_deprecated() {
    301         let db = "target/testdbs/nip10_deprecated_reply";
    302         test_util::cleanup_db(&db);
    303 
    304         {
    305             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    306             let filter = Filter::new().kinds(vec![1]).build();
    307             let root_id: [u8; 32] =
    308                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d4")
    309                     .unwrap()
    310                     .try_into()
    311                     .unwrap();
    312             let reply_id: [u8; 32] =
    313                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3")
    314                     .unwrap()
    315                     .try_into()
    316                     .unwrap();
    317             let sub = ndb.subscribe(&[filter.clone()]).expect("sub_id");
    318             let waiter = ndb.wait_for_notes(sub, 1);
    319 
    320             ndb.process_event(r#"
    321             [
    322               "EVENT",
    323               "huh",
    324               {
    325                 "id": "ebac7df823ab975b6d2696505cf22a959067b74b1761c5581156f2a884036997",
    326                 "pubkey": "118758f9a951c923b8502cfb8b2f329bee2a46356b6fc4f65c1b9b4730e0e9e5",
    327                 "created_at": 1714175831,
    328                 "kind": 1,
    329                 "tags": [
    330                   [
    331                     "e",
    332                     "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d4"
    333                   ],
    334                   [
    335                     "e",
    336                     "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3"
    337                   ]
    338                 ],
    339                 "content": "hi",
    340                 "sig": "05913c7b19a70188d4dec5ac53d5da39fea4d5030c28176e52abb211e1bde60c5947aca8af359a00c8df8d96127b2f945af31f21fe01392b661bae12e7d14b1d"
    341               }
    342             ]"#).expect("process ok");
    343 
    344             let res = waiter.await.expect("await ok");
    345             assert_eq!(res, vec![NoteKey::new(1)]);
    346             let txn = Transaction::new(&ndb).unwrap();
    347             let res = ndb.query(&txn, &[filter], 1).expect("note");
    348             let note_reply = NoteReply::new(res[0].note.tags());
    349 
    350             assert_eq!(*note_reply.root.unwrap().id, root_id);
    351             assert_eq!(*note_reply.reply.unwrap().id, reply_id);
    352             assert_eq!(note_reply.reply_to_root().is_none(), true);
    353             assert_eq!(*note_reply.reply().unwrap().id, reply_id);
    354         }
    355     }
    356 
    357     #[tokio::test]
    358     async fn nip10_mention() {
    359         let db = "target/testdbs/nip10_mention";
    360         test_util::cleanup_db(&db);
    361 
    362         {
    363             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    364             let filter = Filter::new().kinds([1]).build();
    365             let root_id: [u8; 32] =
    366                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d4")
    367                     .unwrap()
    368                     .try_into()
    369                     .unwrap();
    370             let mention_id: [u8; 32] =
    371                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3")
    372                     .unwrap()
    373                     .try_into()
    374                     .unwrap();
    375             let sub = ndb.subscribe(&[filter.clone()]).expect("sub_id");
    376             let waiter = ndb.wait_for_notes(sub, 1);
    377 
    378             ndb.process_event(r#"
    379             [
    380               "EVENT",
    381               "huh",
    382               {
    383                 "id": "9521de81704269f9f61c042355eaa97a845a90c0ce6637b290800fa5a3c0b48d",
    384                 "pubkey": "b3aceb5b36a235377c80dc2a1b3594a1d49e394b4d74fa11bc7cb4cf0bf677b2",
    385                 "created_at": 1714177990,
    386                 "kind": 1,
    387                 "tags": [
    388                   [
    389                     "e",
    390                     "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3",
    391                     "",
    392                     "mention"
    393                   ],
    394                   [
    395                     "e",
    396                     "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d4",
    397                     "wss://relay.damus.io",
    398                     "root"
    399                   ]
    400                 ],
    401                 "content": "hi",
    402                 "sig": "e908ec395f6ea907a4b562b3ebf1bf61653566a5648574a1f8c752285797e5870e57416a0be933ce580fc3d65c874909c9dacbd1575c15bd97b8a68ea2b5160b"
    403               }
    404             ]"#).expect("process ok");
    405 
    406             let res = waiter.await.expect("await ok");
    407             assert_eq!(res, vec![NoteKey::new(1)]);
    408             let txn = Transaction::new(&ndb).unwrap();
    409             let res = ndb.query(&txn, &[filter], 1).expect("note");
    410             let note_reply = NoteReply::new(res[0].note.tags());
    411 
    412             assert_eq!(*note_reply.reply_to_root().unwrap().id, root_id);
    413             assert_eq!(*note_reply.reply().unwrap().id, root_id);
    414             assert_eq!(*note_reply.mention().unwrap().id, mention_id);
    415             assert_eq!(note_reply.is_reply_to_root(), true);
    416             assert_eq!(note_reply.is_reply(), true);
    417         }
    418     }
    419 
    420     #[tokio::test]
    421     async fn nip10_marker_mixed() {
    422         let db = "target/testdbs/nip10_marker_mixed";
    423         test_util::cleanup_db(&db);
    424 
    425         {
    426             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    427             let filter = Filter::new().kinds([1]).build();
    428             let root_id: [u8; 32] =
    429                 hex::decode("27e71cf53299dafb5dc7bcc0a078357418a4375cb1097bf5184662493f79a627")
    430                     .unwrap()
    431                     .try_into()
    432                     .unwrap();
    433             let reply_id: [u8; 32] =
    434                 hex::decode("1a616998552cf76e9786f76ac68f6104cdae46377330735c68bfe0b9426d2fa8")
    435                     .unwrap()
    436                     .try_into()
    437                     .unwrap();
    438             let sub = ndb.subscribe(&[filter.clone()]).expect("sub_id");
    439             let waiter = ndb.wait_for_notes(sub, 1);
    440 
    441             ndb.process_event(r#"
    442             [
    443               "EVENT",
    444               "nostril-query",
    445               {
    446                 "content": "Go to pleblab plz",
    447                 "created_at": 1714157088,
    448                 "id": "19ae8cd276185f6f48fd7e25736c260ea0ac25d9b591ec3194631e3196e19622",
    449                 "kind": 1,
    450                 "pubkey": "ae1008d23930b776c18092f6eab41e4b09fcf3f03f3641b1b4e6ee3aa166d760",
    451                 "sig": "fdafc7192a0f3b5fef5ae794ef61eb2b3c7cc70bace53f3aa6d4263347581d36add7e9468a4e329d9c986e3a5c46e4689a6b79f60c5cf7778a403316ac5b2629",
    452                 "tags": [
    453                   [
    454                     "e",
    455                     "27e71cf53299dafb5dc7bcc0a078357418a4375cb1097bf5184662493f79a627",
    456                     "",
    457                     "root"
    458                   ],
    459                   [
    460                     "e",
    461                     "f99046bd87be7508d55e139de48517c06ef90830d77a5d3213df858d77bb2f8f"
    462                   ],
    463                   [
    464                     "e",
    465                     "1a616998552cf76e9786f76ac68f6104cdae46377330735c68bfe0b9426d2fa8",
    466                     "",
    467                     "reply"
    468                   ],
    469                   [
    470                     "p",
    471                     "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
    472                   ],
    473                   [
    474                     "p",
    475                     "8ea485266b2285463b13bf835907161c22bb3da1e652b443db14f9cee6720a43"
    476                   ],
    477                   [
    478                     "p",
    479                     "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
    480                   ]
    481                 ]
    482               }
    483             ]
    484             "#).expect("process ok");
    485 
    486             let res = waiter.await.expect("await ok");
    487             assert_eq!(res, vec![NoteKey::new(1)]);
    488             let txn = Transaction::new(&ndb).unwrap();
    489             let res = ndb.query(&txn, &[filter], 1).expect("note");
    490             let note = &res[0].note;
    491             let note_reply = NoteReply::new(note.tags());
    492 
    493             assert_eq!(note_reply.reply_to_root().is_none(), true);
    494             assert_eq!(*note_reply.reply().unwrap().id, reply_id);
    495             assert_eq!(*note_reply.root().unwrap().id, root_id);
    496             assert_eq!(note_reply.mention().is_none(), true);
    497 
    498             // test the to_owned version
    499             let back_again = note_reply.to_owned().borrow(note.tags());
    500             assert_eq!(back_again.reply_to_root().is_none(), true);
    501             assert_eq!(*back_again.reply().unwrap().id, reply_id);
    502             assert_eq!(*back_again.root().unwrap().id, root_id);
    503             assert_eq!(back_again.mention().is_none(), true);
    504         }
    505     }
    506 
    507     #[tokio::test]
    508     async fn nip10_reply_to_root_with_reply_tag() {
    509         let db = "target/testdbs/nip10_reply_to_root_with_reply_tag";
    510         test_util::cleanup_db(&db);
    511 
    512         let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    513         let filter = Filter::new().kinds(vec![1]).build();
    514         let root_id: [u8; 32] =
    515             hex::decode("343ff2fe97e352c7012a44dc85135dccef43acb73e459e71f7284c9627b57ab0")
    516                 .unwrap()
    517                 .try_into()
    518                 .unwrap();
    519         let sub = ndb.subscribe(&[filter.clone()]).expect("sub_id");
    520         let waiter = ndb.wait_for_notes(sub, 1);
    521 
    522         ndb.process_event(r#"
    523             ["EVENT", "huh", {
    524               "id": "22c4986d970bb13a9337bdad4e462bc75c5105375669d87caeab0951e76af800",
    525               "pubkey": "592295cf2b09a7f9555f43adb734cbee8a84ee892ed3f9336e6a09b6413a0db9",
    526               "created_at": 1753380428,
    527               "kind": 1,
    528               "tags": [
    529                 [
    530                   "e",
    531                   "343ff2fe97e352c7012a44dc85135dccef43acb73e459e71f7284c9627b57ab0",
    532                   "ws://relay.jb55.com/",
    533                   "root",
    534                   "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
    535                 ],
    536                 [
    537                   "e",
    538                   "343ff2fe97e352c7012a44dc85135dccef43acb73e459e71f7284c9627b57ab0",
    539                   "ws://relay.jb55.com/",
    540                   "reply",
    541                   "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
    542                 ],
    543                 [
    544                   "p",
    545                   "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
    546                   "ws://relay.jb55.com/"
    547                 ]
    548               ],
    549               "content": "So the user part is like nip-C0",
    550               "sig": "d2224177462f3cadfba2ab946005deb3f7485232a9aed78e5304a9f96a1170b45d48c925686293a0272c661db287e201924d49f1216d402fd1f34aa57da70b60"
    551             }]
    552             "#).expect("process ok");
    553 
    554         let res = waiter.await.expect("await ok");
    555         assert_eq!(res, vec![NoteKey::new(1)]);
    556         let txn = Transaction::new(&ndb).unwrap();
    557         let res = ndb.query(&txn, &[filter], 1).expect("note");
    558         let note = &res[0].note;
    559         let note_reply = NoteReply::new(note.tags());
    560 
    561         assert_eq!(note_reply.reply.is_some_and(|r| r.id == &root_id), true);
    562         assert_eq!(note_reply.is_reply_to_root(), true);
    563     }
    564 
    565     #[tokio::test]
    566     async fn nip10_deprecated_reply_to_root() {
    567         let db = "target/testdbs/nip10_deprecated_reply_to_root";
    568         test_util::cleanup_db(&db);
    569 
    570         {
    571             let ndb = Ndb::new(db, &Config::new()).expect("ndb");
    572             let filter = Filter::new().kinds(vec![1]).build();
    573             let root_id: [u8; 32] =
    574                 hex::decode("7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3")
    575                     .unwrap()
    576                     .try_into()
    577                     .unwrap();
    578             let sub = ndb.subscribe(&[filter.clone()]).expect("sub_id");
    579             let waiter = ndb.wait_for_notes(sub, 1);
    580 
    581             ndb.process_event(r#"
    582             [
    583               "EVENT",
    584               "huh",
    585               {
    586                 "id": "140280b7886c48bddd99684b951c6bb61bebc8270a4989f316282c72aa35e5ba",
    587                 "pubkey": "5ee7067e7155a9abf494e3e47e3249254cf95389a0c6e4f75cbbf35c8c675c23",
    588                 "created_at": 1714178274,
    589                 "kind": 1,
    590                 "tags": [
    591                   [
    592                     "e",
    593                     "7d33c272a74e75c7328b891ab69420dd820cc7544fc65cd29a058c3495fd27d3"
    594                   ]
    595                 ],
    596                 "content": "hi",
    597                 "sig": "e433d468d49fbc0f466b1a8ccefda71b0e17af471e579b56b8ce36477c116109c44d1065103ed6c01f838af92a13e51969d3b458f69c09b6f12785bd07053eb5"
    598               }
    599             ]"#).expect("process ok");
    600 
    601             let res = waiter.await.expect("await ok");
    602             assert_eq!(res, vec![NoteKey::new(1)]);
    603             let txn = Transaction::new(&ndb).unwrap();
    604             let res = ndb.query(&txn, &[filter], 1).expect("note");
    605             let note = &res[0].note;
    606             let note_reply = NoteReply::new(note.tags());
    607 
    608             assert_eq!(*note_reply.reply_to_root().unwrap().id, root_id);
    609             assert_eq!(*note_reply.reply().unwrap().id, root_id);
    610             assert_eq!(note_reply.mention().is_none(), true);
    611 
    612             // test the to_owned version
    613             let back_again = note_reply.to_owned().borrow(note.tags());
    614             assert_eq!(*back_again.reply_to_root().unwrap().id, root_id);
    615             assert_eq!(*back_again.reply().unwrap().id, root_id);
    616             assert_eq!(back_again.mention().is_none(), true);
    617         }
    618     }
    619 }