lib

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

encode.rs (4175B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     format,
      4     string::{String, ToString},
      5     vec,
      6     vec::Vec,
      7 };
      8 
      9 use radroots_event::{
     10     envelope::kind::KIND_REACTION, post::reaction::Reaction, social::SocialTarget,
     11 };
     12 
     13 use crate::error::EventEncodeError;
     14 use crate::field_helpers::{
     15     parse_address_tag, validate_lowercase_hex_64, validate_non_empty_field,
     16 };
     17 use radroots_event::wire::Nip01EventWireParts;
     18 
     19 const DEFAULT_KIND: u32 = KIND_REACTION;
     20 
     21 pub fn reaction_build_tags(reaction: &Reaction) -> Result<Vec<Vec<String>>, EventEncodeError> {
     22     let mut tags = Vec::with_capacity(4);
     23     push_reaction_target(&mut tags, &reaction.target)?;
     24     Ok(tags)
     25 }
     26 
     27 pub fn to_wire_parts(reaction: &Reaction) -> Result<Nip01EventWireParts, EventEncodeError> {
     28     to_wire_parts_with_kind(reaction, DEFAULT_KIND)
     29 }
     30 
     31 pub fn to_wire_parts_with_kind(
     32     reaction: &Reaction,
     33     kind: u32,
     34 ) -> Result<Nip01EventWireParts, EventEncodeError> {
     35     if kind != DEFAULT_KIND {
     36         return Err(EventEncodeError::InvalidKind(kind));
     37     }
     38     let tags = reaction_build_tags(reaction)?;
     39     Ok(Nip01EventWireParts {
     40         kind,
     41         content: reaction.content.clone(),
     42         tags,
     43     })
     44 }
     45 
     46 fn push_reaction_target(
     47     tags: &mut Vec<Vec<String>>,
     48     target: &SocialTarget,
     49 ) -> Result<(), EventEncodeError> {
     50     match target {
     51         SocialTarget::Event {
     52             id,
     53             author,
     54             event_kind,
     55             relays,
     56         } => {
     57             validate_lowercase_hex_64(id, "target.id")?;
     58             let mut event_tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len));
     59             event_tag.push("e".to_string());
     60             event_tag.push(id.clone());
     61             if let Some(relays) = relays {
     62                 event_tag.extend(relays.iter().cloned());
     63             }
     64             tags.push(event_tag);
     65             if let Some(author) = author.as_deref() {
     66                 validate_non_empty_field(author, "target.author")?;
     67                 tags.push(vec!["p".to_string(), author.to_string()]);
     68             }
     69             if let Some(kind) = event_kind {
     70                 tags.push(vec!["k".to_string(), kind.to_string()]);
     71             }
     72         }
     73         SocialTarget::Address {
     74             address,
     75             author,
     76             event_kind,
     77             relays,
     78         } => {
     79             let parsed = parse_address_tag(address, "target.address")
     80                 .map_err(|_| EventEncodeError::InvalidField("target.address"))?;
     81             if let Some(kind) = event_kind
     82                 && *kind != parsed.kind
     83             {
     84                 return Err(EventEncodeError::InvalidField("target.kind"));
     85             }
     86             if let Some(author) = author.as_deref()
     87                 && author != parsed.pubkey
     88             {
     89                 return Err(EventEncodeError::InvalidField("target.author"));
     90             }
     91             let mut address_tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len));
     92             address_tag.push("a".to_string());
     93             address_tag.push(format!(
     94                 "{}:{}:{}",
     95                 parsed.kind, parsed.pubkey, parsed.d_tag
     96             ));
     97             if let Some(relays) = relays {
     98                 address_tag.extend(relays.iter().cloned());
     99             }
    100             tags.push(address_tag);
    101             tags.push(vec!["p".to_string(), parsed.pubkey]);
    102             tags.push(vec!["k".to_string(), parsed.kind.to_string()]);
    103         }
    104         SocialTarget::External { .. } => {
    105             return Err(EventEncodeError::InvalidField("target"));
    106         }
    107     }
    108     Ok(())
    109 }
    110 
    111 #[cfg(test)]
    112 mod tests {
    113     use super::*;
    114 
    115     #[test]
    116     fn reaction_event_target_encodes_without_relays() {
    117         let reaction = Reaction {
    118             target: SocialTarget::Event {
    119                 id: "a".repeat(64),
    120                 author: Some("b".repeat(64)),
    121                 event_kind: Some(1),
    122                 relays: None,
    123             },
    124             content: "+".to_string(),
    125         };
    126 
    127         let tags = reaction_build_tags(&reaction).expect("reaction tags");
    128         assert!(
    129             tags.iter()
    130                 .any(|tag| tag == &vec!["e".to_string(), "a".repeat(64)])
    131         );
    132     }
    133 }