encode.rs (3261B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{string::String, vec::Vec}; 3 4 use radroots_events::{ 5 kinds::{KIND_LIST_READ_WRITE_RELAYS, is_nip51_list_set_kind, is_nip51_standard_list_kind}, 6 list::{RadrootsList, RadrootsListEntry}, 7 tags::TAG_R, 8 }; 9 10 use crate::error::EventEncodeError; 11 use crate::wire::WireEventParts; 12 13 fn entry_tag(entry: &RadrootsListEntry) -> Result<Vec<String>, EventEncodeError> { 14 if entry.tag.trim().is_empty() { 15 return Err(EventEncodeError::EmptyRequiredField("entry.tag")); 16 } 17 let first = entry 18 .values 19 .first() 20 .ok_or(EventEncodeError::EmptyRequiredField("entry.values"))?; 21 if first.trim().is_empty() { 22 return Err(EventEncodeError::EmptyRequiredField("entry.values")); 23 } 24 let mut tag = Vec::with_capacity(1 + entry.values.len()); 25 tag.push(entry.tag.clone()); 26 tag.extend(entry.values.iter().cloned()); 27 Ok(tag) 28 } 29 30 pub fn list_entries_to_tags( 31 entries: &[RadrootsListEntry], 32 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 33 let mut tags = Vec::with_capacity(entries.len()); 34 for entry in entries { 35 tags.push(entry_tag(entry)?); 36 } 37 Ok(tags) 38 } 39 40 pub fn list_build_tags(list: &RadrootsList) -> Result<Vec<Vec<String>>, EventEncodeError> { 41 list_entries_to_tags(&list.entries) 42 } 43 44 pub fn to_wire_parts_with_kind( 45 list: &RadrootsList, 46 kind: u32, 47 ) -> Result<WireEventParts, EventEncodeError> { 48 if !is_supported_list_kind(kind) { 49 return Err(EventEncodeError::InvalidKind(kind)); 50 } 51 if kind == KIND_LIST_READ_WRITE_RELAYS { 52 validate_relay_entries(&list.entries)?; 53 } 54 let tags = list_build_tags(list)?; 55 Ok(WireEventParts { 56 kind, 57 content: list.content.clone(), 58 tags, 59 }) 60 } 61 62 fn is_supported_list_kind(kind: u32) -> bool { 63 is_nip51_standard_list_kind(kind) || is_nip51_list_set_kind(kind) 64 } 65 66 fn validate_relay_entries(entries: &[RadrootsListEntry]) -> Result<(), EventEncodeError> { 67 if entries.is_empty() { 68 return Err(EventEncodeError::EmptyRequiredField("relay.entries")); 69 } 70 for entry in entries { 71 if entry.tag != TAG_R { 72 return Err(EventEncodeError::InvalidField("relay.tag")); 73 } 74 let Some(url) = entry.values.first() else { 75 return Err(EventEncodeError::EmptyRequiredField("relay.url")); 76 }; 77 if !is_ws_relay_url(url) { 78 return Err(EventEncodeError::InvalidField("relay.url")); 79 } 80 if entry.values.len() > 2 { 81 return Err(EventEncodeError::InvalidField("relay.marker")); 82 } 83 if let Some(marker) = entry.values.get(1) 84 && marker != "read" 85 && marker != "write" 86 { 87 return Err(EventEncodeError::InvalidField("relay.marker")); 88 } 89 } 90 Ok(()) 91 } 92 93 fn is_ws_relay_url(value: &str) -> bool { 94 (value.starts_with("wss://") && value.len() > "wss://".len()) 95 || (value.starts_with("ws://") && value.len() > "ws://".len()) 96 } 97 98 #[cfg(feature = "serde_json")] 99 pub fn list_private_entries_json( 100 entries: &[RadrootsListEntry], 101 ) -> Result<String, EventEncodeError> { 102 let tags = list_entries_to_tags(entries)?; 103 serde_json::to_string(&tags).map_err(|_| EventEncodeError::Json) 104 }