lib

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

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 }