structured_decode.rs (30743B)
1 #![cfg(feature = "serde_json")] 2 3 #[path = "../src/test_fixtures.rs"] 4 mod test_fixtures; 5 6 use radroots_core::{RadrootsCoreDecimal, RadrootsCoreQuantity, RadrootsCoreUnit}; 7 use radroots_events::coop::RadrootsCoop; 8 use radroots_events::document::{RadrootsDocument, RadrootsDocumentSubject}; 9 use radroots_events::farm::{ 10 RadrootsFarm, RadrootsFarmRef, RadrootsGcsLocation, RadrootsGeoJsonPoint, 11 RadrootsGeoJsonPolygon, 12 }; 13 use radroots_events::kinds::{ 14 KIND_COOP, KIND_DOCUMENT, KIND_FARM, KIND_PLOT, KIND_RESOURCE_AREA, KIND_RESOURCE_HARVEST_CAP, 15 }; 16 use radroots_events::plot::RadrootsPlot; 17 use radroots_events::resource_area::{ 18 RadrootsResourceArea, RadrootsResourceAreaLocation, RadrootsResourceAreaRef, 19 }; 20 use radroots_events::resource_cap::{RadrootsResourceHarvestCap, RadrootsResourceHarvestProduct}; 21 use radroots_events::tags::TAG_D; 22 use radroots_events_codec::coop::decode::{ 23 coop_from_event, data_from_event as coop_metadata_from_event, 24 parsed_from_event as coop_index_from_event, 25 }; 26 use radroots_events_codec::document::decode::{ 27 data_from_event as document_metadata_from_event, document_from_event, 28 parsed_from_event as document_index_from_event, 29 }; 30 use radroots_events_codec::error::EventParseError; 31 use radroots_events_codec::farm::decode::{ 32 data_from_event as farm_metadata_from_event, farm_from_event, 33 parsed_from_event as farm_index_from_event, 34 }; 35 use radroots_events_codec::parsed::{RadrootsParsedData, RadrootsParsedEvent}; 36 use radroots_events_codec::plot::decode::{ 37 data_from_event as plot_metadata_from_event, parsed_from_event as plot_index_from_event, 38 plot_from_event, 39 }; 40 use radroots_events_codec::resource_area::decode::{ 41 data_from_event as resource_area_metadata_from_event, 42 parsed_from_event as resource_area_index_from_event, resource_area_from_event, 43 }; 44 use radroots_events_codec::resource_cap::decode::{ 45 data_from_event as resource_cap_metadata_from_event, 46 parsed_from_event as resource_cap_index_from_event, resource_harvest_cap_from_event, 47 }; 48 use test_fixtures::{FIXTURE_ALICE_NPUB, FIXTURE_ALICE_PUBLIC_KEY_HEX}; 49 50 const TEST_NPUB: &str = FIXTURE_ALICE_NPUB; 51 const TEST_PUBKEY_HEX: &str = FIXTURE_ALICE_PUBLIC_KEY_HEX; 52 53 fn sample_gcs() -> RadrootsGcsLocation { 54 RadrootsGcsLocation { 55 lat: 37.0, 56 lng: -122.0, 57 geohash: "9q8yy".to_string(), 58 point: RadrootsGeoJsonPoint { 59 r#type: "Point".to_string(), 60 coordinates: [-122.0, 37.0], 61 }, 62 polygon: RadrootsGeoJsonPolygon { 63 r#type: "Polygon".to_string(), 64 coordinates: vec![vec![ 65 [-122.0, 37.0], 66 [-122.0, 37.0001], 67 [-122.0001, 37.0001], 68 [-122.0, 37.0], 69 ]], 70 }, 71 accuracy: None, 72 altitude: None, 73 tag_0: None, 74 label: None, 75 area: None, 76 elevation: None, 77 soil: None, 78 climate: None, 79 gc_id: None, 80 gc_name: None, 81 gc_admin1_id: None, 82 gc_admin1_name: None, 83 gc_country_id: None, 84 gc_country_name: None, 85 } 86 } 87 88 fn sample_farm(d_tag: &str) -> RadrootsFarm { 89 RadrootsFarm { 90 d_tag: d_tag.to_string(), 91 name: "Farm".to_string(), 92 about: None, 93 website: None, 94 picture: None, 95 banner: None, 96 location: None, 97 tags: None, 98 } 99 } 100 101 fn sample_coop(d_tag: &str) -> RadrootsCoop { 102 RadrootsCoop { 103 d_tag: d_tag.to_string(), 104 name: "Coop".to_string(), 105 about: None, 106 website: None, 107 picture: None, 108 banner: None, 109 location: None, 110 tags: None, 111 } 112 } 113 114 fn sample_plot(d_tag: &str, farm_pubkey: &str, farm_d_tag: &str) -> RadrootsPlot { 115 RadrootsPlot { 116 d_tag: d_tag.to_string(), 117 farm: RadrootsFarmRef { 118 pubkey: farm_pubkey.to_string(), 119 d_tag: farm_d_tag.to_string(), 120 }, 121 name: "Plot".to_string(), 122 about: None, 123 location: None, 124 tags: None, 125 } 126 } 127 128 fn sample_document( 129 d_tag: &str, 130 subject_pubkey: &str, 131 subject_address: Option<&str>, 132 ) -> RadrootsDocument { 133 RadrootsDocument { 134 d_tag: d_tag.to_string(), 135 doc_type: "charter".to_string(), 136 title: "Charter".to_string(), 137 version: "1.0.0".to_string(), 138 summary: None, 139 effective_at: None, 140 body_markdown: None, 141 subject: RadrootsDocumentSubject { 142 pubkey: subject_pubkey.to_string(), 143 address: subject_address.map(str::to_string), 144 }, 145 tags: None, 146 } 147 } 148 149 fn sample_resource_area(d_tag: &str) -> RadrootsResourceArea { 150 RadrootsResourceArea { 151 d_tag: d_tag.to_string(), 152 name: "Area".to_string(), 153 about: None, 154 location: RadrootsResourceAreaLocation { 155 primary: None, 156 city: None, 157 region: None, 158 country: None, 159 gcs: sample_gcs(), 160 }, 161 tags: None, 162 } 163 } 164 165 fn sample_resource_cap(d_tag: &str) -> RadrootsResourceHarvestCap { 166 RadrootsResourceHarvestCap { 167 d_tag: d_tag.to_string(), 168 resource_area: RadrootsResourceAreaRef { 169 pubkey: TEST_PUBKEY_HEX.to_string(), 170 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 171 }, 172 product: RadrootsResourceHarvestProduct { 173 key: "nutmeg".to_string(), 174 category: Some("spice".to_string()), 175 }, 176 start: 100, 177 end: 200, 178 cap_quantity: RadrootsCoreQuantity::new( 179 RadrootsCoreDecimal::from(1000u32), 180 RadrootsCoreUnit::MassG, 181 ), 182 display_amount: None, 183 display_unit: None, 184 display_label: None, 185 tags: None, 186 } 187 } 188 189 fn d_tag_tags(d_tag: &str) -> Vec<Vec<String>> { 190 vec![vec![TAG_D.to_string(), d_tag.to_string()]] 191 } 192 193 #[test] 194 fn farm_decode_handles_success_fill_and_error_paths() { 195 let d_tag = "AAAAAAAAAAAAAAAAAAAAAA"; 196 let tags = d_tag_tags(d_tag); 197 let farm = sample_farm(d_tag); 198 let content = serde_json::to_string(&farm).expect("farm content"); 199 let parsed = farm_from_event(KIND_FARM, &tags, &content).expect("farm parse"); 200 assert_eq!(parsed.d_tag, d_tag); 201 202 let mut farm_missing = sample_farm(""); 203 let content_missing = serde_json::to_string(&farm_missing).expect("farm missing content"); 204 let filled = farm_from_event(KIND_FARM, &tags, &content_missing).expect("farm fill d"); 205 assert_eq!(filled.d_tag, d_tag); 206 207 farm_missing.d_tag = "AAAAAAAAAAAAAAAAAAAAAQ".to_string(); 208 let mismatch_content = serde_json::to_string(&farm_missing).expect("farm mismatch content"); 209 let mismatch = farm_from_event(KIND_FARM, &tags, &mismatch_content).expect_err("mismatch"); 210 assert!(matches!(mismatch, EventParseError::InvalidTag("d"))); 211 212 let wrong_kind = farm_from_event(KIND_COOP, &tags, &content).expect_err("wrong kind"); 213 assert!(matches!( 214 wrong_kind, 215 EventParseError::InvalidKind { 216 expected: "30340", 217 got: KIND_COOP 218 } 219 )); 220 221 let missing_d = farm_from_event(KIND_FARM, &[], &content).expect_err("missing d"); 222 assert!(matches!(missing_d, EventParseError::MissingTag("d"))); 223 224 let invalid_d = farm_from_event( 225 KIND_FARM, 226 &[vec![TAG_D.to_string(), "farm:invalid".to_string()]], 227 &content, 228 ) 229 .expect_err("invalid d"); 230 assert!(matches!(invalid_d, EventParseError::InvalidTag("d"))); 231 232 let invalid_json = farm_from_event(KIND_FARM, &tags, "").expect_err("invalid content"); 233 assert!(matches!( 234 invalid_json, 235 EventParseError::InvalidJson("content") 236 )); 237 238 let private_field_content = serde_json::json!({ 239 "d_tag": d_tag, 240 "name": "Farm", 241 "workspace": {"pubkey": TEST_PUBKEY_HEX} 242 }) 243 .to_string(); 244 let private_field = 245 farm_from_event(KIND_FARM, &tags, &private_field_content).expect_err("private field"); 246 assert!(matches!( 247 private_field, 248 EventParseError::InvalidJson("content") 249 )); 250 251 let non_object = farm_from_event(KIND_FARM, &tags, "[]").expect_err("non object"); 252 assert!(matches!( 253 non_object, 254 EventParseError::InvalidJson("content") 255 )); 256 } 257 258 #[test] 259 fn farm_metadata_and_index_decode_roundtrip() { 260 let d_tag = "AAAAAAAAAAAAAAAAAAAAAA"; 261 let content = serde_json::to_string(&sample_farm(d_tag)).expect("farm content"); 262 let tags = d_tag_tags(d_tag); 263 let metadata: RadrootsParsedData<RadrootsFarm> = farm_metadata_from_event( 264 "id1".to_string(), 265 TEST_PUBKEY_HEX.to_string(), 266 55, 267 KIND_FARM, 268 content.clone(), 269 tags.clone(), 270 ) 271 .expect("farm metadata"); 272 assert_eq!(metadata.id, "id1"); 273 assert_eq!(metadata.data.d_tag, d_tag); 274 275 let index: RadrootsParsedEvent<RadrootsFarm> = farm_index_from_event( 276 "id1".to_string(), 277 TEST_PUBKEY_HEX.to_string(), 278 55, 279 KIND_FARM, 280 content, 281 tags, 282 "sig1".to_string(), 283 ) 284 .expect("farm index"); 285 assert_eq!(index.event.id, "id1"); 286 assert_eq!(index.data.data.d_tag, d_tag); 287 } 288 289 #[test] 290 fn coop_decode_handles_success_fill_and_error_paths() { 291 let d_tag = "BAAAAAAAAAAAAAAAAAAAAA"; 292 let tags = d_tag_tags(d_tag); 293 let coop = sample_coop(d_tag); 294 let content = serde_json::to_string(&coop).expect("coop content"); 295 let parsed = coop_from_event(KIND_COOP, &tags, &content).expect("coop parse"); 296 assert_eq!(parsed.d_tag, d_tag); 297 298 let content_missing = serde_json::to_string(&sample_coop("")).expect("coop missing content"); 299 let filled = coop_from_event(KIND_COOP, &tags, &content_missing).expect("coop fill d"); 300 assert_eq!(filled.d_tag, d_tag); 301 302 let mismatch_content = 303 serde_json::to_string(&sample_coop("AAAAAAAAAAAAAAAAAAAAAQ")).expect("coop mismatch"); 304 let mismatch = coop_from_event(KIND_COOP, &tags, &mismatch_content).expect_err("mismatch"); 305 assert!(matches!(mismatch, EventParseError::InvalidTag("d"))); 306 307 let wrong_kind = coop_from_event(KIND_FARM, &tags, &content).expect_err("wrong kind"); 308 assert!(matches!( 309 wrong_kind, 310 EventParseError::InvalidKind { 311 expected: "30360", 312 got: KIND_FARM 313 } 314 )); 315 316 let missing_d = coop_from_event(KIND_COOP, &[], &content).expect_err("missing d"); 317 assert!(matches!(missing_d, EventParseError::MissingTag("d"))); 318 } 319 320 #[test] 321 fn coop_metadata_and_index_decode_roundtrip() { 322 let d_tag = "BAAAAAAAAAAAAAAAAAAAAA"; 323 let content = serde_json::to_string(&sample_coop(d_tag)).expect("coop content"); 324 let tags = d_tag_tags(d_tag); 325 let metadata: RadrootsParsedData<RadrootsCoop> = coop_metadata_from_event( 326 "id2".to_string(), 327 TEST_PUBKEY_HEX.to_string(), 328 56, 329 KIND_COOP, 330 content.clone(), 331 tags.clone(), 332 ) 333 .expect("coop metadata"); 334 assert_eq!(metadata.id, "id2"); 335 assert_eq!(metadata.data.d_tag, d_tag); 336 337 let index: RadrootsParsedEvent<RadrootsCoop> = coop_index_from_event( 338 "id2".to_string(), 339 TEST_PUBKEY_HEX.to_string(), 340 56, 341 KIND_COOP, 342 content, 343 tags, 344 "sig2".to_string(), 345 ) 346 .expect("coop index"); 347 assert_eq!(index.event.kind, KIND_COOP); 348 assert_eq!(index.data.data.d_tag, d_tag); 349 } 350 351 #[test] 352 fn plot_decode_handles_success_fill_and_tag_error_paths() { 353 let d_tag = "AAAAAAAAAAAAAAAAAAAAAQ"; 354 let farm_d_tag = "AAAAAAAAAAAAAAAAAAAAAA"; 355 let tags = vec![ 356 vec![TAG_D.to_string(), d_tag.to_string()], 357 vec![ 358 "a".to_string(), 359 format!("30340:{TEST_PUBKEY_HEX}:{farm_d_tag}"), 360 ], 361 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 362 ]; 363 364 let content = serde_json::to_string(&sample_plot(d_tag, TEST_PUBKEY_HEX, farm_d_tag)) 365 .expect("plot content"); 366 let parsed = plot_from_event(KIND_PLOT, &tags, &content).expect("plot parse"); 367 assert_eq!(parsed.farm.pubkey, TEST_PUBKEY_HEX); 368 369 let filled_content = 370 serde_json::to_string(&sample_plot("", "", "")).expect("plot missing content"); 371 let filled = plot_from_event(KIND_PLOT, &tags, &filled_content).expect("plot fill"); 372 assert_eq!(filled.d_tag, d_tag); 373 assert_eq!(filled.farm.d_tag, farm_d_tag); 374 375 let partial_farm_content = 376 serde_json::to_string(&sample_plot(d_tag, TEST_PUBKEY_HEX, "")).expect("plot partial farm"); 377 let partial_farm = 378 plot_from_event(KIND_PLOT, &tags, &partial_farm_content).expect("partial farm fill"); 379 assert_eq!(partial_farm.farm.d_tag, farm_d_tag); 380 381 let bad_a = plot_from_event( 382 KIND_PLOT, 383 &[ 384 vec![TAG_D.to_string(), d_tag.to_string()], 385 vec![ 386 "a".to_string(), 387 format!("30361:{TEST_PUBKEY_HEX}:AAAAAAAAAAAAAAAAAAAAAA"), 388 ], 389 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 390 ], 391 &content, 392 ) 393 .expect_err("bad a"); 394 assert!(matches!(bad_a, EventParseError::InvalidTag("a"))); 395 396 let bad_p = plot_from_event( 397 KIND_PLOT, 398 &[ 399 vec![TAG_D.to_string(), d_tag.to_string()], 400 vec![ 401 "a".to_string(), 402 format!("30340:{TEST_PUBKEY_HEX}:{farm_d_tag}"), 403 ], 404 vec!["p".to_string(), TEST_NPUB.to_string()], 405 ], 406 &content, 407 ) 408 .expect_err("bad p"); 409 assert!(matches!(bad_p, EventParseError::InvalidTag("p"))); 410 411 let missing_a = plot_from_event( 412 KIND_PLOT, 413 &[ 414 vec![TAG_D.to_string(), d_tag.to_string()], 415 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 416 ], 417 &content, 418 ) 419 .expect_err("missing a"); 420 assert!(matches!(missing_a, EventParseError::MissingTag("a"))); 421 422 let invalid_kind = plot_from_event(KIND_FARM, &tags, &content).expect_err("invalid kind"); 423 assert!(matches!( 424 invalid_kind, 425 EventParseError::InvalidKind { 426 expected: "30350", 427 got: KIND_FARM 428 } 429 )); 430 431 let blank_content = plot_from_event(KIND_PLOT, &tags, " ").expect_err("blank content"); 432 assert!(matches!( 433 blank_content, 434 EventParseError::InvalidJson("content") 435 )); 436 437 let empty_d = plot_from_event( 438 KIND_PLOT, 439 &[ 440 vec![TAG_D.to_string(), " ".to_string()], 441 vec![ 442 "a".to_string(), 443 format!("30340:{TEST_PUBKEY_HEX}:{farm_d_tag}"), 444 ], 445 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 446 ], 447 &content, 448 ) 449 .expect_err("empty d"); 450 assert!(matches!(empty_d, EventParseError::InvalidTag("d"))); 451 452 let empty_a_pubkey = plot_from_event( 453 KIND_PLOT, 454 &[ 455 vec![TAG_D.to_string(), d_tag.to_string()], 456 vec!["a".to_string(), format!("30340::{farm_d_tag}")], 457 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 458 ], 459 &content, 460 ) 461 .expect_err("empty a pubkey"); 462 assert!(matches!(empty_a_pubkey, EventParseError::InvalidTag("a"))); 463 464 let empty_a_d_tag = plot_from_event( 465 KIND_PLOT, 466 &[ 467 vec![TAG_D.to_string(), d_tag.to_string()], 468 vec!["a".to_string(), format!("30340:{TEST_PUBKEY_HEX}:")], 469 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 470 ], 471 &content, 472 ) 473 .expect_err("empty a d_tag"); 474 assert!(matches!(empty_a_d_tag, EventParseError::InvalidTag("a"))); 475 476 let empty_p = plot_from_event( 477 KIND_PLOT, 478 &[ 479 vec![TAG_D.to_string(), d_tag.to_string()], 480 vec![ 481 "a".to_string(), 482 format!("30340:{TEST_PUBKEY_HEX}:{farm_d_tag}"), 483 ], 484 vec!["p".to_string(), " ".to_string()], 485 ], 486 &content, 487 ) 488 .expect_err("empty p"); 489 assert!(matches!(empty_p, EventParseError::InvalidTag("p"))); 490 491 let mismatched_d_content = serde_json::to_string(&sample_plot( 492 "BAAAAAAAAAAAAAAAAAAAAA", 493 TEST_PUBKEY_HEX, 494 farm_d_tag, 495 )) 496 .expect("mismatched d content"); 497 let mismatched_d = 498 plot_from_event(KIND_PLOT, &tags, &mismatched_d_content).expect_err("mismatched d"); 499 assert!(matches!(mismatched_d, EventParseError::InvalidTag("d"))); 500 501 let mismatched_farm_pubkey_content = 502 serde_json::to_string(&sample_plot(d_tag, TEST_NPUB, farm_d_tag)) 503 .expect("mismatched farm pubkey content"); 504 let mismatched_farm_pubkey = plot_from_event(KIND_PLOT, &tags, &mismatched_farm_pubkey_content) 505 .expect_err("mismatched farm pubkey"); 506 assert!(matches!( 507 mismatched_farm_pubkey, 508 EventParseError::InvalidTag("a") 509 )); 510 511 let mismatched_farm_d_tag_content = serde_json::to_string(&sample_plot( 512 d_tag, 513 TEST_PUBKEY_HEX, 514 "BAAAAAAAAAAAAAAAAAAAAA", 515 )) 516 .expect("mismatched farm d_tag content"); 517 let mismatched_farm_d_tag = plot_from_event(KIND_PLOT, &tags, &mismatched_farm_d_tag_content) 518 .expect_err("mismatched farm d_tag"); 519 assert!(matches!( 520 mismatched_farm_d_tag, 521 EventParseError::InvalidTag("a") 522 )); 523 524 let parsed_err = plot_index_from_event( 525 "id".to_string(), 526 TEST_PUBKEY_HEX.to_string(), 527 57, 528 KIND_FARM, 529 content, 530 tags, 531 "sig".to_string(), 532 ) 533 .expect_err("parsed error"); 534 assert!(matches!( 535 parsed_err, 536 EventParseError::InvalidKind { 537 expected: "30350", 538 got: KIND_FARM 539 } 540 )); 541 } 542 543 #[test] 544 fn plot_metadata_and_index_decode_roundtrip() { 545 let d_tag = "AAAAAAAAAAAAAAAAAAAAAQ"; 546 let farm_d_tag = "AAAAAAAAAAAAAAAAAAAAAA"; 547 let tags = vec![ 548 vec![TAG_D.to_string(), d_tag.to_string()], 549 vec![ 550 "a".to_string(), 551 format!("30340:{TEST_PUBKEY_HEX}:{farm_d_tag}"), 552 ], 553 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 554 ]; 555 let content = serde_json::to_string(&sample_plot(d_tag, TEST_PUBKEY_HEX, farm_d_tag)) 556 .expect("plot content"); 557 558 let metadata: RadrootsParsedData<RadrootsPlot> = plot_metadata_from_event( 559 "id3".to_string(), 560 TEST_PUBKEY_HEX.to_string(), 561 57, 562 KIND_PLOT, 563 content.clone(), 564 tags.clone(), 565 ) 566 .expect("plot metadata"); 567 assert_eq!(metadata.data.d_tag, d_tag); 568 569 let index: RadrootsParsedEvent<RadrootsPlot> = plot_index_from_event( 570 "id3".to_string(), 571 TEST_PUBKEY_HEX.to_string(), 572 57, 573 KIND_PLOT, 574 content, 575 tags, 576 "sig3".to_string(), 577 ) 578 .expect("plot index"); 579 assert_eq!(index.event.author, TEST_PUBKEY_HEX); 580 assert_eq!(index.data.data.d_tag, d_tag); 581 } 582 583 #[test] 584 fn document_decode_handles_subject_and_address_paths() { 585 let d_tag = "EAAAAAAAAAAAAAAAAAAAAA"; 586 let tag_address = format!("30360:{TEST_PUBKEY_HEX}:BAAAAAAAAAAAAAAAAAAAAA"); 587 let tags = vec![ 588 vec![TAG_D.to_string(), d_tag.to_string()], 589 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 590 vec!["a".to_string(), tag_address.clone()], 591 ]; 592 let content = 593 serde_json::to_string(&sample_document(d_tag, TEST_PUBKEY_HEX, Some(&tag_address))) 594 .expect("document content"); 595 let parsed = document_from_event(KIND_DOCUMENT, &tags, &content).expect("document parse"); 596 assert_eq!(parsed.subject.pubkey, TEST_PUBKEY_HEX); 597 assert_eq!( 598 parsed.subject.address.as_deref(), 599 Some(tag_address.as_str()) 600 ); 601 602 let fill_content = serde_json::to_string(&sample_document("", "", None)).expect("fill"); 603 let filled = document_from_event(KIND_DOCUMENT, &tags, &fill_content).expect("document fill"); 604 assert_eq!(filled.d_tag, d_tag); 605 assert_eq!(filled.subject.pubkey, TEST_PUBKEY_HEX); 606 assert_eq!( 607 filled.subject.address.as_deref(), 608 Some(tag_address.as_str()) 609 ); 610 611 let missing_a_err = document_from_event( 612 KIND_DOCUMENT, 613 &[ 614 vec![TAG_D.to_string(), d_tag.to_string()], 615 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 616 ], 617 &content, 618 ) 619 .expect_err("missing a"); 620 assert!(matches!(missing_a_err, EventParseError::MissingTag("a"))); 621 622 let mismatch_p_content = 623 serde_json::to_string(&sample_document(d_tag, TEST_NPUB, Some(&tag_address))) 624 .expect("mismatch p content"); 625 let mismatch_p = 626 document_from_event(KIND_DOCUMENT, &tags, &mismatch_p_content).expect_err("mismatch p"); 627 assert!(matches!(mismatch_p, EventParseError::InvalidTag("p"))); 628 629 let empty_a_content = 630 serde_json::to_string(&sample_document(d_tag, TEST_PUBKEY_HEX, Some(""))).expect("empty a"); 631 let empty_a = 632 document_from_event(KIND_DOCUMENT, &tags, &empty_a_content).expect_err("empty address"); 633 assert!(matches!(empty_a, EventParseError::InvalidTag("a"))); 634 635 let invalid_kind = document_from_event(KIND_FARM, &tags, &content).expect_err("invalid kind"); 636 assert!(matches!( 637 invalid_kind, 638 EventParseError::InvalidKind { 639 expected: "30361", 640 got: KIND_FARM 641 } 642 )); 643 644 let blank_content = document_from_event(KIND_DOCUMENT, &tags, " ").expect_err("blank content"); 645 assert!(matches!( 646 blank_content, 647 EventParseError::InvalidJson("content") 648 )); 649 650 let empty_d_tag = document_from_event( 651 KIND_DOCUMENT, 652 &[ 653 vec![TAG_D.to_string(), " ".to_string()], 654 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 655 vec!["a".to_string(), tag_address.clone()], 656 ], 657 &content, 658 ) 659 .expect_err("empty d"); 660 assert!(matches!(empty_d_tag, EventParseError::InvalidTag("d"))); 661 662 let empty_p_tag = document_from_event( 663 KIND_DOCUMENT, 664 &[ 665 vec![TAG_D.to_string(), d_tag.to_string()], 666 vec!["p".to_string(), " ".to_string()], 667 vec!["a".to_string(), tag_address.clone()], 668 ], 669 &content, 670 ) 671 .expect_err("empty p"); 672 assert!(matches!(empty_p_tag, EventParseError::InvalidTag("p"))); 673 674 let empty_a_tag = document_from_event( 675 KIND_DOCUMENT, 676 &[ 677 vec![TAG_D.to_string(), d_tag.to_string()], 678 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 679 vec!["a".to_string(), " ".to_string()], 680 ], 681 &content, 682 ) 683 .expect_err("empty a tag"); 684 assert!(matches!(empty_a_tag, EventParseError::InvalidTag("a"))); 685 686 let mismatched_d_content = serde_json::to_string(&sample_document( 687 "FAAAAAAAAAAAAAAAAAAAAA", 688 TEST_PUBKEY_HEX, 689 None, 690 )) 691 .expect("mismatched d content"); 692 let mismatched_d = 693 document_from_event(KIND_DOCUMENT, &tags, &mismatched_d_content).expect_err("mismatched d"); 694 assert!(matches!(mismatched_d, EventParseError::InvalidTag("d"))); 695 696 let mismatched_a_content = serde_json::to_string(&sample_document( 697 d_tag, 698 TEST_PUBKEY_HEX, 699 Some("30360:author:other"), 700 )) 701 .expect("mismatched address content"); 702 let mismatched_a = 703 document_from_event(KIND_DOCUMENT, &tags, &mismatched_a_content).expect_err("mismatched a"); 704 assert!(matches!(mismatched_a, EventParseError::InvalidTag("a"))); 705 706 let parsed_err = document_index_from_event( 707 "id".to_string(), 708 TEST_PUBKEY_HEX.to_string(), 709 58, 710 KIND_FARM, 711 content, 712 tags, 713 "sig".to_string(), 714 ) 715 .expect_err("parsed error"); 716 assert!(matches!( 717 parsed_err, 718 EventParseError::InvalidKind { 719 expected: "30361", 720 got: KIND_FARM 721 } 722 )); 723 } 724 725 #[test] 726 fn document_metadata_and_index_decode_roundtrip() { 727 let d_tag = "EAAAAAAAAAAAAAAAAAAAAA"; 728 let tag_address = format!("30360:{TEST_PUBKEY_HEX}:BAAAAAAAAAAAAAAAAAAAAA"); 729 let tags = vec![ 730 vec![TAG_D.to_string(), d_tag.to_string()], 731 vec!["p".to_string(), TEST_PUBKEY_HEX.to_string()], 732 vec!["a".to_string(), tag_address.clone()], 733 ]; 734 let content = 735 serde_json::to_string(&sample_document(d_tag, TEST_PUBKEY_HEX, Some(&tag_address))) 736 .expect("document content"); 737 738 let metadata: RadrootsParsedData<RadrootsDocument> = document_metadata_from_event( 739 "id4".to_string(), 740 TEST_PUBKEY_HEX.to_string(), 741 58, 742 KIND_DOCUMENT, 743 content.clone(), 744 tags.clone(), 745 ) 746 .expect("document metadata"); 747 assert_eq!(metadata.data.d_tag, d_tag); 748 749 let index: RadrootsParsedEvent<RadrootsDocument> = document_index_from_event( 750 "id4".to_string(), 751 TEST_PUBKEY_HEX.to_string(), 752 58, 753 KIND_DOCUMENT, 754 content, 755 tags, 756 "sig4".to_string(), 757 ) 758 .expect("document index"); 759 assert_eq!(index.event.kind, KIND_DOCUMENT); 760 assert_eq!(index.data.data.d_tag, d_tag); 761 } 762 763 #[test] 764 fn resource_area_decode_handles_success_fill_and_errors() { 765 let d_tag = "AAAAAAAAAAAAAAAAAAAAAw"; 766 let tags = d_tag_tags(d_tag); 767 let area = sample_resource_area(d_tag); 768 let content = serde_json::to_string(&area).expect("area content"); 769 let parsed = resource_area_from_event(KIND_RESOURCE_AREA, &tags, &content).expect("area"); 770 assert_eq!(parsed.d_tag, d_tag); 771 772 let fill_content = serde_json::to_string(&sample_resource_area("")).expect("area fill"); 773 let filled = 774 resource_area_from_event(KIND_RESOURCE_AREA, &tags, &fill_content).expect("area fill"); 775 assert_eq!(filled.d_tag, d_tag); 776 777 let mismatch_content = 778 serde_json::to_string(&sample_resource_area("AAAAAAAAAAAAAAAAAAAAAQ")).expect("mismatch"); 779 let mismatch = 780 resource_area_from_event(KIND_RESOURCE_AREA, &tags, &mismatch_content).expect_err("m"); 781 assert!(matches!(mismatch, EventParseError::InvalidTag("d"))); 782 783 let wrong_kind = resource_area_from_event(KIND_FARM, &tags, &content).expect_err("wrong kind"); 784 assert!(matches!( 785 wrong_kind, 786 EventParseError::InvalidKind { 787 expected: "30370", 788 got: KIND_FARM 789 } 790 )); 791 792 let blank_content = 793 resource_area_from_event(KIND_RESOURCE_AREA, &tags, " ").expect_err("blank content"); 794 assert!(matches!( 795 blank_content, 796 EventParseError::InvalidJson("content") 797 )); 798 799 let missing_d = 800 resource_area_from_event(KIND_RESOURCE_AREA, &[], &content).expect_err("missing d"); 801 assert!(matches!(missing_d, EventParseError::MissingTag("d"))); 802 803 let invalid_d = resource_area_from_event( 804 KIND_RESOURCE_AREA, 805 &[vec![TAG_D.to_string(), " ".to_string()]], 806 &content, 807 ) 808 .expect_err("invalid d"); 809 assert!(matches!(invalid_d, EventParseError::InvalidTag("d"))); 810 } 811 812 #[test] 813 fn resource_area_metadata_and_index_decode_roundtrip() { 814 let d_tag = "AAAAAAAAAAAAAAAAAAAAAw"; 815 let content = serde_json::to_string(&sample_resource_area(d_tag)).expect("area content"); 816 let tags = d_tag_tags(d_tag); 817 let metadata: RadrootsParsedData<RadrootsResourceArea> = resource_area_metadata_from_event( 818 "id5".to_string(), 819 TEST_PUBKEY_HEX.to_string(), 820 59, 821 KIND_RESOURCE_AREA, 822 content.clone(), 823 tags.clone(), 824 ) 825 .expect("area metadata"); 826 assert_eq!(metadata.data.d_tag, d_tag); 827 828 let index: RadrootsParsedEvent<RadrootsResourceArea> = resource_area_index_from_event( 829 "id5".to_string(), 830 TEST_PUBKEY_HEX.to_string(), 831 59, 832 KIND_RESOURCE_AREA, 833 content, 834 tags, 835 "sig5".to_string(), 836 ) 837 .expect("area index"); 838 assert_eq!(index.event.id, "id5"); 839 assert_eq!(index.data.data.d_tag, d_tag); 840 } 841 842 #[test] 843 fn resource_cap_decode_handles_success_fill_and_errors() { 844 let d_tag = "DAAAAAAAAAAAAAAAAAAAAA"; 845 let tags = d_tag_tags(d_tag); 846 let cap = sample_resource_cap(d_tag); 847 let content = serde_json::to_string(&cap).expect("cap content"); 848 let parsed = resource_harvest_cap_from_event(KIND_RESOURCE_HARVEST_CAP, &tags, &content) 849 .expect("cap parse"); 850 assert_eq!(parsed.d_tag, d_tag); 851 852 let fill_content = serde_json::to_string(&sample_resource_cap("")).expect("cap fill"); 853 let filled = resource_harvest_cap_from_event(KIND_RESOURCE_HARVEST_CAP, &tags, &fill_content) 854 .expect("cap fill parse"); 855 assert_eq!(filled.d_tag, d_tag); 856 857 let mismatch_content = 858 serde_json::to_string(&sample_resource_cap("AAAAAAAAAAAAAAAAAAAAAQ")).expect("mismatch"); 859 let mismatch = 860 resource_harvest_cap_from_event(KIND_RESOURCE_HARVEST_CAP, &tags, &mismatch_content) 861 .expect_err("cap mismatch"); 862 assert!(matches!(mismatch, EventParseError::InvalidTag("d"))); 863 864 let wrong_kind = 865 resource_harvest_cap_from_event(KIND_FARM, &tags, &content).expect_err("wrong kind"); 866 assert!(matches!( 867 wrong_kind, 868 EventParseError::InvalidKind { 869 expected: "30371", 870 got: KIND_FARM 871 } 872 )); 873 874 let blank_content = resource_harvest_cap_from_event(KIND_RESOURCE_HARVEST_CAP, &tags, " ") 875 .expect_err("blank content"); 876 assert!(matches!( 877 blank_content, 878 EventParseError::InvalidJson("content") 879 )); 880 881 let missing_d = resource_harvest_cap_from_event(KIND_RESOURCE_HARVEST_CAP, &[], &content) 882 .expect_err("missing d"); 883 assert!(matches!(missing_d, EventParseError::MissingTag("d"))); 884 885 let invalid_d = resource_harvest_cap_from_event( 886 KIND_RESOURCE_HARVEST_CAP, 887 &[vec![TAG_D.to_string(), " ".to_string()]], 888 &content, 889 ) 890 .expect_err("invalid d"); 891 assert!(matches!(invalid_d, EventParseError::InvalidTag("d"))); 892 } 893 894 #[test] 895 fn resource_cap_metadata_and_index_decode_roundtrip() { 896 let d_tag = "DAAAAAAAAAAAAAAAAAAAAA"; 897 let content = serde_json::to_string(&sample_resource_cap(d_tag)).expect("cap content"); 898 let tags = d_tag_tags(d_tag); 899 let metadata: RadrootsParsedData<RadrootsResourceHarvestCap> = 900 resource_cap_metadata_from_event( 901 "id6".to_string(), 902 TEST_PUBKEY_HEX.to_string(), 903 60, 904 KIND_RESOURCE_HARVEST_CAP, 905 content.clone(), 906 tags.clone(), 907 ) 908 .expect("cap metadata"); 909 assert_eq!(metadata.data.d_tag, d_tag); 910 911 let index: RadrootsParsedEvent<RadrootsResourceHarvestCap> = resource_cap_index_from_event( 912 "id6".to_string(), 913 TEST_PUBKEY_HEX.to_string(), 914 60, 915 KIND_RESOURCE_HARVEST_CAP, 916 content, 917 tags, 918 "sig6".to_string(), 919 ) 920 .expect("cap index"); 921 assert_eq!(index.event.sig, "sig6"); 922 assert_eq!(index.data.data.d_tag, d_tag); 923 }