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 }