lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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 }