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 }