mod.rs (4082B)
1 //! Typed Nostr event authoring profiles used by the Radroots adapter. 2 //! 3 //! Focused builders seal reserved kinds and canonical tag construction before 4 //! signer access. Live publication, persistence, and retry policy remain 5 //! outside this module. 6 7 #[cfg(feature = "events")] 8 pub mod application_handler; 9 #[cfg(feature = "events")] 10 pub mod calendar; 11 #[cfg(feature = "events")] 12 pub mod comment; 13 #[cfg(feature = "events")] 14 pub mod deletion; 15 #[cfg(feature = "events")] 16 pub mod food_availability; 17 pub mod jobs; 18 pub mod metadata; 19 pub mod post; 20 #[cfg(feature = "events")] 21 pub mod reply; 22 #[cfg(feature = "events")] 23 mod sealed; 24 25 extern crate alloc; 26 #[cfg(any(feature = "events", test))] 27 use alloc::{string::String, vec::Vec}; 28 29 #[cfg(any(feature = "events", test))] 30 use crate::error::Error; 31 #[cfg(any(feature = "events", test))] 32 use crate::types::{ 33 RadrootsNostrEventBuilderUnchecked, RadrootsNostrKind, RadrootsNostrTag, RadrootsNostrTagKind, 34 }; 35 36 #[cfg(any(feature = "events", test))] 37 pub(crate) fn build_event_unchecked( 38 kind_u32: u32, 39 content: impl Into<String>, 40 tag_slices: Vec<Vec<String>>, 41 ) -> Result<RadrootsNostrEventBuilderUnchecked, Error> { 42 let kind = u16::try_from(kind_u32).map_err(|_| Error::KindOutOfRange { 43 kind: kind_u32, 44 max: u16::MAX, 45 })?; 46 let mut tags: Vec<RadrootsNostrTag> = Vec::new(); 47 for mut s in tag_slices { 48 if s.is_empty() { 49 continue; 50 } 51 let key = s.remove(0); 52 let values = s; 53 tags.push(RadrootsNostrTag::custom( 54 RadrootsNostrTagKind::Custom(key.into()), 55 values, 56 )); 57 } 58 let builder = 59 RadrootsNostrEventBuilderUnchecked::new(RadrootsNostrKind::Custom(kind), content.into()) 60 .tags(tags) 61 .allow_self_tagging(); 62 Ok(builder) 63 } 64 65 #[cfg(test)] 66 mod tests { 67 use alloc::{vec, vec::Vec}; 68 69 use super::build_event_unchecked; 70 use crate::error::Error; 71 use crate::test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX; 72 use crate::types::{RadrootsNostrPublicKey, RadrootsNostrTagKind}; 73 74 #[test] 75 fn build_event_preserves_self_p_tag() { 76 let pubkey_hex = FIXTURE_ALICE_PUBLIC_KEY_HEX; 77 let pubkey = RadrootsNostrPublicKey::from_hex(pubkey_hex).expect("pubkey"); 78 let tags = vec![ 79 vec!["x".to_string(), "v".to_string()], 80 vec!["p".to_string(), pubkey_hex.to_string()], 81 ]; 82 83 let builder = build_event_unchecked(1, "test", tags).expect("builder"); 84 let event = builder.build(pubkey); 85 86 let has_self_tag = event.tags.iter().any(|tag| { 87 tag.kind() == RadrootsNostrTagKind::p() && tag.content() == Some(pubkey_hex) 88 }); 89 assert!(has_self_tag); 90 let has_other_self_tag = event.tags.iter().any(|tag| { 91 tag.kind() == RadrootsNostrTagKind::p() 92 && tag.content() 93 == Some("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") 94 }); 95 assert!(!has_other_self_tag); 96 } 97 98 #[test] 99 fn build_event_accepts_maximum_nip01_kind() { 100 let builder = build_event_unchecked(u32::from(u16::MAX), "test", Vec::new()) 101 .expect("maximum NIP-01 kind"); 102 let event = builder 103 .build(RadrootsNostrPublicKey::from_hex(FIXTURE_ALICE_PUBLIC_KEY_HEX).expect("pubkey")); 104 assert_eq!(event.kind.as_u16(), u16::MAX); 105 } 106 107 #[test] 108 fn build_event_rejects_kind_overflow() { 109 let kind = u32::from(u16::MAX) + 1; 110 assert!(matches!( 111 build_event_unchecked(kind, "test", Vec::new()), 112 Err(Error::KindOutOfRange { 113 kind: actual, 114 max: u16::MAX 115 }) if actual == kind 116 )); 117 } 118 119 #[test] 120 fn build_event_ignores_empty_tag_slices() { 121 let builder = build_event_unchecked(1, "test", vec![Vec::new()]).expect("builder"); 122 let event = builder 123 .build(RadrootsNostrPublicKey::from_hex(FIXTURE_ALICE_PUBLIC_KEY_HEX).expect("pubkey")); 124 assert!(event.tags.is_empty()); 125 } 126 }