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