lib

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

encode.rs (5192B)


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