lib

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

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 }