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 }