lib

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

decode.rs (24228B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     string::{String, ToString},
      4     vec::Vec,
      5 };
      6 
      7 use radroots_event::envelope::kind::{
      8     KIND_CONTRIBUTION_ATTESTATION, KIND_EVIDENCE_BOUNTY, KIND_KNOWLEDGE_CHANGE_PROPOSAL,
      9     KIND_KNOWLEDGE_CLAIM, KIND_KNOWLEDGE_FIELD_REPORT, KIND_KNOWLEDGE_RELATION,
     10     KIND_KNOWLEDGE_REVIEW, KIND_KNOWLEDGE_SOURCE, KIND_WIKI_ARTICLE, KIND_WIKI_MERGE_REQUEST,
     11     KIND_WIKI_REDIRECT,
     12 };
     13 use radroots_event::knowledge::{
     14     AddressableRef, ContributionAttestation, EvidenceBounty, KnowledgeChangeProposal,
     15     KnowledgeClaim, KnowledgeFieldReport, KnowledgeRelation, KnowledgeReview, KnowledgeSource,
     16     KnowledgeValidationError, RADROOTS_CONTRIBUTION_ATTESTATION_SCHEMA,
     17     RADROOTS_EVIDENCE_BOUNTY_SCHEMA, RADROOTS_KNOWLEDGE_CHANGE_PROPOSAL_SCHEMA,
     18     RADROOTS_KNOWLEDGE_CLAIM_SCHEMA, RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA,
     19     RADROOTS_KNOWLEDGE_RELATION_SCHEMA, RADROOTS_KNOWLEDGE_REVIEW_SCHEMA,
     20     RADROOTS_KNOWLEDGE_SOURCE_SCHEMA, WikiArticle, WikiArticleVersionRef, WikiMergeRequest,
     21     WikiRedirect, validate_contribution_attestation, validate_evidence_bounty,
     22     validate_knowledge_change_proposal, validate_knowledge_claim, validate_knowledge_field_report,
     23     validate_knowledge_relation, validate_knowledge_review, validate_knowledge_source,
     24     validate_wiki_article, validate_wiki_d_tag, validate_wiki_merge_request,
     25     validate_wiki_redirect,
     26 };
     27 use radroots_event::tag::name::{
     28     TAG_A, TAG_CONTRACT, TAG_D, TAG_E, TAG_G, TAG_P, TAG_SUMMARY, TAG_T,
     29 };
     30 use radroots_event::{envelope::EventEnvelope, tag::EventRef};
     31 use serde::de::DeserializeOwned;
     32 
     33 use crate::error::EventParseError;
     34 use crate::event_ref::parse_event_ref_tag;
     35 use crate::knowledge::encode::{
     36     contribution_attestation_build_tags, evidence_bounty_build_tags,
     37     knowledge_change_proposal_build_tags, knowledge_claim_build_tags,
     38     knowledge_field_report_build_tags, knowledge_relation_build_tags, knowledge_review_build_tags,
     39     knowledge_source_build_tags,
     40 };
     41 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent};
     42 
     43 const TAG_TITLE: &str = "title";
     44 const TAG_SOURCE: &str = "source";
     45 const TAG_CITATION: &str = "citation";
     46 const TAG_REVIEW_TARGET: &str = "review_target";
     47 const TAG_EVIDENCE: &str = "evidence";
     48 const MARKER_FORK: &str = "fork";
     49 const MARKER_DEFER: &str = "defer";
     50 const E_MARKER_SOURCE: &str = "source";
     51 
     52 fn ensure_kind(kind: u32, expected: u32, name: &'static str) -> Result<(), EventParseError> {
     53     if kind == expected {
     54         Ok(())
     55     } else {
     56         Err(EventParseError::InvalidKind {
     57             expected: name,
     58             got: kind,
     59         })
     60     }
     61 }
     62 
     63 fn matching_tags<'a>(tags: &'a [Vec<String>], name: &'static str) -> Vec<&'a Vec<String>> {
     64     tags.iter()
     65         .filter(|tag| tag.first().map(|value| value.as_str()) == Some(name))
     66         .collect()
     67 }
     68 
     69 fn required_one_value(tags: &[Vec<String>], name: &'static str) -> Result<String, EventParseError> {
     70     let matches = matching_tags(tags, name);
     71     if matches.is_empty() {
     72         return Err(EventParseError::MissingTag(name));
     73     }
     74     if matches.len() > 1 {
     75         return Err(EventParseError::InvalidTag(name));
     76     }
     77     let value = matches[0].get(1).ok_or(EventParseError::InvalidTag(name))?;
     78     if value.trim().is_empty() {
     79         return Err(EventParseError::InvalidTag(name));
     80     }
     81     Ok(value.clone())
     82 }
     83 
     84 fn optional_one_value(
     85     tags: &[Vec<String>],
     86     name: &'static str,
     87 ) -> Result<Option<String>, EventParseError> {
     88     let matches = matching_tags(tags, name);
     89     if matches.len() > 1 {
     90         return Err(EventParseError::InvalidTag(name));
     91     }
     92     Ok(matches
     93         .first()
     94         .and_then(|tag| tag.get(1))
     95         .filter(|value| !value.trim().is_empty())
     96         .cloned())
     97 }
     98 
     99 fn values(tags: &[Vec<String>], name: &'static str) -> Vec<String> {
    100     tags.iter()
    101         .filter(|tag| tag.first().map(|value| value.as_str()) == Some(name))
    102         .filter_map(|tag| tag.get(1))
    103         .filter(|value| !value.trim().is_empty())
    104         .cloned()
    105         .collect()
    106 }
    107 
    108 fn tag_matches_names(tag: &[String], names: &[&'static str]) -> bool {
    109     tag.first()
    110         .map(|value| names.contains(&value.as_str()))
    111         .unwrap_or(false)
    112 }
    113 
    114 fn mirrored_tags<'a>(tags: &'a [Vec<String>], names: &[&'static str]) -> Vec<&'a [String]> {
    115     tags.iter()
    116         .map(Vec::as_slice)
    117         .filter(|tag| tag_matches_names(tag, names))
    118         .collect()
    119 }
    120 
    121 fn tag_name(tag: &[String]) -> Option<&str> {
    122     tag.first().map(String::as_str)
    123 }
    124 
    125 fn first_mirrored_difference(
    126     actual: &[&[String]],
    127     expected: &[&[String]],
    128     names: &[&'static str],
    129 ) -> &'static str {
    130     for (actual_tag, expected_tag) in actual.iter().zip(expected.iter()) {
    131         if actual_tag != expected_tag {
    132             if let Some(name) = tag_name(actual_tag)
    133                 && let Some(expected_name) = names.iter().find(|candidate| **candidate == name)
    134             {
    135                 return expected_name;
    136             }
    137             if let Some(name) = tag_name(expected_tag)
    138                 && let Some(expected_name) = names.iter().find(|candidate| **candidate == name)
    139             {
    140                 return expected_name;
    141             }
    142         }
    143     }
    144     if actual.len() > expected.len()
    145         && let Some(name) = tag_name(actual[expected.len()])
    146         && let Some(expected_name) = names.iter().find(|candidate| **candidate == name)
    147     {
    148         return expected_name;
    149     }
    150     if expected.len() > actual.len()
    151         && let Some(name) = tag_name(expected[actual.len()])
    152         && let Some(expected_name) = names.iter().find(|candidate| **candidate == name)
    153     {
    154         return expected_name;
    155     }
    156     names[0]
    157 }
    158 
    159 fn ensure_mirrored_tags(
    160     actual_tags: &[Vec<String>],
    161     expected_tags: &[Vec<String>],
    162     names: &[&'static str],
    163 ) -> Result<(), EventParseError> {
    164     let actual = mirrored_tags(actual_tags, names);
    165     let expected = mirrored_tags(expected_tags, names);
    166     if actual == expected {
    167         Ok(())
    168     } else {
    169         Err(EventParseError::InvalidTag(first_mirrored_difference(
    170             &actual, &expected, names,
    171         )))
    172     }
    173 }
    174 
    175 fn event_refs(tags: &[Vec<String>], name: &'static str) -> Result<Vec<EventRef>, EventParseError> {
    176     matching_tags(tags, name)
    177         .into_iter()
    178         .map(|tag| parse_event_ref_tag(tag, name))
    179         .collect()
    180 }
    181 
    182 fn marker(tag: &[String]) -> Option<&str> {
    183     tag.last()
    184         .map(|value| value.as_str())
    185         .filter(|value| matches!(*value, MARKER_FORK | MARKER_DEFER | E_MARKER_SOURCE))
    186 }
    187 
    188 fn wiki_version_marker(value: &str) -> bool {
    189     matches!(value, MARKER_FORK | MARKER_DEFER)
    190 }
    191 
    192 fn validate_wiki_version_marker_position(
    193     tag: &[String],
    194     name: &'static str,
    195 ) -> Result<(), EventParseError> {
    196     if tag.len() > 2
    197         && tag[2..tag.len().saturating_sub(1)]
    198             .iter()
    199             .any(|value| wiki_version_marker(value.as_str()))
    200     {
    201         Err(EventParseError::InvalidTag(name))
    202     } else {
    203         Ok(())
    204     }
    205 }
    206 
    207 fn validate_wiki_version_marker_position_for_tag(tag: &[String]) -> Result<(), EventParseError> {
    208     match tag.first().map(String::as_str) {
    209         Some(TAG_A) => validate_wiki_version_marker_position(tag, TAG_A),
    210         Some(TAG_E) => validate_wiki_version_marker_position(tag, TAG_E),
    211         _ => Ok(()),
    212     }
    213 }
    214 
    215 fn marked_wiki_version_relay_entries<'a>(
    216     tag: &'a [String],
    217     name: &'static str,
    218 ) -> Result<&'a [String], EventParseError> {
    219     if tag.len() < 4 {
    220         return Err(EventParseError::InvalidTag(name));
    221     }
    222     Ok(&tag[2..tag.len() - 1])
    223 }
    224 
    225 fn unmarked_tags<'a>(tags: &'a [Vec<String>], name: &'static str) -> Vec<&'a Vec<String>> {
    226     matching_tags(tags, name)
    227         .into_iter()
    228         .filter(|tag| marker(tag).is_none())
    229         .collect()
    230 }
    231 
    232 fn address_from_tag(tag: &[String], name: &'static str) -> Result<AddressableRef, EventParseError> {
    233     let value = tag.get(1).ok_or(EventParseError::InvalidTag(name))?;
    234     let mut parts = value.splitn(3, ':');
    235     let kind = parts
    236         .next()
    237         .ok_or(EventParseError::InvalidTag(name))?
    238         .parse()
    239         .map_err(|error| EventParseError::InvalidNumber(name, error))?;
    240     let pubkey = parts
    241         .next()
    242         .filter(|value| !value.trim().is_empty())
    243         .ok_or(EventParseError::InvalidTag(name))?;
    244     let d_tag = parts
    245         .next()
    246         .filter(|value| !value.trim().is_empty())
    247         .ok_or(EventParseError::InvalidTag(name))?;
    248     let relays_end = if marker(tag).is_some() {
    249         tag.len().saturating_sub(1)
    250     } else {
    251         tag.len()
    252     };
    253     Ok(AddressableRef {
    254         kind,
    255         pubkey: pubkey.to_string(),
    256         d_tag: d_tag.to_string(),
    257         relays: tag
    258             .get(2..relays_end)
    259             .map(|values| {
    260                 values
    261                     .iter()
    262                     .filter(|value| !value.is_empty())
    263                     .cloned()
    264                     .collect()
    265             })
    266             .unwrap_or_default(),
    267     })
    268 }
    269 
    270 fn address_from_a_tag(
    271     tags: &[Vec<String>],
    272     name: &'static str,
    273 ) -> Result<AddressableRef, EventParseError> {
    274     let matches = unmarked_tags(tags, name);
    275     if matches.is_empty() {
    276         return Err(EventParseError::MissingTag(name));
    277     }
    278     if matches.len() > 1 {
    279         return Err(EventParseError::InvalidTag(name));
    280     }
    281     address_from_tag(matches[0], name)
    282 }
    283 
    284 fn wiki_version_refs(
    285     tags: &[Vec<String>],
    286     marker_name: &'static str,
    287 ) -> Result<Vec<WikiArticleVersionRef>, EventParseError> {
    288     let mut refs = Vec::new();
    289     let mut index = 0;
    290     while let Some(tag) = tags.get(index) {
    291         validate_wiki_version_marker_position_for_tag(tag)?;
    292 
    293         if marker(tag) != Some(marker_name) {
    294             index += 1;
    295             continue;
    296         }
    297 
    298         match tag.first().map(String::as_str) {
    299             Some(TAG_A) => {
    300                 let event_tag = tags
    301                     .get(index + 1)
    302                     .ok_or(EventParseError::InvalidTag(TAG_A))?;
    303                 validate_wiki_version_marker_position_for_tag(event_tag)?;
    304                 if event_tag.first().map(String::as_str) != Some(TAG_E)
    305                     || marker(event_tag) != Some(marker_name)
    306                 {
    307                     return Err(EventParseError::InvalidTag(TAG_A));
    308                 }
    309                 let address_ref = address_from_tag(tag, TAG_A)?;
    310                 if address_ref.kind != KIND_WIKI_ARTICLE {
    311                     return Err(EventParseError::InvalidTag(TAG_A));
    312                 }
    313                 if marked_wiki_version_relay_entries(tag, TAG_A)?
    314                     != marked_wiki_version_relay_entries(event_tag, TAG_E)?
    315                 {
    316                     return Err(EventParseError::InvalidTag(TAG_E));
    317                 }
    318                 let event_id = event_tag
    319                     .get(1)
    320                     .filter(|value| !value.trim().is_empty())
    321                     .ok_or(EventParseError::InvalidTag(TAG_E))?
    322                     .clone();
    323                 refs.push(WikiArticleVersionRef {
    324                     event_id,
    325                     address_ref,
    326                 });
    327                 index += 2;
    328             }
    329             Some(TAG_E) => return Err(EventParseError::InvalidTag(TAG_E)),
    330             _ => {
    331                 index += 1;
    332             }
    333         }
    334     }
    335     Ok(refs)
    336 }
    337 
    338 fn wiki_merge_source_event_id(tags: &[Vec<String>]) -> Result<String, EventParseError> {
    339     let source_tags = matching_tags(tags, TAG_E)
    340         .into_iter()
    341         .filter(|tag| marker(tag) == Some(E_MARKER_SOURCE))
    342         .collect::<Vec<_>>();
    343     if source_tags.len() != 1 {
    344         return Err(EventParseError::InvalidTag(TAG_E));
    345     }
    346     source_tags[0]
    347         .get(1)
    348         .filter(|value| !value.trim().is_empty())
    349         .cloned()
    350         .ok_or(EventParseError::InvalidTag(TAG_E))
    351 }
    352 
    353 fn wiki_merge_base_event_id(tags: &[Vec<String>]) -> Result<Option<String>, EventParseError> {
    354     let base_tags = unmarked_tags(tags, TAG_E);
    355     if base_tags.len() > 1 {
    356         return Err(EventParseError::InvalidTag(TAG_E));
    357     }
    358     base_tags
    359         .first()
    360         .map(|tag| {
    361             tag.get(1)
    362                 .filter(|value| !value.trim().is_empty())
    363                 .cloned()
    364                 .ok_or(EventParseError::InvalidTag(TAG_E))
    365         })
    366         .transpose()
    367 }
    368 
    369 fn json_content<T: DeserializeOwned>(content: &str) -> Result<T, EventParseError> {
    370     serde_json::from_str(content).map_err(|_| EventParseError::InvalidJson("content"))
    371 }
    372 
    373 fn parsed<T>(event: EventEnvelope, data: T) -> RadrootsParsedEvent<T> {
    374     let parsed_data = RadrootsParsedData::new(
    375         event.id_hex(),
    376         event.author().to_hex().to_string(),
    377         event.created_at_u64(),
    378         event.kind_u32(),
    379         data,
    380     );
    381     RadrootsParsedEvent::new(event, parsed_data)
    382 }
    383 
    384 fn require_contract_tag(
    385     tags: &[Vec<String>],
    386     expected: &'static str,
    387 ) -> Result<(), EventParseError> {
    388     let actual = required_one_value(tags, TAG_CONTRACT)?;
    389     if actual == expected {
    390         Ok(())
    391     } else {
    392         Err(EventParseError::InvalidTag(TAG_CONTRACT))
    393     }
    394 }
    395 
    396 fn parse_validation_error(error: KnowledgeValidationError) -> EventParseError {
    397     match error.field() {
    398         "d_tag" => EventParseError::InvalidTag(TAG_D),
    399         "references" => EventParseError::InvalidTag(TAG_SOURCE),
    400         "forked_from" | "deferred_to" | "wiki_redirect.target" | "target_article" => {
    401             EventParseError::InvalidTag(TAG_A)
    402         }
    403         "destination_pubkey" => EventParseError::InvalidTag(TAG_P),
    404         "base_version_event_id" | "source_version_event_id" => EventParseError::InvalidTag(TAG_E),
    405         field => EventParseError::InvalidJson(field),
    406     }
    407 }
    408 
    409 fn reject_private_coordinate_keys(content: &str) -> Result<(), EventParseError> {
    410     let value: serde_json::Value =
    411         serde_json::from_str(content).map_err(|_| EventParseError::InvalidJson("content"))?;
    412     if contains_private_coordinate_key(&value) {
    413         Err(EventParseError::InvalidJson("content"))
    414     } else {
    415         Ok(())
    416     }
    417 }
    418 
    419 fn contains_private_coordinate_key(value: &serde_json::Value) -> bool {
    420     match value {
    421         serde_json::Value::Object(object) => object.iter().any(|(key, value)| {
    422             let lower = key.as_str();
    423             matches!(
    424                 lower,
    425                 "latitude"
    426                     | "longitude"
    427                     | "lat"
    428                     | "lon"
    429                     | "lng"
    430                     | "coordinates"
    431                     | "exact_coordinates"
    432             ) || contains_private_coordinate_key(value)
    433         }),
    434         serde_json::Value::Array(values) => values.iter().any(contains_private_coordinate_key),
    435         _ => false,
    436     }
    437 }
    438 
    439 pub fn wiki_article_from_event(
    440     event: EventEnvelope,
    441 ) -> Result<RadrootsParsedEvent<WikiArticle>, EventParseError> {
    442     ensure_kind(event.kind_u32(), KIND_WIKI_ARTICLE, "wiki article")?;
    443     let d_tag = required_one_value(&event.tags_as_vec(), TAG_D)?;
    444     validate_wiki_d_tag(&d_tag).map_err(|_| EventParseError::InvalidTag(TAG_D))?;
    445     let title = optional_one_value(&event.tags_as_vec(), TAG_TITLE)?;
    446     let summary = optional_one_value(&event.tags_as_vec(), TAG_SUMMARY)?;
    447     let topics = values(&event.tags_as_vec(), TAG_T);
    448     let references = event_refs(&event.tags_as_vec(), TAG_SOURCE)?;
    449     let forked_from = wiki_version_refs(&event.tags_as_vec(), MARKER_FORK)?;
    450     let mut deferred_refs = wiki_version_refs(&event.tags_as_vec(), MARKER_DEFER)?;
    451     if deferred_refs.len() > 1 {
    452         return Err(EventParseError::InvalidTag(TAG_A));
    453     }
    454     let deferred_to = deferred_refs.pop();
    455     let article = WikiArticle {
    456         d_tag,
    457         title,
    458         content_djot: event.content().to_string(),
    459         summary,
    460         topics,
    461         references,
    462         forked_from,
    463         deferred_to,
    464     };
    465     validate_wiki_article(&article).map_err(parse_validation_error)?;
    466     Ok(parsed(event, article))
    467 }
    468 
    469 pub fn wiki_redirect_from_event(
    470     event: EventEnvelope,
    471 ) -> Result<RadrootsParsedEvent<WikiRedirect>, EventParseError> {
    472     ensure_kind(event.kind_u32(), KIND_WIKI_REDIRECT, "wiki redirect")?;
    473     if !event.content().is_empty() {
    474         return Err(EventParseError::InvalidJson("content"));
    475     }
    476     let d_tag = required_one_value(&event.tags_as_vec(), TAG_D)?;
    477     validate_wiki_d_tag(&d_tag).map_err(|_| EventParseError::InvalidTag(TAG_D))?;
    478     let target = address_from_a_tag(&event.tags_as_vec(), TAG_A)?;
    479     if target.kind != KIND_WIKI_ARTICLE {
    480         return Err(EventParseError::InvalidTag(TAG_A));
    481     }
    482     let redirect = WikiRedirect { d_tag, target };
    483     validate_wiki_redirect(&redirect).map_err(parse_validation_error)?;
    484     Ok(parsed(event, redirect))
    485 }
    486 
    487 pub fn wiki_merge_request_from_event(
    488     event: EventEnvelope,
    489 ) -> Result<RadrootsParsedEvent<WikiMergeRequest>, EventParseError> {
    490     ensure_kind(
    491         event.kind_u32(),
    492         KIND_WIKI_MERGE_REQUEST,
    493         "wiki merge request",
    494     )?;
    495     let target_article = address_from_a_tag(&event.tags_as_vec(), TAG_A)?;
    496     if target_article.kind != KIND_WIKI_ARTICLE {
    497         return Err(EventParseError::InvalidTag(TAG_A));
    498     }
    499     let destination_pubkey = required_one_value(&event.tags_as_vec(), TAG_P)?;
    500     let base_version_event_id = wiki_merge_base_event_id(&event.tags_as_vec())?;
    501     let source_version_event_id = wiki_merge_source_event_id(&event.tags_as_vec())?;
    502     let explanation = if event.content().is_empty() {
    503         None
    504     } else {
    505         Some(event.content().to_string())
    506     };
    507     let request = WikiMergeRequest {
    508         target_article,
    509         destination_pubkey,
    510         base_version_event_id,
    511         source_version_event_id,
    512         explanation,
    513     };
    514     validate_wiki_merge_request(&request).map_err(parse_validation_error)?;
    515     Ok(parsed(event, request))
    516 }
    517 
    518 pub fn knowledge_source_from_event(
    519     event: EventEnvelope,
    520 ) -> Result<RadrootsParsedEvent<KnowledgeSource>, EventParseError> {
    521     ensure_kind(event.kind_u32(), KIND_KNOWLEDGE_SOURCE, "knowledge source")?;
    522     require_contract_tag(&event.tags_as_vec(), RADROOTS_KNOWLEDGE_SOURCE_SCHEMA)?;
    523     let source: KnowledgeSource = json_content(event.content())?;
    524     let d_tag = required_one_value(&event.tags_as_vec(), TAG_D)?;
    525     if d_tag != source.d_tag {
    526         return Err(EventParseError::InvalidTag(TAG_D));
    527     }
    528     validate_knowledge_source(&source).map_err(parse_validation_error)?;
    529     let expected_tags =
    530         knowledge_source_build_tags(&source).map_err(|_| EventParseError::InvalidTag(TAG_D))?;
    531     ensure_mirrored_tags(
    532         &event.tags_as_vec(),
    533         &expected_tags,
    534         &[TAG_D, TAG_T, TAG_SOURCE],
    535     )?;
    536     Ok(parsed(event, source))
    537 }
    538 
    539 pub fn evidence_bounty_from_event(
    540     event: EventEnvelope,
    541 ) -> Result<RadrootsParsedEvent<EvidenceBounty>, EventParseError> {
    542     ensure_kind(event.kind_u32(), KIND_EVIDENCE_BOUNTY, "evidence bounty")?;
    543     require_contract_tag(&event.tags_as_vec(), RADROOTS_EVIDENCE_BOUNTY_SCHEMA)?;
    544     let bounty: EvidenceBounty = json_content(event.content())?;
    545     let d_tag = required_one_value(&event.tags_as_vec(), TAG_D)?;
    546     if d_tag != bounty.d_tag {
    547         return Err(EventParseError::InvalidTag(TAG_D));
    548     }
    549     validate_evidence_bounty(&bounty).map_err(parse_validation_error)?;
    550     let expected_tags =
    551         evidence_bounty_build_tags(&bounty).map_err(|_| EventParseError::InvalidTag(TAG_D))?;
    552     ensure_mirrored_tags(
    553         &event.tags_as_vec(),
    554         &expected_tags,
    555         &[TAG_D, TAG_T, TAG_EVIDENCE],
    556     )?;
    557     Ok(parsed(event, bounty))
    558 }
    559 
    560 pub fn knowledge_claim_from_event(
    561     event: EventEnvelope,
    562 ) -> Result<RadrootsParsedEvent<KnowledgeClaim>, EventParseError> {
    563     ensure_kind(event.kind_u32(), KIND_KNOWLEDGE_CLAIM, "knowledge claim")?;
    564     require_contract_tag(&event.tags_as_vec(), RADROOTS_KNOWLEDGE_CLAIM_SCHEMA)?;
    565     let claim: KnowledgeClaim = json_content(event.content())?;
    566     validate_knowledge_claim(&claim).map_err(parse_validation_error)?;
    567     let expected_tags =
    568         knowledge_claim_build_tags(&claim).map_err(|_| EventParseError::InvalidTag(TAG_SOURCE))?;
    569     ensure_mirrored_tags(
    570         &event.tags_as_vec(),
    571         &expected_tags,
    572         &[TAG_T, TAG_SOURCE, TAG_CITATION],
    573     )?;
    574     Ok(parsed(event, claim))
    575 }
    576 
    577 pub fn knowledge_relation_from_event(
    578     event: EventEnvelope,
    579 ) -> Result<RadrootsParsedEvent<KnowledgeRelation>, EventParseError> {
    580     ensure_kind(
    581         event.kind_u32(),
    582         KIND_KNOWLEDGE_RELATION,
    583         "knowledge relation",
    584     )?;
    585     require_contract_tag(&event.tags_as_vec(), RADROOTS_KNOWLEDGE_RELATION_SCHEMA)?;
    586     let relation: KnowledgeRelation = json_content(event.content())?;
    587     validate_knowledge_relation(&relation).map_err(parse_validation_error)?;
    588     let expected_tags = knowledge_relation_build_tags(&relation)
    589         .map_err(|_| EventParseError::InvalidTag(TAG_SOURCE))?;
    590     ensure_mirrored_tags(&event.tags_as_vec(), &expected_tags, &[TAG_SOURCE])?;
    591     Ok(parsed(event, relation))
    592 }
    593 
    594 pub fn knowledge_review_from_event(
    595     event: EventEnvelope,
    596 ) -> Result<RadrootsParsedEvent<KnowledgeReview>, EventParseError> {
    597     ensure_kind(event.kind_u32(), KIND_KNOWLEDGE_REVIEW, "knowledge review")?;
    598     require_contract_tag(&event.tags_as_vec(), RADROOTS_KNOWLEDGE_REVIEW_SCHEMA)?;
    599     required_one_value(&event.tags_as_vec(), TAG_REVIEW_TARGET)?;
    600     let review: KnowledgeReview = json_content(event.content())?;
    601     validate_knowledge_review(&review).map_err(parse_validation_error)?;
    602     let expected_tags = knowledge_review_build_tags(&review)
    603         .map_err(|_| EventParseError::InvalidTag(TAG_REVIEW_TARGET))?;
    604     ensure_mirrored_tags(
    605         &event.tags_as_vec(),
    606         &expected_tags,
    607         &[TAG_REVIEW_TARGET, TAG_EVIDENCE],
    608     )?;
    609     Ok(parsed(event, review))
    610 }
    611 
    612 pub fn knowledge_field_report_from_event(
    613     event: EventEnvelope,
    614 ) -> Result<RadrootsParsedEvent<KnowledgeFieldReport>, EventParseError> {
    615     ensure_kind(
    616         event.kind_u32(),
    617         KIND_KNOWLEDGE_FIELD_REPORT,
    618         "knowledge field report",
    619     )?;
    620     require_contract_tag(&event.tags_as_vec(), RADROOTS_KNOWLEDGE_FIELD_REPORT_SCHEMA)?;
    621     reject_private_coordinate_keys(event.content())?;
    622     let report: KnowledgeFieldReport = json_content(event.content())?;
    623     validate_knowledge_field_report(&report).map_err(parse_validation_error)?;
    624     let expected_tags = knowledge_field_report_build_tags(&report)
    625         .map_err(|_| EventParseError::InvalidTag(TAG_EVIDENCE))?;
    626     ensure_mirrored_tags(
    627         &event.tags_as_vec(),
    628         &expected_tags,
    629         &[TAG_T, TAG_G, TAG_EVIDENCE],
    630     )?;
    631     Ok(parsed(event, report))
    632 }
    633 
    634 pub fn knowledge_change_proposal_from_event(
    635     event: EventEnvelope,
    636 ) -> Result<RadrootsParsedEvent<KnowledgeChangeProposal>, EventParseError> {
    637     ensure_kind(
    638         event.kind_u32(),
    639         KIND_KNOWLEDGE_CHANGE_PROPOSAL,
    640         "knowledge change proposal",
    641     )?;
    642     require_contract_tag(
    643         &event.tags_as_vec(),
    644         RADROOTS_KNOWLEDGE_CHANGE_PROPOSAL_SCHEMA,
    645     )?;
    646     let proposal: KnowledgeChangeProposal = json_content(event.content())?;
    647     validate_knowledge_change_proposal(&proposal).map_err(parse_validation_error)?;
    648     let expected_tags = knowledge_change_proposal_build_tags(&proposal)
    649         .map_err(|_| EventParseError::InvalidTag(TAG_EVIDENCE))?;
    650     ensure_mirrored_tags(&event.tags_as_vec(), &expected_tags, &[TAG_EVIDENCE])?;
    651     Ok(parsed(event, proposal))
    652 }
    653 
    654 pub fn contribution_attestation_from_event(
    655     event: EventEnvelope,
    656 ) -> Result<RadrootsParsedEvent<ContributionAttestation>, EventParseError> {
    657     ensure_kind(
    658         event.kind_u32(),
    659         KIND_CONTRIBUTION_ATTESTATION,
    660         "contribution attestation",
    661     )?;
    662     require_contract_tag(
    663         &event.tags_as_vec(),
    664         RADROOTS_CONTRIBUTION_ATTESTATION_SCHEMA,
    665     )?;
    666     let attestation: ContributionAttestation = json_content(event.content())?;
    667     validate_contribution_attestation(&attestation).map_err(parse_validation_error)?;
    668     let expected_tags = contribution_attestation_build_tags(&attestation)
    669         .map_err(|_| EventParseError::InvalidTag(TAG_EVIDENCE))?;
    670     ensure_mirrored_tags(&event.tags_as_vec(), &expected_tags, &[TAG_EVIDENCE])?;
    671     Ok(parsed(event, attestation))
    672 }