lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

event_convert.rs (5380B)


      1 #![forbid(unsafe_code)]
      2 
      3 use crate::error::Error;
      4 use crate::types::RadrootsNostrEvent as RadrootsNostrRawEvent;
      5 use radroots_event::{
      6     envelope::EventEnvelope, envelope::EventEnvelopeError, envelope::EventEnvelopeParts,
      7     tag::EventPtr,
      8 };
      9 
     10 pub fn from_nostr(event: &RadrootsNostrRawEvent) -> Result<EventEnvelope, EventEnvelopeError> {
     11     EventEnvelope::new(EventEnvelopeParts {
     12         id: event.id.to_string(),
     13         author: event.pubkey.to_string(),
     14         created_at: event.created_at.as_secs(),
     15         kind: event.kind.as_u16() as u32,
     16         tags: event.tags.iter().map(|t| t.as_slice().to_vec()).collect(),
     17         content: event.content.clone(),
     18         sig: event.sig.to_string(),
     19     })
     20 }
     21 
     22 /// Converts a protocol-neutral Radroots envelope into the upstream Nostr value.
     23 ///
     24 /// This adapter performs no network access and does not imply that the event's
     25 /// identifier or signature has been verified.
     26 pub fn to_nostr(event: &EventEnvelope) -> Result<RadrootsNostrRawEvent, Error> {
     27     let kind = u16::try_from(event.kind_u32()).map_err(|_| Error::KindOutOfRange {
     28         kind: event.kind_u32(),
     29         max: u16::MAX,
     30     })?;
     31     let id = nostr::EventId::from_slice(event.id().as_bytes())
     32         .map_err(|_| Error::EventConversion { field: "id" })?;
     33     let public_key = nostr::PublicKey::from_slice(event.author().as_bytes())
     34         .map_err(|_| Error::EventConversion { field: "author" })?;
     35     let signature = nostr::secp256k1::schnorr::Signature::from_slice(event.sig().as_bytes())
     36         .map_err(|_| Error::EventConversion { field: "signature" })?;
     37     let tags = event
     38         .tags_as_vec()
     39         .into_iter()
     40         .map(nostr::Tag::parse)
     41         .collect::<Result<Vec<_>, _>>()
     42         .map_err(|_| Error::EventConversion { field: "tags" })?;
     43 
     44     Ok(nostr::Event::new(
     45         id,
     46         public_key,
     47         nostr::Timestamp::from_secs(event.created_at_u64()),
     48         nostr::Kind::Custom(kind),
     49         tags,
     50         event.content().to_owned(),
     51         signature,
     52     ))
     53 }
     54 
     55 pub fn pointer_from_nostr(event: &RadrootsNostrRawEvent) -> EventPtr {
     56     EventPtr {
     57         id: event.id.to_string(),
     58         relays: None,
     59     }
     60 }
     61 
     62 #[cfg(test)]
     63 mod tests {
     64     use super::*;
     65     use crate::events::build_event_unchecked;
     66     use crate::test_fixtures::FIXTURE_ALICE;
     67     use crate::types::{RadrootsNostrKeys, RadrootsNostrTimestamp};
     68     use nostr::SecretKey;
     69     use radroots_event::{
     70         envelope::EventEnvelopeParts,
     71         wire::{DEFAULT_CONTENT_MAX_BYTES, DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT},
     72     };
     73 
     74     fn fixture_keys() -> RadrootsNostrKeys {
     75         let secret_key = SecretKey::from_hex(FIXTURE_ALICE.secret_key_hex).expect("secret key");
     76         RadrootsNostrKeys::new(secret_key)
     77     }
     78 
     79     fn signed_event(content: String, tags: Vec<Vec<String>>) -> RadrootsNostrRawEvent {
     80         build_event_unchecked(1, content, tags)
     81             .expect("builder")
     82             .custom_created_at(RadrootsNostrTimestamp::from_secs(1_700_000_000))
     83             .sign_with_keys(&fixture_keys())
     84             .expect("signed event")
     85     }
     86 
     87     #[test]
     88     fn conversion_round_trips_protocol_neutral_event() {
     89         let original = signed_event(
     90             "harvest update".to_owned(),
     91             vec![vec!["t".to_owned(), "soil".to_owned()]],
     92         );
     93         let envelope = from_nostr(&original).expect("Radroots envelope");
     94 
     95         let converted = to_nostr(&envelope).expect("Nostr event");
     96 
     97         assert_eq!(converted, original);
     98     }
     99 
    100     #[test]
    101     fn conversion_rejects_kind_outside_nostr_range() {
    102         let original = signed_event(String::new(), Vec::new());
    103         let envelope = from_nostr(&original).expect("Radroots envelope");
    104         let kind = u32::from(u16::MAX) + 1;
    105         let widened = EventEnvelope::new(EventEnvelopeParts {
    106             id: envelope.id_hex(),
    107             author: envelope.author().to_hex(),
    108             created_at: envelope.created_at_u64(),
    109             kind,
    110             tags: envelope.tags_as_vec(),
    111             content: envelope.content().to_owned(),
    112             sig: envelope.signature_hex(),
    113         })
    114         .expect("wider Radroots kind");
    115 
    116         assert!(matches!(
    117             to_nostr(&widened),
    118             Err(Error::KindOutOfRange {
    119                 kind: actual,
    120                 max: u16::MAX,
    121             }) if actual == kind
    122         ));
    123     }
    124 
    125     #[test]
    126     fn conversion_returns_typed_content_limit_error() {
    127         let content = "x".repeat(DEFAULT_CONTENT_MAX_BYTES + 1);
    128         let event = signed_event(content, Vec::new());
    129 
    130         assert_eq!(
    131             from_nostr(&event),
    132             Err(EventEnvelopeError::ContentTooLarge {
    133                 max: DEFAULT_CONTENT_MAX_BYTES,
    134                 actual: DEFAULT_CONTENT_MAX_BYTES + 1,
    135             })
    136         );
    137     }
    138 
    139     #[test]
    140     fn conversion_returns_typed_total_tag_element_limit_error() {
    141         let mut tag = Vec::with_capacity(DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT + 1);
    142         tag.push("x".to_owned());
    143         tag.resize(DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT + 1, String::new());
    144         let event = signed_event(String::new(), vec![tag]);
    145 
    146         assert_eq!(
    147             from_nostr(&event),
    148             Err(EventEnvelopeError::TooManyTagElements {
    149                 max: DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT,
    150                 actual: DEFAULT_TAG_TOTAL_ELEMENT_MAX_COUNT + 1,
    151             })
    152         );
    153     }
    154 }