lib

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

encode.rs (6676B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     format,
      4     string::{String, ToString},
      5     vec::Vec,
      6 };
      7 
      8 use radroots_events::{
      9     comment::RadrootsComment,
     10     kinds::{KIND_COMMENT, KIND_POST},
     11     social::RadrootsSocialTarget,
     12 };
     13 
     14 use crate::error::EventEncodeError;
     15 use crate::field_helpers::{
     16     parse_address_tag, validate_lowercase_hex_64, validate_non_empty_field,
     17 };
     18 use crate::wire::WireEventParts;
     19 
     20 const DEFAULT_KIND: u32 = KIND_COMMENT;
     21 
     22 pub fn comment_build_tags(comment: &RadrootsComment) -> Result<Vec<Vec<String>>, EventEncodeError> {
     23     let mut tags = Vec::with_capacity(8);
     24     push_comment_target(&mut tags, &comment.root, CommentTargetTags::root())?;
     25     push_comment_target(&mut tags, &comment.parent, CommentTargetTags::parent())?;
     26     Ok(tags)
     27 }
     28 
     29 pub fn to_wire_parts(comment: &RadrootsComment) -> Result<WireEventParts, EventEncodeError> {
     30     to_wire_parts_with_kind(comment, DEFAULT_KIND)
     31 }
     32 
     33 pub fn to_wire_parts_with_kind(
     34     comment: &RadrootsComment,
     35     kind: u32,
     36 ) -> Result<WireEventParts, EventEncodeError> {
     37     if kind != DEFAULT_KIND {
     38         return Err(EventEncodeError::InvalidKind(kind));
     39     }
     40     if comment.content.trim().is_empty() {
     41         return Err(EventEncodeError::EmptyRequiredField("content"));
     42     }
     43     let tags = comment_build_tags(comment)?;
     44     Ok(WireEventParts {
     45         kind,
     46         content: comment.content.clone(),
     47         tags,
     48     })
     49 }
     50 
     51 struct CommentTargetTags {
     52     event: &'static str,
     53     address: &'static str,
     54     external: &'static str,
     55     author: &'static str,
     56     kind: &'static str,
     57     field: &'static str,
     58 }
     59 
     60 impl CommentTargetTags {
     61     fn root() -> Self {
     62         Self {
     63             event: "E",
     64             address: "A",
     65             external: "I",
     66             author: "P",
     67             kind: "K",
     68             field: "root",
     69         }
     70     }
     71 
     72     fn parent() -> Self {
     73         Self {
     74             event: "e",
     75             address: "a",
     76             external: "i",
     77             author: "p",
     78             kind: "k",
     79             field: "parent",
     80         }
     81     }
     82 }
     83 
     84 fn push_comment_target(
     85     tags: &mut Vec<Vec<String>>,
     86     target: &RadrootsSocialTarget,
     87     keys: CommentTargetTags,
     88 ) -> Result<(), EventEncodeError> {
     89     match target {
     90         RadrootsSocialTarget::Event {
     91             id,
     92             author,
     93             event_kind,
     94             relays,
     95         } => {
     96             validate_lowercase_hex_64(id, keys.field)?;
     97             let author = author
     98                 .as_deref()
     99                 .ok_or(EventEncodeError::EmptyRequiredField(keys.field))?;
    100             validate_non_empty_field(author, keys.field)?;
    101             let kind = event_kind.ok_or(EventEncodeError::EmptyRequiredField(keys.field))?;
    102             validate_comment_target_kind(kind, keys.field)?;
    103             let mut event_tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len));
    104             event_tag.push(keys.event.to_string());
    105             event_tag.push(id.clone());
    106             if let Some(relays) = relays {
    107                 event_tag.extend(relays.iter().cloned());
    108             }
    109             tags.push(event_tag);
    110             tags.push(vec![keys.author.to_string(), author.to_string()]);
    111             tags.push(vec![keys.kind.to_string(), kind.to_string()]);
    112         }
    113         RadrootsSocialTarget::Address {
    114             address,
    115             author,
    116             event_kind,
    117             relays,
    118         } => {
    119             let parsed = parse_address_tag(address, keys.field)
    120                 .map_err(|_| EventEncodeError::InvalidField(keys.field))?;
    121             validate_comment_target_kind(parsed.kind, keys.field)?;
    122             if let Some(kind) = event_kind
    123                 && *kind != parsed.kind
    124             {
    125                 return Err(EventEncodeError::InvalidField(keys.field));
    126             }
    127             if let Some(author) = author.as_deref()
    128                 && author != parsed.pubkey
    129             {
    130                 return Err(EventEncodeError::InvalidField(keys.field));
    131             }
    132             let mut address_tag = Vec::with_capacity(2 + relays.as_ref().map_or(0, Vec::len));
    133             address_tag.push(keys.address.to_string());
    134             address_tag.push(format!(
    135                 "{}:{}:{}",
    136                 parsed.kind, parsed.pubkey, parsed.d_tag
    137             ));
    138             if let Some(relays) = relays {
    139                 address_tag.extend(relays.iter().cloned());
    140             }
    141             tags.push(address_tag);
    142             tags.push(vec![keys.author.to_string(), parsed.pubkey]);
    143             tags.push(vec![keys.kind.to_string(), parsed.kind.to_string()]);
    144         }
    145         RadrootsSocialTarget::External {
    146             id,
    147             external_kind,
    148             hint,
    149         } => {
    150             validate_non_empty_field(id, keys.field)?;
    151             validate_non_empty_field(external_kind, keys.field)?;
    152             if external_kind == "1" {
    153                 return Err(EventEncodeError::InvalidField(keys.field));
    154             }
    155             let mut external_tag = Vec::with_capacity(3);
    156             external_tag.push(keys.external.to_string());
    157             external_tag.push(id.clone());
    158             if let Some(hint) = hint.as_deref().filter(|value| !value.trim().is_empty()) {
    159                 external_tag.push(hint.to_string());
    160             }
    161             tags.push(external_tag);
    162             tags.push(vec![keys.kind.to_string(), external_kind.clone()]);
    163         }
    164     }
    165     Ok(())
    166 }
    167 
    168 fn validate_comment_target_kind(kind: u32, field: &'static str) -> Result<(), EventEncodeError> {
    169     if kind == KIND_POST {
    170         Err(EventEncodeError::InvalidField(field))
    171     } else {
    172         Ok(())
    173     }
    174 }
    175 
    176 #[cfg(test)]
    177 mod tests {
    178     use super::*;
    179 
    180     #[test]
    181     fn comment_targets_encode_without_relays() {
    182         let author = "a".repeat(64);
    183         let comment = RadrootsComment {
    184             root: RadrootsSocialTarget::Event {
    185                 id: "b".repeat(64),
    186                 author: Some(author.clone()),
    187                 event_kind: Some(KIND_COMMENT),
    188                 relays: None,
    189             },
    190             parent: RadrootsSocialTarget::Address {
    191                 address: format!("{KIND_COMMENT}:{author}:AAAAAAAAAAAAAAAAAAAAAA"),
    192                 author: Some(author),
    193                 event_kind: Some(KIND_COMMENT),
    194                 relays: None,
    195             },
    196             content: "looks good".to_string(),
    197         };
    198 
    199         let tags = comment_build_tags(&comment).expect("comment tags");
    200         assert!(
    201             tags.iter()
    202                 .any(|tag| tag == &vec!["E".to_string(), "b".repeat(64)])
    203         );
    204         assert!(
    205             tags.iter()
    206                 .any(|tag| tag.first().map(String::as_str) == Some("a") && tag.len() == 2)
    207         );
    208     }
    209 }