lib

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

encode.rs (5093B)


      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_REPORT,
     11     post::report::Report,
     12     social::{ReportFileTarget, ReportType, SocialTarget},
     13     tag::name::{TAG_A, TAG_E, TAG_MAGNET, TAG_P, TAG_SERVER, TAG_SHA256},
     14 };
     15 
     16 use crate::error::EventEncodeError;
     17 use crate::field_helpers::{
     18     parse_address_tag, push_tag, validate_lowercase_hex_64, validate_non_empty_field,
     19 };
     20 use crate::social_helpers::validate_http_url;
     21 use radroots_event::wire::Nip01EventWireParts;
     22 
     23 pub fn report_build_tags(report: &Report) -> Result<Vec<Vec<String>>, EventEncodeError> {
     24     validate_report(report)?;
     25     let report_type = report_type_as_str(&report.report_type);
     26     let mut tags = Vec::new();
     27     tags.push(vec![
     28         TAG_P.to_string(),
     29         report.reported_pubkey.clone(),
     30         report_type.to_string(),
     31     ]);
     32     if let Some(event) = report.event.as_ref() {
     33         push_report_event_target(&mut tags, event, report_type)?;
     34     }
     35     if let Some(file) = report.file.as_ref() {
     36         push_report_file_target(&mut tags, file, report_type)?;
     37     }
     38     Ok(tags)
     39 }
     40 
     41 pub fn to_wire_parts(report: &Report) -> Result<Nip01EventWireParts, EventEncodeError> {
     42     to_wire_parts_with_kind(report, KIND_REPORT)
     43 }
     44 
     45 pub fn to_wire_parts_with_kind(
     46     report: &Report,
     47     kind: u32,
     48 ) -> Result<Nip01EventWireParts, EventEncodeError> {
     49     if kind != KIND_REPORT {
     50         return Err(EventEncodeError::InvalidKind(kind));
     51     }
     52     Ok(Nip01EventWireParts {
     53         kind,
     54         content: report.content.clone().unwrap_or_default(),
     55         tags: report_build_tags(report)?,
     56     })
     57 }
     58 
     59 fn validate_report(report: &Report) -> Result<(), EventEncodeError> {
     60     validate_non_empty_field(&report.reported_pubkey, "reported_pubkey")?;
     61     validate_lowercase_hex_64(&report.reported_pubkey, "reported_pubkey")?;
     62     if let Some(file) = report.file.as_ref() {
     63         validate_file_target(file)?;
     64     }
     65     Ok(())
     66 }
     67 
     68 fn push_report_event_target(
     69     tags: &mut Vec<Vec<String>>,
     70     target: &SocialTarget,
     71     report_type: &'static str,
     72 ) -> Result<(), EventEncodeError> {
     73     match target {
     74         SocialTarget::Event { id, relays, .. } => {
     75             validate_lowercase_hex_64(id, "event.id")?;
     76             let mut tag = vec![TAG_E.to_string(), id.clone(), report_type.to_string()];
     77             if let Some(relays) = relays.as_ref() {
     78                 tag.extend(
     79                     relays
     80                         .iter()
     81                         .filter(|relay| !relay.trim().is_empty())
     82                         .cloned(),
     83                 );
     84             }
     85             tags.push(tag);
     86             Ok(())
     87         }
     88         SocialTarget::Address {
     89             address, relays, ..
     90         } => {
     91             let address = parse_address_tag(address, "event.address")
     92                 .map_err(|_| EventEncodeError::InvalidField("event.address"))?;
     93             let mut tag = vec![
     94                 TAG_A.to_string(),
     95                 format!("{}:{}:{}", address.kind, address.pubkey, address.d_tag),
     96                 report_type.to_string(),
     97             ];
     98             if let Some(relays) = relays.as_ref() {
     99                 tag.extend(
    100                     relays
    101                         .iter()
    102                         .filter(|relay| !relay.trim().is_empty())
    103                         .cloned(),
    104                 );
    105             }
    106             tags.push(tag);
    107             Ok(())
    108         }
    109         SocialTarget::External { .. } => Err(EventEncodeError::InvalidField("event")),
    110     }
    111 }
    112 
    113 fn push_report_file_target(
    114     tags: &mut Vec<Vec<String>>,
    115     file: &ReportFileTarget,
    116     report_type: &'static str,
    117 ) -> Result<(), EventEncodeError> {
    118     if let Some(hash) = file.sha256.as_deref() {
    119         tags.push(vec![
    120             TAG_SHA256.to_string(),
    121             hash.to_string(),
    122             report_type.to_string(),
    123         ]);
    124     }
    125     if let Some(url) = file.url.as_deref() {
    126         push_tag(tags, TAG_SERVER, url);
    127     }
    128     if let Some(magnet) = file.magnet.as_deref() {
    129         push_tag(tags, TAG_MAGNET, magnet);
    130     }
    131     Ok(())
    132 }
    133 
    134 fn validate_file_target(file: &ReportFileTarget) -> Result<(), EventEncodeError> {
    135     if file.sha256.is_none() && file.url.is_none() && file.magnet.is_none() {
    136         return Err(EventEncodeError::EmptyRequiredField("file"));
    137     }
    138     if let Some(hash) = file.sha256.as_deref() {
    139         validate_lowercase_hex_64(hash, "file.sha256")?;
    140     }
    141     if let Some(url) = file.url.as_deref() {
    142         validate_http_url(url, "file.url")?;
    143     }
    144     if let Some(magnet) = file.magnet.as_deref() {
    145         validate_non_empty_field(magnet, "file.magnet")?;
    146     }
    147     Ok(())
    148 }
    149 
    150 fn report_type_as_str(report_type: &ReportType) -> &'static str {
    151     match report_type {
    152         ReportType::Nudity => "nudity",
    153         ReportType::Malware => "malware",
    154         ReportType::Profanity => "profanity",
    155         ReportType::Illegal => "illegal",
    156         ReportType::Spam => "spam",
    157         ReportType::Impersonation => "impersonation",
    158         ReportType::Other => "other",
    159     }
    160 }