encode.rs (14408B)
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::envelope::kind::{ 10 KIND_CONTRIBUTION_ATTESTATION, KIND_EVIDENCE_BOUNTY, KIND_KNOWLEDGE_CHANGE_PROPOSAL, 11 KIND_KNOWLEDGE_CLAIM, KIND_KNOWLEDGE_FIELD_REPORT, KIND_KNOWLEDGE_RELATION, 12 KIND_KNOWLEDGE_REVIEW, KIND_KNOWLEDGE_SOURCE, KIND_WIKI_ARTICLE, KIND_WIKI_MERGE_REQUEST, 13 KIND_WIKI_REDIRECT, 14 }; 15 use radroots_event::knowledge::{ 16 AddressableRef, ContributionAttestation, EvidenceBounty, KnowledgeChangeProposal, 17 KnowledgeClaim, KnowledgeFieldReport, KnowledgeRelation, KnowledgeReview, 18 KnowledgeReviewTarget, KnowledgeSource, KnowledgeValidationError, 19 RADROOTS_CONTRIBUTION_ATTESTATION_SCHEMA, RADROOTS_EVIDENCE_BOUNTY_SCHEMA, 20 RADROOTS_KNOWLEDGE_CHANGE_PROPOSAL_SCHEMA, RADROOTS_KNOWLEDGE_CLAIM_SCHEMA, 21 RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA, RADROOTS_KNOWLEDGE_RELATION_SCHEMA, 22 RADROOTS_KNOWLEDGE_REVIEW_SCHEMA, RADROOTS_KNOWLEDGE_SOURCE_SCHEMA, WikiArticle, 23 WikiArticleVersionRef, WikiMergeRequest, WikiRedirect, validate_contribution_attestation, 24 validate_evidence_bounty, validate_knowledge_change_proposal, validate_knowledge_claim, 25 validate_knowledge_field_report, validate_knowledge_relation, validate_knowledge_review, 26 validate_knowledge_source, validate_wiki_article, validate_wiki_merge_request, 27 validate_wiki_redirect, 28 }; 29 use radroots_event::tag::EventRef; 30 use radroots_event::tag::name::{ 31 TAG_A, TAG_CONTRACT, TAG_D, TAG_E, TAG_G, TAG_P, TAG_SUMMARY, TAG_T, 32 }; 33 use serde::Serialize; 34 35 use crate::error::EventEncodeError; 36 use crate::event_ref::build_event_ref_tag; 37 use crate::wire::empty_content; 38 use radroots_event::wire::Nip01EventWireParts; 39 use radroots_identity::PublicKey; 40 41 const TAG_TITLE: &str = "title"; 42 const TAG_SOURCE: &str = "source"; 43 const TAG_CITATION: &str = "citation"; 44 const TAG_REVIEW_TARGET: &str = "review_target"; 45 const TAG_EVIDENCE: &str = "evidence"; 46 const MARKER_FORK: &str = "fork"; 47 const MARKER_DEFER: &str = "defer"; 48 const E_MARKER_SOURCE: &str = "source"; 49 50 fn push_value(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { 51 if !value.trim().is_empty() { 52 tags.push(vec![key.to_string(), value.to_string()]); 53 } 54 } 55 56 fn push_optional_value(tags: &mut Vec<Vec<String>>, key: &str, value: Option<&str>) { 57 if let Some(value) = value { 58 push_value(tags, key, value); 59 } 60 } 61 62 fn push_topics(tags: &mut Vec<Vec<String>>, topics: &[String]) { 63 for topic in topics { 64 push_value(tags, TAG_T, topic); 65 } 66 } 67 68 fn push_event_refs(tags: &mut Vec<Vec<String>>, tag_name: &str, refs: &[EventRef]) { 69 for event_ref in refs { 70 tags.push(build_event_ref_tag(tag_name, event_ref)); 71 } 72 } 73 74 fn address_coordinate(address: &AddressableRef) -> String { 75 format!("{}:{}:{}", address.kind, address.pubkey, address.d_tag) 76 } 77 78 fn address_tag(tag_name: &str, address: &AddressableRef) -> Vec<String> { 79 let mut tag = Vec::with_capacity(2 + address.relays.len()); 80 tag.push(tag_name.to_string()); 81 tag.push(address_coordinate(address)); 82 tag.extend(address.relays.iter().cloned()); 83 tag 84 } 85 86 fn marker_address_tag(address: &AddressableRef, marker: &'static str) -> Vec<String> { 87 let mut tag = Vec::with_capacity(4 + address.relays.len()); 88 tag.push(TAG_A.to_string()); 89 tag.push(address_coordinate(address)); 90 if address.relays.is_empty() { 91 tag.push(String::new()); 92 } else { 93 tag.extend(address.relays.iter().cloned()); 94 } 95 tag.push(marker.to_string()); 96 tag 97 } 98 99 fn marker_event_tag(version_ref: &WikiArticleVersionRef, marker: &'static str) -> Vec<String> { 100 let mut tag = Vec::with_capacity(4 + version_ref.address_ref.relays.len()); 101 tag.push(TAG_E.to_string()); 102 tag.push(version_ref.event_id.clone()); 103 if version_ref.address_ref.relays.is_empty() { 104 tag.push(String::new()); 105 } else { 106 tag.extend(version_ref.address_ref.relays.iter().cloned()); 107 } 108 tag.push(marker.to_string()); 109 tag 110 } 111 112 fn push_wiki_version_ref_tags( 113 tags: &mut Vec<Vec<String>>, 114 version_ref: &WikiArticleVersionRef, 115 marker: &'static str, 116 ) { 117 tags.push(marker_address_tag(&version_ref.address_ref, marker)); 118 tags.push(marker_event_tag(version_ref, marker)); 119 } 120 121 fn review_target_ref(target: &KnowledgeReviewTarget) -> Result<EventRef, EventEncodeError> { 122 Ok(EventRef { 123 id: target.event_id.clone(), 124 author: PublicKey::from_hex(&target.author_pubkey) 125 .map_err(|_| EventEncodeError::InvalidField("review_target"))?, 126 kind: target.kind, 127 d_tag: target 128 .address 129 .as_deref() 130 .and_then(|address| address.rsplit_once(':').map(|(_, d_tag)| d_tag.to_string())), 131 relays: if target.relays.is_empty() { 132 None 133 } else { 134 Some(target.relays.clone()) 135 }, 136 }) 137 } 138 139 fn json_content<T: Serialize>(value: &T) -> Result<String, EventEncodeError> { 140 serde_json::to_string(value).map_err(|_| EventEncodeError::Json) 141 } 142 143 fn encode_validation_error(error: KnowledgeValidationError) -> EventEncodeError { 144 match error { 145 KnowledgeValidationError::EmptyField(field) => EventEncodeError::EmptyRequiredField(field), 146 KnowledgeValidationError::InvalidField(field) => EventEncodeError::InvalidField(field), 147 } 148 } 149 150 fn custom_tags(contract_id: &'static str) -> Vec<Vec<String>> { 151 vec![vec![TAG_CONTRACT.to_string(), contract_id.to_string()]] 152 } 153 154 pub fn wiki_article_build_tags( 155 article: &WikiArticle, 156 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 157 validate_wiki_article(article).map_err(encode_validation_error)?; 158 let mut tags = Vec::new(); 159 tags.push(vec![TAG_D.to_string(), article.d_tag.clone()]); 160 push_optional_value(&mut tags, TAG_TITLE, article.title.as_deref()); 161 push_optional_value(&mut tags, TAG_SUMMARY, article.summary.as_deref()); 162 push_topics(&mut tags, &article.topics); 163 push_event_refs(&mut tags, TAG_SOURCE, &article.references); 164 for forked_from in &article.forked_from { 165 push_wiki_version_ref_tags(&mut tags, forked_from, MARKER_FORK); 166 } 167 if let Some(deferred_to) = &article.deferred_to { 168 push_wiki_version_ref_tags(&mut tags, deferred_to, MARKER_DEFER); 169 } 170 Ok(tags) 171 } 172 173 pub fn wiki_article_to_wire_parts( 174 article: &WikiArticle, 175 ) -> Result<Nip01EventWireParts, EventEncodeError> { 176 Ok(Nip01EventWireParts { 177 kind: KIND_WIKI_ARTICLE, 178 content: article.content_djot.clone(), 179 tags: wiki_article_build_tags(article)?, 180 }) 181 } 182 183 pub fn wiki_redirect_build_tags( 184 redirect: &WikiRedirect, 185 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 186 validate_wiki_redirect(redirect).map_err(encode_validation_error)?; 187 let tags = vec![ 188 vec![TAG_D.to_string(), redirect.d_tag.clone()], 189 address_tag(TAG_A, &redirect.target), 190 ]; 191 Ok(tags) 192 } 193 194 pub fn wiki_redirect_to_wire_parts( 195 redirect: &WikiRedirect, 196 ) -> Result<Nip01EventWireParts, EventEncodeError> { 197 Ok(Nip01EventWireParts { 198 kind: KIND_WIKI_REDIRECT, 199 content: empty_content(), 200 tags: wiki_redirect_build_tags(redirect)?, 201 }) 202 } 203 204 pub fn wiki_merge_request_build_tags( 205 request: &WikiMergeRequest, 206 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 207 validate_wiki_merge_request(request).map_err(encode_validation_error)?; 208 let mut tags = Vec::new(); 209 tags.push(address_tag(TAG_A, &request.target_article)); 210 tags.push(vec![TAG_P.to_string(), request.destination_pubkey.clone()]); 211 if let Some(base) = request 212 .base_version_event_id 213 .as_ref() 214 .filter(|value| !value.trim().is_empty()) 215 { 216 tags.push(vec![TAG_E.to_string(), base.clone(), String::new()]); 217 } 218 tags.push(vec![ 219 TAG_E.to_string(), 220 request.source_version_event_id.clone(), 221 String::new(), 222 E_MARKER_SOURCE.to_string(), 223 ]); 224 Ok(tags) 225 } 226 227 pub fn wiki_merge_request_to_wire_parts( 228 request: &WikiMergeRequest, 229 ) -> Result<Nip01EventWireParts, EventEncodeError> { 230 Ok(Nip01EventWireParts { 231 kind: KIND_WIKI_MERGE_REQUEST, 232 content: request.explanation.clone().unwrap_or_default(), 233 tags: wiki_merge_request_build_tags(request)?, 234 }) 235 } 236 237 pub fn knowledge_source_build_tags( 238 source: &KnowledgeSource, 239 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 240 validate_knowledge_source(source).map_err(encode_validation_error)?; 241 let mut tags = custom_tags(RADROOTS_KNOWLEDGE_SOURCE_SCHEMA); 242 tags.push(vec![TAG_D.to_string(), source.d_tag.clone()]); 243 push_topics(&mut tags, &source.topics); 244 push_event_refs(&mut tags, TAG_SOURCE, &source.artifact_refs); 245 Ok(tags) 246 } 247 248 pub fn knowledge_source_to_wire_parts( 249 source: &KnowledgeSource, 250 ) -> Result<Nip01EventWireParts, EventEncodeError> { 251 Ok(Nip01EventWireParts { 252 kind: KIND_KNOWLEDGE_SOURCE, 253 content: json_content(source)?, 254 tags: knowledge_source_build_tags(source)?, 255 }) 256 } 257 258 pub fn evidence_bounty_build_tags( 259 bounty: &EvidenceBounty, 260 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 261 validate_evidence_bounty(bounty).map_err(encode_validation_error)?; 262 let mut tags = custom_tags(RADROOTS_EVIDENCE_BOUNTY_SCHEMA); 263 tags.push(vec![TAG_D.to_string(), bounty.d_tag.clone()]); 264 push_topics(&mut tags, &bounty.topics); 265 push_event_refs(&mut tags, TAG_EVIDENCE, &bounty.target_refs); 266 Ok(tags) 267 } 268 269 pub fn evidence_bounty_to_wire_parts( 270 bounty: &EvidenceBounty, 271 ) -> Result<Nip01EventWireParts, EventEncodeError> { 272 Ok(Nip01EventWireParts { 273 kind: KIND_EVIDENCE_BOUNTY, 274 content: json_content(bounty)?, 275 tags: evidence_bounty_build_tags(bounty)?, 276 }) 277 } 278 279 pub fn knowledge_claim_build_tags( 280 claim: &KnowledgeClaim, 281 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 282 validate_knowledge_claim(claim).map_err(encode_validation_error)?; 283 let mut tags = custom_tags(RADROOTS_KNOWLEDGE_CLAIM_SCHEMA); 284 push_topics(&mut tags, &claim.topics); 285 for citation in &claim.citation_spans { 286 tags.push(build_event_ref_tag(TAG_SOURCE, &citation.source_ref)); 287 if let Some(quote_hash) = citation.quote_hash.as_deref() { 288 push_value(&mut tags, TAG_CITATION, quote_hash); 289 } 290 } 291 Ok(tags) 292 } 293 294 pub fn knowledge_claim_to_wire_parts( 295 claim: &KnowledgeClaim, 296 ) -> Result<Nip01EventWireParts, EventEncodeError> { 297 Ok(Nip01EventWireParts { 298 kind: KIND_KNOWLEDGE_CLAIM, 299 content: json_content(claim)?, 300 tags: knowledge_claim_build_tags(claim)?, 301 }) 302 } 303 304 pub fn knowledge_relation_build_tags( 305 relation: &KnowledgeRelation, 306 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 307 validate_knowledge_relation(relation).map_err(encode_validation_error)?; 308 let mut tags = custom_tags(RADROOTS_KNOWLEDGE_RELATION_SCHEMA); 309 push_event_refs(&mut tags, TAG_SOURCE, &relation.support_refs); 310 Ok(tags) 311 } 312 313 pub fn knowledge_relation_to_wire_parts( 314 relation: &KnowledgeRelation, 315 ) -> Result<Nip01EventWireParts, EventEncodeError> { 316 Ok(Nip01EventWireParts { 317 kind: KIND_KNOWLEDGE_RELATION, 318 content: json_content(relation)?, 319 tags: knowledge_relation_build_tags(relation)?, 320 }) 321 } 322 323 pub fn knowledge_review_build_tags( 324 review: &KnowledgeReview, 325 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 326 validate_knowledge_review(review).map_err(encode_validation_error)?; 327 let mut tags = custom_tags(RADROOTS_KNOWLEDGE_REVIEW_SCHEMA); 328 tags.push(build_event_ref_tag( 329 TAG_REVIEW_TARGET, 330 &review_target_ref(&review.target)?, 331 )); 332 push_event_refs(&mut tags, TAG_EVIDENCE, &review.evidence_refs); 333 Ok(tags) 334 } 335 336 pub fn knowledge_review_to_wire_parts( 337 review: &KnowledgeReview, 338 ) -> Result<Nip01EventWireParts, EventEncodeError> { 339 Ok(Nip01EventWireParts { 340 kind: KIND_KNOWLEDGE_REVIEW, 341 content: json_content(review)?, 342 tags: knowledge_review_build_tags(review)?, 343 }) 344 } 345 346 pub fn knowledge_field_report_build_tags( 347 report: &KnowledgeFieldReport, 348 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 349 validate_knowledge_field_report(report).map_err(encode_validation_error)?; 350 let mut tags = custom_tags(RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA); 351 push_topics(&mut tags, &report.context.topics); 352 if let Some(location) = &report.context.public_location 353 && let Some(geohash) = location.geohash.as_deref() 354 { 355 push_value(&mut tags, TAG_G, geohash); 356 } 357 push_event_refs(&mut tags, TAG_EVIDENCE, &report.artifact_refs); 358 push_event_refs(&mut tags, TAG_EVIDENCE, &report.related_refs); 359 Ok(tags) 360 } 361 362 pub fn knowledge_field_report_to_wire_parts( 363 report: &KnowledgeFieldReport, 364 ) -> Result<Nip01EventWireParts, EventEncodeError> { 365 Ok(Nip01EventWireParts { 366 kind: KIND_KNOWLEDGE_FIELD_REPORT, 367 content: json_content(report)?, 368 tags: knowledge_field_report_build_tags(report)?, 369 }) 370 } 371 372 pub fn knowledge_change_proposal_build_tags( 373 proposal: &KnowledgeChangeProposal, 374 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 375 validate_knowledge_change_proposal(proposal).map_err(encode_validation_error)?; 376 let mut tags = custom_tags(RADROOTS_KNOWLEDGE_CHANGE_PROPOSAL_SCHEMA); 377 push_event_refs(&mut tags, TAG_EVIDENCE, &proposal.evidence_refs); 378 Ok(tags) 379 } 380 381 pub fn knowledge_change_proposal_to_wire_parts( 382 proposal: &KnowledgeChangeProposal, 383 ) -> Result<Nip01EventWireParts, EventEncodeError> { 384 Ok(Nip01EventWireParts { 385 kind: KIND_KNOWLEDGE_CHANGE_PROPOSAL, 386 content: json_content(proposal)?, 387 tags: knowledge_change_proposal_build_tags(proposal)?, 388 }) 389 } 390 391 pub fn contribution_attestation_build_tags( 392 attestation: &ContributionAttestation, 393 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 394 validate_contribution_attestation(attestation).map_err(encode_validation_error)?; 395 let mut tags = custom_tags(RADROOTS_CONTRIBUTION_ATTESTATION_SCHEMA); 396 push_event_refs(&mut tags, TAG_EVIDENCE, &attestation.evidence_refs); 397 Ok(tags) 398 } 399 400 pub fn contribution_attestation_to_wire_parts( 401 attestation: &ContributionAttestation, 402 ) -> Result<Nip01EventWireParts, EventEncodeError> { 403 Ok(Nip01EventWireParts { 404 kind: KIND_CONTRIBUTION_ATTESTATION, 405 content: json_content(attestation)?, 406 tags: contribution_attestation_build_tags(attestation)?, 407 }) 408 }