encode.rs (5752B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{ 3 string::{String, ToString}, 4 vec::Vec, 5 }; 6 7 use radroots_event::social::follow::{Follow, FollowProfile}; 8 9 use crate::error::EventEncodeError; 10 use radroots_event::envelope::kind::KIND_FOLLOW; 11 use radroots_event::wire::Nip01EventWireParts; 12 13 const DEFAULT_KIND: u32 = KIND_FOLLOW; 14 15 fn follow_tag(profile: &FollowProfile) -> Result<Vec<String>, EventEncodeError> { 16 if profile.public_key.trim().is_empty() { 17 return Err(EventEncodeError::EmptyRequiredField("follow.public_key")); 18 } 19 let relay = profile.relay_url.as_ref().filter(|v| !v.is_empty()); 20 let name = profile.contact_name.as_ref().filter(|v| !v.is_empty()); 21 let mut tag = 22 Vec::with_capacity(2 + usize::from(relay.is_some()) + usize::from(name.is_some())); 23 tag.push("p".to_string()); 24 tag.push(profile.public_key.clone()); 25 if let Some(relay) = relay { 26 tag.push(relay.clone()); 27 } 28 if let Some(name) = name { 29 tag.push(name.clone()); 30 } 31 Ok(tag) 32 } 33 34 pub fn follow_build_tags(follow: &Follow) -> Result<Vec<Vec<String>>, EventEncodeError> { 35 let mut tags = Vec::with_capacity(follow.list.len()); 36 for profile in &follow.list { 37 tags.push(follow_tag(profile)?); 38 } 39 Ok(tags) 40 } 41 42 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 43 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 44 #[derive(Clone, Debug, PartialEq, Eq)] 45 pub enum FollowMutation { 46 Follow { 47 public_key: String, 48 relay_url: Option<String>, 49 contact_name: Option<String>, 50 }, 51 Unfollow { 52 public_key: String, 53 }, 54 Toggle { 55 public_key: String, 56 relay_url: Option<String>, 57 contact_name: Option<String>, 58 }, 59 } 60 61 pub fn to_wire_parts(follow: &Follow) -> Result<Nip01EventWireParts, EventEncodeError> { 62 to_wire_parts_with_kind(follow, DEFAULT_KIND) 63 } 64 65 pub fn to_wire_parts_with_kind( 66 follow: &Follow, 67 kind: u32, 68 ) -> Result<Nip01EventWireParts, EventEncodeError> { 69 let tags = follow_build_tags(follow)?; 70 Ok(Nip01EventWireParts { 71 kind, 72 content: String::new(), 73 tags, 74 }) 75 } 76 77 pub fn follow_apply(follow: &Follow, mutation: FollowMutation) -> Result<Follow, EventEncodeError> { 78 let mut list = normalize_list(&follow.list)?; 79 80 match mutation { 81 FollowMutation::Follow { 82 public_key, 83 relay_url, 84 contact_name, 85 } => { 86 let public_key = normalize_public_key(&public_key)?; 87 let relay_url = normalize_optional(relay_url); 88 let contact_name = normalize_optional(contact_name); 89 apply_follow(&mut list, public_key, relay_url, contact_name); 90 } 91 FollowMutation::Unfollow { public_key } => { 92 let public_key = normalize_public_key(&public_key)?; 93 list.retain(|entry| entry.public_key != public_key); 94 } 95 FollowMutation::Toggle { 96 public_key, 97 relay_url, 98 contact_name, 99 } => { 100 let public_key = normalize_public_key(&public_key)?; 101 if list.iter().any(|entry| entry.public_key == public_key) { 102 list.retain(|entry| entry.public_key != public_key); 103 } else { 104 let relay_url = normalize_optional(relay_url); 105 let contact_name = normalize_optional(contact_name); 106 list.push(FollowProfile { 107 published_at: 0, 108 public_key, 109 relay_url, 110 contact_name, 111 }); 112 } 113 } 114 } 115 116 Ok(Follow { list }) 117 } 118 119 pub fn follow_to_wire_parts_after( 120 follow: &Follow, 121 mutation: FollowMutation, 122 ) -> Result<Nip01EventWireParts, EventEncodeError> { 123 let updated = follow_apply(follow, mutation)?; 124 to_wire_parts(&updated) 125 } 126 127 fn normalize_public_key(value: &str) -> Result<String, EventEncodeError> { 128 let trimmed = value.trim(); 129 if trimmed.is_empty() { 130 return Err(EventEncodeError::EmptyRequiredField("follow.public_key")); 131 } 132 Ok(trimmed.to_string()) 133 } 134 135 fn normalize_optional(value: Option<String>) -> Option<String> { 136 value.and_then(|value| { 137 let trimmed = value.trim(); 138 if trimmed.is_empty() { 139 None 140 } else { 141 Some(trimmed.to_string()) 142 } 143 }) 144 } 145 146 fn normalize_list(list: &[FollowProfile]) -> Result<Vec<FollowProfile>, EventEncodeError> { 147 let mut out = Vec::with_capacity(list.len()); 148 for entry in list { 149 let public_key = normalize_public_key(&entry.public_key)?; 150 if out 151 .iter() 152 .any(|item: &FollowProfile| item.public_key == public_key) 153 { 154 continue; 155 } 156 let mut normalized = entry.clone(); 157 normalized.public_key = public_key; 158 normalized.relay_url = normalize_optional(normalized.relay_url); 159 normalized.contact_name = normalize_optional(normalized.contact_name); 160 out.push(normalized); 161 } 162 Ok(out) 163 } 164 165 fn apply_follow( 166 list: &mut Vec<FollowProfile>, 167 public_key: String, 168 relay_url: Option<String>, 169 contact_name: Option<String>, 170 ) { 171 if let Some(pos) = list.iter().position(|entry| entry.public_key == public_key) { 172 let mut entry = list[pos].clone(); 173 if let Some(relay_url) = relay_url { 174 entry.relay_url = Some(relay_url); 175 } 176 if let Some(contact_name) = contact_name { 177 entry.contact_name = Some(contact_name); 178 } 179 list[pos] = entry; 180 } else { 181 list.push(FollowProfile { 182 published_at: 0, 183 public_key, 184 relay_url, 185 contact_name, 186 }); 187 } 188 }