encode.rs (5849B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{ 3 string::{String, ToString}, 4 vec::Vec, 5 }; 6 7 use radroots_events::follow::{RadrootsFollow, RadrootsFollowProfile}; 8 9 use crate::error::EventEncodeError; 10 use crate::wire::WireEventParts; 11 use radroots_events::kinds::KIND_FOLLOW; 12 13 const DEFAULT_KIND: u32 = KIND_FOLLOW; 14 15 fn follow_tag(profile: &RadrootsFollowProfile) -> 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: &RadrootsFollow) -> 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: &RadrootsFollow) -> Result<WireEventParts, EventEncodeError> { 62 to_wire_parts_with_kind(follow, DEFAULT_KIND) 63 } 64 65 pub fn to_wire_parts_with_kind( 66 follow: &RadrootsFollow, 67 kind: u32, 68 ) -> Result<WireEventParts, EventEncodeError> { 69 let tags = follow_build_tags(follow)?; 70 Ok(WireEventParts { 71 kind, 72 content: String::new(), 73 tags, 74 }) 75 } 76 77 pub fn follow_apply( 78 follow: &RadrootsFollow, 79 mutation: FollowMutation, 80 ) -> Result<RadrootsFollow, EventEncodeError> { 81 let mut list = normalize_list(&follow.list)?; 82 83 match mutation { 84 FollowMutation::Follow { 85 public_key, 86 relay_url, 87 contact_name, 88 } => { 89 let public_key = normalize_public_key(&public_key)?; 90 let relay_url = normalize_optional(relay_url); 91 let contact_name = normalize_optional(contact_name); 92 apply_follow(&mut list, public_key, relay_url, contact_name); 93 } 94 FollowMutation::Unfollow { public_key } => { 95 let public_key = normalize_public_key(&public_key)?; 96 list.retain(|entry| entry.public_key != public_key); 97 } 98 FollowMutation::Toggle { 99 public_key, 100 relay_url, 101 contact_name, 102 } => { 103 let public_key = normalize_public_key(&public_key)?; 104 if list.iter().any(|entry| entry.public_key == public_key) { 105 list.retain(|entry| entry.public_key != public_key); 106 } else { 107 let relay_url = normalize_optional(relay_url); 108 let contact_name = normalize_optional(contact_name); 109 list.push(RadrootsFollowProfile { 110 published_at: 0, 111 public_key, 112 relay_url, 113 contact_name, 114 }); 115 } 116 } 117 } 118 119 Ok(RadrootsFollow { list }) 120 } 121 122 pub fn follow_to_wire_parts_after( 123 follow: &RadrootsFollow, 124 mutation: FollowMutation, 125 ) -> Result<WireEventParts, EventEncodeError> { 126 let updated = follow_apply(follow, mutation)?; 127 to_wire_parts(&updated) 128 } 129 130 fn normalize_public_key(value: &str) -> Result<String, EventEncodeError> { 131 let trimmed = value.trim(); 132 if trimmed.is_empty() { 133 return Err(EventEncodeError::EmptyRequiredField("follow.public_key")); 134 } 135 Ok(trimmed.to_string()) 136 } 137 138 fn normalize_optional(value: Option<String>) -> Option<String> { 139 value.and_then(|value| { 140 let trimmed = value.trim(); 141 if trimmed.is_empty() { 142 None 143 } else { 144 Some(trimmed.to_string()) 145 } 146 }) 147 } 148 149 fn normalize_list( 150 list: &[RadrootsFollowProfile], 151 ) -> Result<Vec<RadrootsFollowProfile>, EventEncodeError> { 152 let mut out = Vec::with_capacity(list.len()); 153 for entry in list { 154 let public_key = normalize_public_key(&entry.public_key)?; 155 if out 156 .iter() 157 .any(|item: &RadrootsFollowProfile| item.public_key == public_key) 158 { 159 continue; 160 } 161 let mut normalized = entry.clone(); 162 normalized.public_key = public_key; 163 normalized.relay_url = normalize_optional(normalized.relay_url); 164 normalized.contact_name = normalize_optional(normalized.contact_name); 165 out.push(normalized); 166 } 167 Ok(out) 168 } 169 170 fn apply_follow( 171 list: &mut Vec<RadrootsFollowProfile>, 172 public_key: String, 173 relay_url: Option<String>, 174 contact_name: Option<String>, 175 ) { 176 if let Some(pos) = list.iter().position(|entry| entry.public_key == public_key) { 177 let mut entry = list[pos].clone(); 178 if let Some(relay_url) = relay_url { 179 entry.relay_url = Some(relay_url); 180 } 181 if let Some(contact_name) = contact_name { 182 entry.contact_name = Some(contact_name); 183 } 184 list[pos] = entry; 185 } else { 186 list.push(RadrootsFollowProfile { 187 published_at: 0, 188 public_key, 189 relay_url, 190 contact_name, 191 }); 192 } 193 }