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 }