lib

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

calendar_conformance.rs (56363B)


      1 use std::{borrow::Cow, fs, path::Path};
      2 
      3 use radroots_blossom::{BlobDescriptor, ByteVerifiedDescriptor, Error};
      4 use radroots_event::{
      5     calendar::{
      6         AuthoredCalendar, AuthoredCalendarDateEvent, AuthoredCalendarEventRsvp,
      7         AuthoredCalendarTimeEvent, CalendarAdmissionError, CalendarDate,
      8         CalendarEventAuthorReference, CalendarEventError, CalendarEventFreeBusy,
      9         CalendarEventReference, CalendarEventRevisionReference, CalendarEventRsvpStatus,
     10         CalendarParticipant, CalendarRequest, CalendarUid, CalendarUri, IanaTimeZoneId,
     11         ParsedNip52Calendar, ParsedNip52CalendarCommon, ParsedNip52CalendarEventRsvp,
     12         RADROOTS_CALENDAR_MAX_COVERED_UTC_DAYS,
     13     },
     14     contract::validate_event_contract_parts,
     15     media::AuthoredImage,
     16 };
     17 use radroots_event_codec::decode::{
     18     EventParseError,
     19     calendar::{
     20         admit_radroots_calendar, admit_radroots_calendar_date_event,
     21         admit_radroots_calendar_event_rsvp, admit_radroots_calendar_time_event,
     22         parse_nip52_calendar, parse_nip52_calendar_date_event, parse_nip52_calendar_event_rsvp,
     23         parse_nip52_calendar_time_event,
     24     },
     25 };
     26 use radroots_event_codec::encode::calendar::{
     27     calendar_collection_build_tags, calendar_date_event_build_tags, calendar_time_event_build_tags,
     28     calendar_to_wire_parts, date_to_wire_parts, rsvp_build_tags, rsvp_to_wire_parts,
     29     time_to_wire_parts,
     30 };
     31 use serde_json::{Map, Value};
     32 
     33 const PACKAGED_BASELINE: &str = include_str!("fixtures/calendar_nip52_baseline.v1.json");
     34 const PACKAGED_PROFILE: &str = include_str!("fixtures/calendar_radroots_profile.v1.json");
     35 const WORKSPACE_BASELINE_PATH: &str =
     36     "../../contracts/conformance/vectors/calendar/nip52_baseline.v1.json";
     37 const WORKSPACE_PROFILE_PATH: &str =
     38     "../../contracts/conformance/vectors/calendar/radroots_profile.v1.json";
     39 const WORKSPACE_CONTRACT_MARKER_PATH: &str = "../../contracts/manifest.toml";
     40 const BASELINE_VECTOR_KIND_COUNTS: &[(&str, usize)] = &[
     41     ("calendar.baseline.collection.invalid", 15),
     42     ("calendar.baseline.collection.valid", 4),
     43     ("calendar.baseline.date.invalid", 8),
     44     ("calendar.baseline.date.valid", 3),
     45     ("calendar.baseline.rsvp.invalid", 23),
     46     ("calendar.baseline.rsvp.valid", 4),
     47     ("calendar.baseline.time.invalid", 4),
     48     ("calendar.baseline.time.valid", 4),
     49 ];
     50 const PROFILE_VECTOR_KIND_COUNTS: &[(&str, usize)] = &[
     51     ("calendar.profile.admit.collection.invalid", 6),
     52     ("calendar.profile.admit.collection.valid", 2),
     53     ("calendar.profile.admit.date.invalid", 4),
     54     ("calendar.profile.admit.date.valid", 1),
     55     ("calendar.profile.admit.rsvp.invalid", 9),
     56     ("calendar.profile.admit.rsvp.valid", 3),
     57     ("calendar.profile.admit.time.invalid", 4),
     58     ("calendar.profile.admit.time.valid", 1),
     59     ("calendar.profile.authored.collection.invalid", 6),
     60     ("calendar.profile.authored.collection.valid", 3),
     61     ("calendar.profile.authored.date.invalid", 3),
     62     ("calendar.profile.authored.date.valid", 1),
     63     ("calendar.profile.authored.rsvp.invalid", 6),
     64     ("calendar.profile.authored.rsvp.valid", 2),
     65     ("calendar.profile.authored.time.coverage.valid", 1),
     66     ("calendar.profile.authored.time.invalid", 2),
     67     ("calendar.profile.authored.time.valid", 1),
     68     ("calendar.profile.date.parse.invalid", 2),
     69     ("calendar.profile.date.parse.valid", 2),
     70 ];
     71 
     72 #[test]
     73 fn checked_in_baseline_vectors_execute_against_tolerant_nip52_parsers() {
     74     let suite = conformance_suite(WORKSPACE_BASELINE_PATH, PACKAGED_BASELINE);
     75     assert_eq!(suite["suite"], "calendar_nip52_baseline");
     76     assert_eq!(suite["contract_version"], "1.0.0");
     77     let vectors = suite["vectors"].as_array().expect("baseline vectors");
     78     assert_vector_kind_inventory(vectors, BASELINE_VECTOR_KIND_COUNTS);
     79     for vector in vectors {
     80         execute_baseline(vector);
     81     }
     82 }
     83 
     84 #[test]
     85 fn checked_in_profile_vectors_execute_against_authored_and_admission_apis() {
     86     let suite = conformance_suite(WORKSPACE_PROFILE_PATH, PACKAGED_PROFILE);
     87     assert_eq!(suite["suite"], "calendar_radroots_profile");
     88     assert_eq!(suite["contract_version"], "1.0.0");
     89     let vectors = suite["vectors"].as_array().expect("profile vectors");
     90     assert_vector_kind_inventory(vectors, PROFILE_VECTOR_KIND_COUNTS);
     91     for vector in vectors {
     92         execute_profile(vector);
     93     }
     94 }
     95 
     96 fn assert_vector_kind_inventory(vectors: &[Value], expected: &[(&str, usize)]) {
     97     assert_eq!(
     98         vectors.len(),
     99         expected.iter().map(|(_, count)| count).sum::<usize>(),
    100         "Calendar vector inventory count drifted"
    101     );
    102     for (kind, expected_count) in expected {
    103         let actual_count = vectors
    104             .iter()
    105             .filter(|vector| vector_kind(vector) == *kind)
    106             .count();
    107         assert_eq!(
    108             actual_count, *expected_count,
    109             "Calendar vector kind {kind} inventory drifted"
    110         );
    111     }
    112 }
    113 
    114 fn conformance_suite(workspace_relative_path: &str, packaged: &'static str) -> Value {
    115     let workspace_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(workspace_relative_path);
    116     let vectors = match fs::read_to_string(&workspace_path) {
    117         Ok(canonical) => {
    118             assert_eq!(
    119                 canonical,
    120                 packaged,
    121                 "packaged Calendar vectors must match {}",
    122                 workspace_path.display()
    123             );
    124             Cow::Owned(canonical)
    125         }
    126         Err(error)
    127             if error.kind() == std::io::ErrorKind::NotFound
    128                 && !Path::new(env!("CARGO_MANIFEST_DIR"))
    129                     .join(WORKSPACE_CONTRACT_MARKER_PATH)
    130                     .is_file() =>
    131         {
    132             Cow::Borrowed(packaged)
    133         }
    134         Err(error) => panic!("failed to read {}: {error}", workspace_path.display()),
    135     };
    136     serde_json::from_str(&vectors).expect("Calendar vectors must parse")
    137 }
    138 
    139 fn execute_baseline(vector: &Value) {
    140     match vector_kind(vector) {
    141         "calendar.baseline.date.valid" => baseline_date_valid(vector),
    142         "calendar.baseline.date.invalid" => baseline_date_invalid(vector),
    143         "calendar.baseline.time.valid" => baseline_time_valid(vector),
    144         "calendar.baseline.time.invalid" => baseline_time_invalid(vector),
    145         "calendar.baseline.collection.valid" => baseline_collection_valid(vector),
    146         "calendar.baseline.collection.invalid" => baseline_collection_invalid(vector),
    147         "calendar.baseline.rsvp.valid" => baseline_rsvp_valid(vector),
    148         "calendar.baseline.rsvp.invalid" => baseline_rsvp_invalid(vector),
    149         kind => panic!(
    150             "{} uses unsupported baseline kind {kind}",
    151             vector_id(vector)
    152         ),
    153     }
    154 }
    155 
    156 fn execute_profile(vector: &Value) {
    157     match vector_kind(vector) {
    158         "calendar.profile.date.parse.valid" => profile_date_parse_valid(vector),
    159         "calendar.profile.date.parse.invalid" => profile_date_parse_invalid(vector),
    160         "calendar.profile.authored.date.valid" => profile_authored_date_valid(vector),
    161         "calendar.profile.authored.date.invalid" => profile_authored_date_invalid(vector),
    162         "calendar.profile.authored.time.valid" => profile_authored_time_valid(vector),
    163         "calendar.profile.authored.time.coverage.valid" => {
    164             profile_authored_time_coverage_valid(vector)
    165         }
    166         "calendar.profile.authored.time.invalid" => profile_authored_time_invalid(vector),
    167         "calendar.profile.authored.collection.valid" => profile_authored_collection_valid(vector),
    168         "calendar.profile.authored.collection.invalid" => {
    169             profile_authored_collection_invalid(vector)
    170         }
    171         "calendar.profile.authored.rsvp.valid" => profile_authored_rsvp_valid(vector),
    172         "calendar.profile.authored.rsvp.invalid" => profile_authored_rsvp_invalid(vector),
    173         "calendar.profile.admit.date.valid" => profile_admit_date_valid(vector),
    174         "calendar.profile.admit.date.invalid" => profile_admit_date_invalid(vector),
    175         "calendar.profile.admit.time.valid" => profile_admit_time_valid(vector),
    176         "calendar.profile.admit.time.invalid" => profile_admit_time_invalid(vector),
    177         "calendar.profile.admit.collection.valid" => profile_admit_collection_valid(vector),
    178         "calendar.profile.admit.collection.invalid" => profile_admit_collection_invalid(vector),
    179         "calendar.profile.admit.rsvp.valid" => profile_admit_rsvp_valid(vector),
    180         "calendar.profile.admit.rsvp.invalid" => profile_admit_rsvp_invalid(vector),
    181         kind => panic!("{} uses unsupported profile kind {kind}", vector_id(vector)),
    182     }
    183 }
    184 
    185 fn baseline_date_valid(vector: &Value) {
    186     let (kind, tags, content) = inbound_parts(vector);
    187     let parsed = parse_nip52_calendar_date_event(kind, &tags, content)
    188         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    189     let expected = expected(vector);
    190     assert_common(parsed.common(), expected, vector);
    191     assert_eq!(parsed.start().as_str(), value_str(expected, "start"));
    192     assert_optional_str(
    193         parsed.end().map(CalendarDate::as_str),
    194         expected,
    195         "end",
    196         vector,
    197     );
    198     assert_eq!(
    199         serde_json::to_value(parsed.extension_day_tags()).unwrap(),
    200         expected["date_day_extensions"],
    201         "{}",
    202         vector_id(vector)
    203     );
    204 }
    205 
    206 fn baseline_time_valid(vector: &Value) {
    207     let (kind, tags, content) = inbound_parts(vector);
    208     let parsed = parse_nip52_calendar_time_event(kind, &tags, content)
    209         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    210     let expected = expected(vector);
    211     assert_common(parsed.common(), expected, vector);
    212     assert_eq!(parsed.start_wire(), value_str(expected, "start_wire"));
    213     assert_eq!(parsed.start(), value_u64(expected, "start"));
    214     assert_optional_str(parsed.end_wire(), expected, "end_wire", vector);
    215     assert_eq!(
    216         parsed.end(),
    217         expected.get("end").and_then(Value::as_u64),
    218         "{}",
    219         vector_id(vector)
    220     );
    221     let actual_days = parsed
    222         .observed_day_indices()
    223         .iter()
    224         .map(|day| {
    225             serde_json::json!({
    226                 "wire": day.wire_value(),
    227                 "index": day.index(),
    228                 "canonical": day.is_canonical(),
    229             })
    230         })
    231         .collect::<Vec<_>>();
    232     assert_eq!(
    233         Value::Array(actual_days),
    234         expected["observed_days"],
    235         "{}",
    236         vector_id(vector)
    237     );
    238     assert_optional_str(
    239         parsed.start_tzid().map(IanaTimeZoneId::as_str),
    240         expected,
    241         "start_tzid",
    242         vector,
    243     );
    244     assert_optional_str(
    245         parsed.end_tzid().map(IanaTimeZoneId::as_str),
    246         expected,
    247         "end_tzid",
    248         vector,
    249     );
    250     assert_optional_str(
    251         parsed.effective_end_tzid().map(IanaTimeZoneId::as_str),
    252         expected,
    253         "effective_end_tzid",
    254         vector,
    255     );
    256 }
    257 
    258 fn baseline_date_invalid(vector: &Value) {
    259     let (kind, tags, content) = inbound_parts(vector);
    260     let error = parse_nip52_calendar_date_event(kind, &tags, content)
    261         .expect_err("invalid baseline date vector must fail");
    262     assert_parse_error(vector, &error);
    263 }
    264 
    265 fn baseline_time_invalid(vector: &Value) {
    266     let (kind, tags, content) = inbound_parts(vector);
    267     let error = parse_nip52_calendar_time_event(kind, &tags, content)
    268         .expect_err("invalid baseline time vector must fail");
    269     assert_parse_error(vector, &error);
    270 }
    271 
    272 fn baseline_collection_valid(vector: &Value) {
    273     let (kind, tags, content) = inbound_parts(vector);
    274     let parsed = parse_nip52_calendar(kind, &tags, content)
    275         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    276     assert_parsed_collection(&parsed, expected(vector), vector);
    277 }
    278 
    279 fn baseline_collection_invalid(vector: &Value) {
    280     let (kind, tags, content) = inbound_parts(vector);
    281     let error = parse_nip52_calendar(kind, &tags, content)
    282         .expect_err("invalid baseline collection vector must fail");
    283     assert_parse_error(vector, &error);
    284 }
    285 
    286 fn baseline_rsvp_valid(vector: &Value) {
    287     let (kind, tags, content) = inbound_parts(vector);
    288     let parsed = parse_nip52_calendar_event_rsvp(kind, &tags, content)
    289         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    290     assert_parsed_rsvp(&parsed, expected(vector), vector);
    291 }
    292 
    293 fn baseline_rsvp_invalid(vector: &Value) {
    294     let (kind, tags, content) = inbound_parts(vector);
    295     let error = parse_nip52_calendar_event_rsvp(kind, &tags, content)
    296         .expect_err("invalid baseline RSVP vector must fail");
    297     assert_parse_error(vector, &error);
    298 }
    299 
    300 fn assert_parsed_collection(parsed: &ParsedNip52Calendar, expected: &Value, vector: &Value) {
    301     assert_eq!(
    302         parsed.d_tag(),
    303         value_str(expected, "d"),
    304         "{}",
    305         vector_id(vector)
    306     );
    307     assert_eq!(
    308         parsed.title(),
    309         value_str(expected, "title"),
    310         "{}",
    311         vector_id(vector)
    312     );
    313     assert_eq!(
    314         parsed.content(),
    315         value_str(expected, "content"),
    316         "{}",
    317         vector_id(vector)
    318     );
    319     assert_optional_str(
    320         parsed.list_description(),
    321         expected,
    322         "list_description",
    323         vector,
    324     );
    325     assert_optional_str(
    326         parsed.image().map(CalendarUri::as_str),
    327         expected,
    328         "image",
    329         vector,
    330     );
    331     assert_event_references(
    332         parsed.event_references(),
    333         expected,
    334         "event_references",
    335         vector,
    336     );
    337 }
    338 
    339 fn assert_parsed_rsvp(parsed: &ParsedNip52CalendarEventRsvp, expected: &Value, vector: &Value) {
    340     assert_eq!(
    341         parsed.d_tag(),
    342         value_str(expected, "d"),
    343         "{}",
    344         vector_id(vector)
    345     );
    346     assert_event_reference(
    347         parsed.event_reference(),
    348         &expected["event_reference"],
    349         vector,
    350     );
    351     assert_revision_reference(parsed.revision_reference(), expected, vector);
    352     assert_eq!(
    353         rsvp_status_str(parsed.status()),
    354         value_str(expected, "status"),
    355         "{}",
    356         vector_id(vector)
    357     );
    358     assert_optional_str(
    359         parsed.observed_free_busy().map(free_busy_str),
    360         expected,
    361         "observed_free_busy",
    362         vector,
    363     );
    364     assert_optional_str(
    365         parsed.effective_free_busy().map(free_busy_str),
    366         expected,
    367         "effective_free_busy",
    368         vector,
    369     );
    370     assert_author_reference(parsed.author_hint(), expected, vector);
    371     assert_optional_str(parsed.note(), expected, "note", vector);
    372 }
    373 
    374 fn assert_common(common: &ParsedNip52CalendarCommon, expected: &Value, vector: &Value) {
    375     assert_eq!(
    376         common.d_tag(),
    377         value_str(expected, "d"),
    378         "{}",
    379         vector_id(vector)
    380     );
    381     assert_eq!(
    382         common.title(),
    383         value_str(expected, "title"),
    384         "{}",
    385         vector_id(vector)
    386     );
    387     assert_optional_str(common.description(), expected, "description", vector);
    388     assert_eq!(
    389         serde_json::to_value(common.locations()).unwrap(),
    390         expected["locations"],
    391         "{}",
    392         vector_id(vector)
    393     );
    394     assert_optional_str(common.geohash(), expected, "geohash", vector);
    395     assert_optional_str(common.summary(), expected, "summary", vector);
    396     assert_optional_str(
    397         common.image().map(CalendarUri::as_str),
    398         expected,
    399         "image",
    400         vector,
    401     );
    402     assert_participants(common, expected, vector);
    403     assert_eq!(
    404         serde_json::to_value(common.categories()).unwrap(),
    405         expected["categories"],
    406         "{}",
    407         vector_id(vector)
    408     );
    409     assert_eq!(
    410         common
    411             .references()
    412             .iter()
    413             .map(CalendarUri::as_str)
    414             .collect::<Vec<_>>(),
    415         expected["references"]
    416             .as_array()
    417             .expect("expected references")
    418             .iter()
    419             .map(|value| value.as_str().expect("reference"))
    420             .collect::<Vec<_>>(),
    421         "{}",
    422         vector_id(vector)
    423     );
    424     assert_calendar_requests(common, expected, vector);
    425     assert_optional_str(common.legacy_name(), expected, "legacy_name", vector);
    426 }
    427 
    428 fn assert_participants(common: &ParsedNip52CalendarCommon, expected: &Value, vector: &Value) {
    429     if let Some(count) = expected["participants"].as_u64() {
    430         assert_eq!(
    431             common.participants().len() as u64,
    432             count,
    433             "{}",
    434             vector_id(vector)
    435         );
    436         return;
    437     }
    438     let actual = common
    439         .participants()
    440         .iter()
    441         .map(|participant| {
    442             serde_json::json!({
    443                 "pubkey": participant.pubkey,
    444                 "relay": participant.relay,
    445                 "role": participant.role,
    446             })
    447         })
    448         .collect::<Vec<_>>();
    449     assert_eq!(
    450         Value::Array(actual),
    451         expected["participants"],
    452         "{}",
    453         vector_id(vector)
    454     );
    455 }
    456 
    457 fn assert_calendar_requests(common: &ParsedNip52CalendarCommon, expected: &Value, vector: &Value) {
    458     if let Some(count) = expected["calendar_requests"].as_u64() {
    459         assert_eq!(
    460             common.calendar_requests().len() as u64,
    461             count,
    462             "{}",
    463             vector_id(vector)
    464         );
    465         return;
    466     }
    467     let actual = common
    468         .calendar_requests()
    469         .iter()
    470         .map(|request| {
    471             serde_json::json!({
    472                 "calendar": request.calendar().as_str(),
    473                 "relay": request.relay(),
    474             })
    475         })
    476         .collect::<Vec<_>>();
    477     assert_eq!(
    478         Value::Array(actual),
    479         expected["calendar_requests"],
    480         "{}",
    481         vector_id(vector)
    482     );
    483 }
    484 
    485 fn assert_event_references(
    486     actual: &[CalendarEventReference],
    487     expected: &Value,
    488     key: &str,
    489     vector: &Value,
    490 ) {
    491     let expected = expected[key]
    492         .as_array()
    493         .unwrap_or_else(|| panic!("{} expected.{key} must be an array", vector_id(vector)));
    494     assert_eq!(actual.len(), expected.len(), "{}", vector_id(vector));
    495     for (actual, expected) in actual.iter().zip(expected) {
    496         assert_event_reference(actual, expected, vector);
    497     }
    498 }
    499 
    500 fn assert_event_reference(actual: &CalendarEventReference, expected: &Value, vector: &Value) {
    501     let coordinate = value_str(expected, "coordinate");
    502     let relay = expected.get("relay").and_then(Value::as_str);
    503     assert_eq!(
    504         actual.coordinate().as_str(),
    505         coordinate,
    506         "{}",
    507         vector_id(vector)
    508     );
    509     assert_eq!(actual.relay(), relay, "{}", vector_id(vector));
    510 
    511     let parts = radroots_event::id::AddressableCoordinateParts::parse(coordinate)
    512         .expect("expected event reference coordinate");
    513     let canonical_coordinate = format!("{}:{}:{}", parts.kind, parts.pubkey, parts.d_tag);
    514     let canonical_relay =
    515         relay.is_none_or(|relay| radroots_event::id::RelayUrl::parse(relay).is_ok());
    516     assert_eq!(
    517         actual.is_canonical(),
    518         coordinate == canonical_coordinate && canonical_relay,
    519         "{} reference canonicality",
    520         vector_id(vector)
    521     );
    522 }
    523 
    524 fn assert_revision_reference(
    525     actual: Option<&CalendarEventRevisionReference>,
    526     expected: &Value,
    527     vector: &Value,
    528 ) {
    529     let Some(expected) = expected
    530         .get("revision_reference")
    531         .and_then(Value::as_object)
    532     else {
    533         assert!(actual.is_none(), "{} revision reference", vector_id(vector));
    534         return;
    535     };
    536     let actual = actual.unwrap_or_else(|| panic!("{} revision reference", vector_id(vector)));
    537     let event_id = map_str(expected, "id");
    538     let relay = expected.get("relay").and_then(Value::as_str);
    539     assert_eq!(actual.raw_event_id(), event_id, "{}", vector_id(vector));
    540     assert_eq!(
    541         actual.event_id().to_hex(),
    542         event_id.to_ascii_lowercase(),
    543         "{} normalized revision id",
    544         vector_id(vector)
    545     );
    546     assert_eq!(actual.relay(), relay, "{}", vector_id(vector));
    547     let canonical_relay =
    548         relay.is_none_or(|relay| radroots_event::id::RelayUrl::parse(relay).is_ok());
    549     assert_eq!(
    550         actual.is_canonical(),
    551         event_id == actual.event_id().to_hex() && canonical_relay,
    552         "{} revision canonicality",
    553         vector_id(vector)
    554     );
    555 }
    556 
    557 fn assert_author_reference(
    558     actual: Option<&CalendarEventAuthorReference>,
    559     expected: &Value,
    560     vector: &Value,
    561 ) {
    562     let Some(expected) = expected.get("author_hint").and_then(Value::as_object) else {
    563         assert!(actual.is_none(), "{} author hint", vector_id(vector));
    564         return;
    565     };
    566     let actual = actual.unwrap_or_else(|| panic!("{} author hint", vector_id(vector)));
    567     let pubkey = map_str(expected, "pubkey");
    568     let relay = expected.get("relay").and_then(Value::as_str);
    569     assert_eq!(actual.raw_pubkey(), pubkey, "{}", vector_id(vector));
    570     assert_eq!(
    571         actual.pubkey().to_hex(),
    572         pubkey.to_ascii_lowercase(),
    573         "{} normalized author key",
    574         vector_id(vector)
    575     );
    576     assert_eq!(actual.relay(), relay, "{}", vector_id(vector));
    577     let canonical_relay =
    578         relay.is_none_or(|relay| radroots_event::id::RelayUrl::parse(relay).is_ok());
    579     assert_eq!(
    580         actual.is_canonical(),
    581         pubkey == actual.pubkey().to_hex() && canonical_relay,
    582         "{} author canonicality",
    583         vector_id(vector)
    584     );
    585 }
    586 
    587 fn rsvp_status_str(status: &CalendarEventRsvpStatus) -> &'static str {
    588     match status {
    589         CalendarEventRsvpStatus::Accepted => "accepted",
    590         CalendarEventRsvpStatus::Declined => "declined",
    591         CalendarEventRsvpStatus::Tentative => "tentative",
    592     }
    593 }
    594 
    595 fn free_busy_str(free_busy: &CalendarEventFreeBusy) -> &'static str {
    596     match free_busy {
    597         CalendarEventFreeBusy::Free => "free",
    598         CalendarEventFreeBusy::Busy => "busy",
    599     }
    600 }
    601 
    602 fn profile_date_parse_valid(vector: &Value) {
    603     let parsed = CalendarDate::parse(input_str(vector, "value"))
    604         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    605     assert_eq!(parsed.as_str(), expected_str(vector, "value"));
    606 }
    607 
    608 fn profile_date_parse_invalid(vector: &Value) {
    609     let error = CalendarDate::parse(input_str(vector, "value"))
    610         .expect_err("invalid profile date must fail");
    611     assert_calendar_event_error(vector, &error);
    612 }
    613 
    614 fn profile_authored_date_valid(vector: &Value) {
    615     let event = authored_date_event(input(vector), vector_id(vector))
    616         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    617     let wire = date_to_wire_parts(&event)
    618         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    619     assert_eq!(calendar_date_event_build_tags(&event).unwrap(), wire.tags);
    620     assert!(
    621         wire.tags
    622             .iter()
    623             .all(|tag| tag.first().map(String::as_str) != Some("D")),
    624         "{}",
    625         vector_id(vector)
    626     );
    627     assert_wire_parts(&wire, &expected(vector)["wire_parts"], vector_id(vector));
    628     assert_authored_media_claims(vector, event.image().is_some());
    629     assert_registry_accepts(
    630         wire.kind,
    631         &wire.tags,
    632         &wire.content,
    633         "radroots.calendar.date_event.v1",
    634         vector,
    635     );
    636 
    637     let parsed = parse_nip52_calendar_date_event(wire.kind, &wire.tags, &wire.content).unwrap();
    638     admit_radroots_calendar_date_event(parsed)
    639         .unwrap_or_else(|error| panic!("{} admission failed: {error}", vector_id(vector)));
    640 }
    641 
    642 fn profile_authored_date_invalid(vector: &Value) {
    643     let error = authored_date_event(input(vector), vector_id(vector))
    644         .expect_err("invalid authored date event must fail");
    645     assert_calendar_event_error(vector, &error);
    646 }
    647 
    648 fn profile_authored_time_valid(vector: &Value) {
    649     let event = authored_time_event(input(vector), vector_id(vector))
    650         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    651     assert_optional_str(
    652         event.effective_end_tzid().map(IanaTimeZoneId::as_str),
    653         expected(vector),
    654         "effective_end_tzid",
    655         vector,
    656     );
    657     let wire = time_to_wire_parts(&event)
    658         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    659     assert_eq!(calendar_time_event_build_tags(&event).unwrap(), wire.tags);
    660     assert_wire_parts(&wire, &expected(vector)["wire_parts"], vector_id(vector));
    661     assert_authored_media_claims(vector, event.image().is_some());
    662     assert_registry_accepts(
    663         wire.kind,
    664         &wire.tags,
    665         &wire.content,
    666         "radroots.calendar.time_event.v1",
    667         vector,
    668     );
    669 
    670     let parsed = parse_nip52_calendar_time_event(wire.kind, &wire.tags, &wire.content).unwrap();
    671     admit_radroots_calendar_time_event(parsed)
    672         .unwrap_or_else(|error| panic!("{} admission failed: {error}", vector_id(vector)));
    673 }
    674 
    675 fn profile_authored_time_coverage_valid(vector: &Value) {
    676     let event = authored_time_event(input(vector), vector_id(vector))
    677         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    678     let wire = time_to_wire_parts(&event).unwrap();
    679     assert_registry_accepts(
    680         wire.kind,
    681         &wire.tags,
    682         &wire.content,
    683         "radroots.calendar.time_event.v1",
    684         vector,
    685     );
    686     let covered = wire
    687         .tags
    688         .iter()
    689         .filter(|tag| tag.first().map(String::as_str) == Some("D"))
    690         .collect::<Vec<_>>();
    691     assert_eq!(
    692         covered.len() as u64,
    693         expected_u64(vector, "count"),
    694         "{}",
    695         vector_id(vector)
    696     );
    697     assert_eq!(
    698         covered.len(),
    699         RADROOTS_CALENDAR_MAX_COVERED_UTC_DAYS as usize
    700     );
    701     assert_eq!(covered.first().unwrap()[1], expected_str(vector, "first"));
    702     assert_eq!(covered.last().unwrap()[1], expected_str(vector, "last"));
    703     for (index, tag) in covered.iter().enumerate() {
    704         assert_eq!(tag[0], "D", "{}", vector_id(vector));
    705         assert_eq!(tag[1], index.to_string(), "{}", vector_id(vector));
    706     }
    707 }
    708 
    709 fn profile_authored_time_invalid(vector: &Value) {
    710     let error = authored_time_event(input(vector), vector_id(vector))
    711         .expect_err("invalid authored time event must fail");
    712     assert_calendar_event_error(vector, &error);
    713 }
    714 
    715 fn profile_authored_collection_valid(vector: &Value) {
    716     let calendar = authored_collection(input(vector), vector_id(vector))
    717         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    718     let wire = calendar_to_wire_parts(&calendar)
    719         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    720     assert_eq!(
    721         calendar_collection_build_tags(&calendar).unwrap(),
    722         wire.tags,
    723         "{}",
    724         vector_id(vector)
    725     );
    726     assert_wire_parts(&wire, &expected(vector)["wire_parts"], vector_id(vector));
    727     assert_authored_media_claims(vector, calendar.image().is_some());
    728     assert_registry_accepts(
    729         wire.kind,
    730         &wire.tags,
    731         &wire.content,
    732         "radroots.calendar.collection.v1",
    733         vector,
    734     );
    735 
    736     let parsed = parse_nip52_calendar(wire.kind, &wire.tags, &wire.content).unwrap();
    737     assert_eq!(
    738         parsed.d_tag(),
    739         calendar.uid().as_str(),
    740         "{}",
    741         vector_id(vector)
    742     );
    743     assert_eq!(parsed.title(), calendar.title(), "{}", vector_id(vector));
    744     assert_eq!(
    745         parsed.content(),
    746         calendar.content(),
    747         "{}",
    748         vector_id(vector)
    749     );
    750     assert_eq!(
    751         parsed.event_references(),
    752         calendar.event_references(),
    753         "{}",
    754         vector_id(vector)
    755     );
    756     assert_eq!(
    757         parsed.list_description(),
    758         calendar.list_description(),
    759         "{}",
    760         vector_id(vector)
    761     );
    762     assert_eq!(
    763         parsed.image().map(CalendarUri::as_str),
    764         calendar
    765             .image()
    766             .map(|image| image.descriptor().url().as_str()),
    767         "{}",
    768         vector_id(vector)
    769     );
    770     assert!(
    771         parsed
    772             .event_references()
    773             .iter()
    774             .all(CalendarEventReference::is_canonical),
    775         "{}",
    776         vector_id(vector)
    777     );
    778     let admitted = admit_radroots_calendar(parsed)
    779         .unwrap_or_else(|error| panic!("{} admission failed: {error}", vector_id(vector)));
    780     assert_eq!(admitted.uid(), calendar.uid(), "{}", vector_id(vector));
    781     assert_eq!(
    782         admitted.blossom_image().is_some(),
    783         calendar.image().is_some(),
    784         "{}",
    785         vector_id(vector)
    786     );
    787 }
    788 
    789 fn profile_authored_collection_invalid(vector: &Value) {
    790     if expected_str(vector, "error") == "blob_hash_mismatch" {
    791         let image = &input(vector)["image"];
    792         let error = verified_descriptor_result(image).expect_err("invalid image must fail");
    793         assert_eq!(
    794             error.code(),
    795             expected_str(vector, "error"),
    796             "{}",
    797             vector_id(vector)
    798         );
    799         return;
    800     }
    801     let error = authored_collection(input(vector), vector_id(vector))
    802         .expect_err("invalid authored collection must fail");
    803     assert_calendar_event_error(vector, &error);
    804 }
    805 
    806 fn profile_authored_rsvp_valid(vector: &Value) {
    807     let rsvp = authored_rsvp(input(vector))
    808         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    809     let wire = rsvp_to_wire_parts(&rsvp)
    810         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    811     assert_eq!(
    812         rsvp_build_tags(&rsvp).unwrap(),
    813         wire.tags,
    814         "{}",
    815         vector_id(vector)
    816     );
    817     assert_wire_parts(&wire, &expected(vector)["wire_parts"], vector_id(vector));
    818     assert_registry_accepts(
    819         wire.kind,
    820         &wire.tags,
    821         &wire.content,
    822         "radroots.calendar.rsvp.v1",
    823         vector,
    824     );
    825     assert_optional_str(
    826         rsvp.observed_free_busy().map(free_busy_str),
    827         expected(vector),
    828         "observed_free_busy",
    829         vector,
    830     );
    831     assert_optional_str(
    832         rsvp.effective_free_busy().map(free_busy_str),
    833         expected(vector),
    834         "effective_free_busy",
    835         vector,
    836     );
    837 
    838     let parsed = parse_nip52_calendar_event_rsvp(wire.kind, &wire.tags, &wire.content).unwrap();
    839     assert_eq!(parsed.d_tag(), rsvp.uid().as_str(), "{}", vector_id(vector));
    840     assert_eq!(
    841         parsed.event_reference(),
    842         rsvp.event_reference(),
    843         "{}",
    844         vector_id(vector)
    845     );
    846     assert_eq!(
    847         parsed.revision_reference(),
    848         rsvp.revision_reference(),
    849         "{}",
    850         vector_id(vector)
    851     );
    852     assert_eq!(parsed.status(), rsvp.status(), "{}", vector_id(vector));
    853     assert_eq!(
    854         parsed.observed_free_busy(),
    855         rsvp.observed_free_busy(),
    856         "{}",
    857         vector_id(vector)
    858     );
    859     assert_eq!(
    860         parsed.effective_free_busy(),
    861         rsvp.effective_free_busy(),
    862         "{}",
    863         vector_id(vector)
    864     );
    865     assert_eq!(
    866         parsed.author_hint(),
    867         rsvp.author_hint(),
    868         "{}",
    869         vector_id(vector)
    870     );
    871     assert_eq!(parsed.note(), rsvp.note(), "{}", vector_id(vector));
    872     let admitted = admit_radroots_calendar_event_rsvp(parsed)
    873         .unwrap_or_else(|error| panic!("{} admission failed: {error}", vector_id(vector)));
    874     assert_eq!(admitted.uid(), rsvp.uid(), "{}", vector_id(vector));
    875 }
    876 
    877 fn profile_authored_rsvp_invalid(vector: &Value) {
    878     let error = authored_rsvp(input(vector)).expect_err("invalid authored RSVP must fail");
    879     assert_calendar_event_error(vector, &error);
    880 }
    881 
    882 fn profile_admit_date_valid(vector: &Value) {
    883     let (kind, tags, content) = inbound_parts(vector);
    884     let parsed = parse_nip52_calendar_date_event(kind, &tags, content).unwrap();
    885     let admitted = admit_radroots_calendar_date_event(parsed)
    886         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    887     assert_eq!(admitted.d_tag().as_str(), expected_str(vector, "d"));
    888     assert_eq!(
    889         admitted.blossom_image().is_some(),
    890         expected(vector)["blossom_image"].as_bool().unwrap()
    891     );
    892     assert_registry_accepts(
    893         kind,
    894         &tags,
    895         content,
    896         "radroots.calendar.date_event.v1",
    897         vector,
    898     );
    899 }
    900 
    901 fn profile_admit_date_invalid(vector: &Value) {
    902     let (kind, tags, content) = inbound_parts(vector);
    903     let parsed = parse_nip52_calendar_date_event(kind, &tags, content).unwrap();
    904     let error = admit_radroots_calendar_date_event(parsed)
    905         .expect_err("invalid date admission vector must fail");
    906     assert_admission_error(vector, &error);
    907     assert_registry_rejects(
    908         kind,
    909         &tags,
    910         content,
    911         "radroots.calendar.date_event.v1",
    912         vector,
    913     );
    914 }
    915 
    916 fn profile_admit_time_valid(vector: &Value) {
    917     let (kind, tags, content) = inbound_parts(vector);
    918     let parsed = parse_nip52_calendar_time_event(kind, &tags, content).unwrap();
    919     let admitted = admit_radroots_calendar_time_event(parsed)
    920         .unwrap_or_else(|error| panic!("{} failed: {error}", vector_id(vector)));
    921     assert_eq!(admitted.d_tag().as_str(), expected_str(vector, "d"));
    922     assert_eq!(
    923         serde_json::to_value(admitted.covered_utc_days()).unwrap(),
    924         expected(vector)["covered_days"]
    925     );
    926     assert_eq!(
    927         admitted.blossom_image().is_some(),
    928         expected(vector)["blossom_image"].as_bool().unwrap()
    929     );
    930     assert_registry_accepts(
    931         kind,
    932         &tags,
    933         content,
    934         "radroots.calendar.time_event.v1",
    935         vector,
    936     );
    937 }
    938 
    939 fn assert_registry_accepts(
    940     kind: u32,
    941     tags: &[Vec<String>],
    942     content: &str,
    943     contract_id: &str,
    944     vector: &Value,
    945 ) {
    946     validate_event_contract_parts(kind, tags, content, contract_id)
    947         .unwrap_or_else(|error| panic!("{} registry drift: {error:?}", vector_id(vector)));
    948 }
    949 
    950 fn assert_registry_rejects(
    951     kind: u32,
    952     tags: &[Vec<String>],
    953     content: &str,
    954     contract_id: &str,
    955     vector: &Value,
    956 ) {
    957     assert!(
    958         validate_event_contract_parts(kind, tags, content, contract_id).is_err(),
    959         "{} registry accepted an admission-invalid event",
    960         vector_id(vector)
    961     );
    962 }
    963 
    964 fn profile_admit_time_invalid(vector: &Value) {
    965     let (kind, tags, content) = inbound_parts(vector);
    966     let parsed = parse_nip52_calendar_time_event(kind, &tags, content).unwrap();
    967     let error = admit_radroots_calendar_time_event(parsed)
    968         .expect_err("invalid time admission vector must fail");
    969     assert_admission_error(vector, &error);
    970     assert_registry_rejects(
    971         kind,
    972         &tags,
    973         content,
    974         "radroots.calendar.time_event.v1",
    975         vector,
    976     );
    977 }
    978 
    979 fn profile_admit_collection_valid(vector: &Value) {
    980     let (kind, tags, content) = inbound_parts(vector);
    981     let parsed = parse_nip52_calendar(kind, &tags, content)
    982         .unwrap_or_else(|error| panic!("{} parse failed: {error}", vector_id(vector)));
    983     let admitted = admit_radroots_calendar(parsed)
    984         .unwrap_or_else(|error| panic!("{} admission failed: {error}", vector_id(vector)));
    985     let expected = expected(vector);
    986     assert_eq!(
    987         admitted.uid().as_str(),
    988         value_str(expected, "d"),
    989         "{}",
    990         vector_id(vector)
    991     );
    992     let title = tags
    993         .iter()
    994         .find(|tag| tag.first().map(String::as_str) == Some("title"))
    995         .and_then(|tag| tag.get(1))
    996         .expect("admitted collection title tag");
    997     assert_eq!(admitted.title(), title, "{}", vector_id(vector));
    998     assert_eq!(admitted.content(), content, "{}", vector_id(vector));
    999     assert_optional_str(
   1000         admitted.list_description(),
   1001         expected,
   1002         "list_description",
   1003         vector,
   1004     );
   1005     assert_event_references(
   1006         admitted.event_references(),
   1007         expected,
   1008         "event_references",
   1009         vector,
   1010     );
   1011     assert_eq!(
   1012         admitted.blossom_image().is_some(),
   1013         expected["blossom_image"].as_bool().unwrap(),
   1014         "{}",
   1015         vector_id(vector)
   1016     );
   1017     let image = tags
   1018         .iter()
   1019         .find(|tag| tag.first().map(String::as_str) == Some("image"))
   1020         .and_then(|tag| tag.get(1))
   1021         .map(String::as_str);
   1022     assert_eq!(
   1023         admitted.parsed().image().map(CalendarUri::as_str),
   1024         image,
   1025         "{}",
   1026         vector_id(vector)
   1027     );
   1028     if let Some(level) = expected.get("media_verification") {
   1029         assert_eq!(level, "structural_only", "{}", vector_id(vector));
   1030         assert!(admitted.blossom_image().is_some(), "{}", vector_id(vector));
   1031     }
   1032     assert_registry_accepts(
   1033         kind,
   1034         &tags,
   1035         content,
   1036         "radroots.calendar.collection.v1",
   1037         vector,
   1038     );
   1039 }
   1040 
   1041 fn profile_admit_collection_invalid(vector: &Value) {
   1042     let (kind, tags, content) = inbound_parts(vector);
   1043     let parsed = parse_nip52_calendar(kind, &tags, content)
   1044         .unwrap_or_else(|error| panic!("{} parse failed: {error}", vector_id(vector)));
   1045     let error =
   1046         admit_radroots_calendar(parsed).expect_err("invalid collection admission vector must fail");
   1047     assert_admission_error(vector, &error);
   1048     assert_registry_rejects(
   1049         kind,
   1050         &tags,
   1051         content,
   1052         "radroots.calendar.collection.v1",
   1053         vector,
   1054     );
   1055 }
   1056 
   1057 fn profile_admit_rsvp_valid(vector: &Value) {
   1058     let (kind, tags, content) = inbound_parts(vector);
   1059     let parsed = parse_nip52_calendar_event_rsvp(kind, &tags, content)
   1060         .unwrap_or_else(|error| panic!("{} parse failed: {error}", vector_id(vector)));
   1061     let admitted = admit_radroots_calendar_event_rsvp(parsed)
   1062         .unwrap_or_else(|error| panic!("{} admission failed: {error}", vector_id(vector)));
   1063     let expected = expected(vector);
   1064     assert_eq!(
   1065         admitted.uid().as_str(),
   1066         value_str(expected, "d"),
   1067         "{}",
   1068         vector_id(vector)
   1069     );
   1070     assert_eq!(
   1071         rsvp_status_str(admitted.status()),
   1072         value_str(expected, "status"),
   1073         "{}",
   1074         vector_id(vector)
   1075     );
   1076     assert_optional_str(
   1077         admitted.observed_free_busy().map(free_busy_str),
   1078         expected,
   1079         "observed_free_busy",
   1080         vector,
   1081     );
   1082     assert_optional_str(
   1083         admitted.effective_free_busy().map(free_busy_str),
   1084         expected,
   1085         "effective_free_busy",
   1086         vector,
   1087     );
   1088     assert_admitted_rsvp_references_against_tags(&admitted, &tags, vector);
   1089     assert_eq!(
   1090         admitted.note(),
   1091         (!content.is_empty()).then_some(content),
   1092         "{}",
   1093         vector_id(vector)
   1094     );
   1095     if expected.get("event_reference").is_some() {
   1096         assert_event_reference(
   1097             admitted.event_reference(),
   1098             &expected["event_reference"],
   1099             vector,
   1100         );
   1101     }
   1102     if expected.get("revision_reference").is_some() {
   1103         assert_revision_reference(admitted.revision_reference(), expected, vector);
   1104     }
   1105     if expected.get("author_hint").is_some() {
   1106         assert_author_reference(admitted.author_hint(), expected, vector);
   1107     }
   1108     assert_registry_accepts(kind, &tags, content, "radroots.calendar.rsvp.v1", vector);
   1109 }
   1110 
   1111 fn assert_admitted_rsvp_references_against_tags(
   1112     admitted: &radroots_event::calendar::AdmittedCalendarEventRsvp,
   1113     tags: &[Vec<String>],
   1114     vector: &Value,
   1115 ) {
   1116     let event_tag = tags
   1117         .iter()
   1118         .find(|tag| tag.first().map(String::as_str) == Some("a"))
   1119         .expect("admitted RSVP a tag");
   1120     assert_eq!(
   1121         admitted.event_reference().coordinate().as_str(),
   1122         event_tag[1],
   1123         "{}",
   1124         vector_id(vector)
   1125     );
   1126     assert_eq!(
   1127         admitted.event_reference().relay(),
   1128         event_tag.get(2).map(String::as_str),
   1129         "{}",
   1130         vector_id(vector)
   1131     );
   1132     assert!(
   1133         admitted.event_reference().is_canonical(),
   1134         "{}",
   1135         vector_id(vector)
   1136     );
   1137 
   1138     let revision_tag = tags
   1139         .iter()
   1140         .find(|tag| tag.first().map(String::as_str) == Some("e"));
   1141     match (admitted.revision_reference(), revision_tag) {
   1142         (Some(reference), Some(tag)) => {
   1143             assert_eq!(reference.raw_event_id(), tag[1], "{}", vector_id(vector));
   1144             assert_eq!(reference.relay(), tag.get(2).map(String::as_str));
   1145             assert!(reference.is_canonical(), "{}", vector_id(vector));
   1146         }
   1147         (None, None) => {}
   1148         _ => panic!("{} revision reference drift", vector_id(vector)),
   1149     }
   1150 
   1151     let author_tag = tags
   1152         .iter()
   1153         .find(|tag| tag.first().map(String::as_str) == Some("p"));
   1154     match (admitted.author_hint(), author_tag) {
   1155         (Some(reference), Some(tag)) => {
   1156             assert_eq!(reference.raw_pubkey(), tag[1], "{}", vector_id(vector));
   1157             assert_eq!(reference.relay(), tag.get(2).map(String::as_str));
   1158             assert!(reference.is_canonical(), "{}", vector_id(vector));
   1159         }
   1160         (None, None) => {}
   1161         _ => panic!("{} author hint drift", vector_id(vector)),
   1162     }
   1163 }
   1164 
   1165 fn profile_admit_rsvp_invalid(vector: &Value) {
   1166     let (kind, tags, content) = inbound_parts(vector);
   1167     let parsed = parse_nip52_calendar_event_rsvp(kind, &tags, content)
   1168         .unwrap_or_else(|error| panic!("{} parse failed: {error}", vector_id(vector)));
   1169     let error = admit_radroots_calendar_event_rsvp(parsed)
   1170         .expect_err("invalid RSVP admission vector must fail");
   1171     assert_admission_error(vector, &error);
   1172     assert_registry_rejects(kind, &tags, content, "radroots.calendar.rsvp.v1", vector);
   1173 }
   1174 
   1175 fn authored_collection(
   1176     input: &Map<String, Value>,
   1177     vector_id: &str,
   1178 ) -> Result<AuthoredCalendar, CalendarEventError> {
   1179     let uid = CalendarUid::parse(map_str(input, "d"))?;
   1180     let event_references = input["event_references"]
   1181         .as_array()
   1182         .expect("input.event_references")
   1183         .iter()
   1184         .map(calendar_event_reference)
   1185         .collect::<Result<Vec<_>, _>>()?;
   1186     let mut calendar = AuthoredCalendar::new(
   1187         uid,
   1188         map_str(input, "title"),
   1189         map_optional_str(input, "content").unwrap_or_default(),
   1190         event_references,
   1191     )?;
   1192     if let Some(description) = map_optional_str(input, "list_description") {
   1193         calendar = calendar.with_list_description(description)?;
   1194     }
   1195     if let Some(image) = input.get("image") {
   1196         calendar = calendar.with_image(authored_image(image, vector_id))?;
   1197     }
   1198     Ok(calendar)
   1199 }
   1200 
   1201 fn authored_rsvp(
   1202     input: &Map<String, Value>,
   1203 ) -> Result<AuthoredCalendarEventRsvp, CalendarEventError> {
   1204     let uid = CalendarUid::parse(map_str(input, "d"))?;
   1205     let event_reference = calendar_event_reference(&input["event_reference"])?;
   1206     let mut rsvp = AuthoredCalendarEventRsvp::new(
   1207         uid,
   1208         event_reference,
   1209         parse_authored_rsvp_status(map_str(input, "status")),
   1210     )?;
   1211     if let Some(reference) = input.get("revision_reference") {
   1212         rsvp = rsvp.with_revision_reference(calendar_revision_reference(reference)?)?;
   1213     }
   1214     if let Some(free_busy) = map_optional_str(input, "free_busy") {
   1215         rsvp = rsvp.with_free_busy(parse_authored_free_busy(free_busy))?;
   1216     }
   1217     if let Some(author_hint) = input.get("author_hint") {
   1218         rsvp = rsvp.with_author_hint(calendar_author_reference(author_hint)?)?;
   1219     }
   1220     if let Some(note) = map_optional_str(input, "note") {
   1221         rsvp = rsvp.with_note(note)?;
   1222     }
   1223     Ok(rsvp)
   1224 }
   1225 
   1226 fn calendar_event_reference(value: &Value) -> Result<CalendarEventReference, CalendarEventError> {
   1227     CalendarEventReference::parse(
   1228         value["coordinate"]
   1229             .as_str()
   1230             .expect("event_reference.coordinate"),
   1231         value.get("relay").and_then(Value::as_str),
   1232     )
   1233 }
   1234 
   1235 fn calendar_revision_reference(
   1236     value: &Value,
   1237 ) -> Result<CalendarEventRevisionReference, CalendarEventError> {
   1238     CalendarEventRevisionReference::parse(
   1239         value["id"].as_str().expect("revision_reference.id"),
   1240         value.get("relay").and_then(Value::as_str),
   1241     )
   1242 }
   1243 
   1244 fn calendar_author_reference(
   1245     value: &Value,
   1246 ) -> Result<CalendarEventAuthorReference, CalendarEventError> {
   1247     CalendarEventAuthorReference::parse(
   1248         value["pubkey"].as_str().expect("author_hint.pubkey"),
   1249         value.get("relay").and_then(Value::as_str),
   1250     )
   1251 }
   1252 
   1253 fn parse_authored_rsvp_status(value: &str) -> CalendarEventRsvpStatus {
   1254     match value {
   1255         "accepted" => CalendarEventRsvpStatus::Accepted,
   1256         "declined" => CalendarEventRsvpStatus::Declined,
   1257         "tentative" => CalendarEventRsvpStatus::Tentative,
   1258         value => panic!("unsupported authored RSVP status {value}"),
   1259     }
   1260 }
   1261 
   1262 fn parse_authored_free_busy(value: &str) -> CalendarEventFreeBusy {
   1263     match value {
   1264         "free" => CalendarEventFreeBusy::Free,
   1265         "busy" => CalendarEventFreeBusy::Busy,
   1266         value => panic!("unsupported authored RSVP free/busy value {value}"),
   1267     }
   1268 }
   1269 
   1270 fn authored_date_event(
   1271     input: &Map<String, Value>,
   1272     vector_id: &str,
   1273 ) -> Result<AuthoredCalendarDateEvent, CalendarEventError> {
   1274     let start = CalendarDate::parse(map_str(input, "start"))?;
   1275     let mut event =
   1276         AuthoredCalendarDateEvent::new(map_str(input, "d"), map_str(input, "title"), start)?;
   1277     if let Some(value) = map_optional_str(input, "end") {
   1278         event = event.with_end(CalendarDate::parse(value)?)?;
   1279     }
   1280     event = apply_authored_date_common(event, input, vector_id)?;
   1281     Ok(event)
   1282 }
   1283 
   1284 fn apply_authored_date_common(
   1285     mut event: AuthoredCalendarDateEvent,
   1286     input: &Map<String, Value>,
   1287     vector_id: &str,
   1288 ) -> Result<AuthoredCalendarDateEvent, CalendarEventError> {
   1289     if let Some(value) = map_optional_str(input, "description") {
   1290         event = event.with_description(value)?;
   1291     }
   1292     if let Some(value) = map_optional_string_vec(input, "locations") {
   1293         event = event.with_locations(value)?;
   1294     }
   1295     if let Some(value) = map_optional_str(input, "geohash") {
   1296         event = event.with_geohash(value)?;
   1297     }
   1298     if let Some(value) = map_optional_str(input, "summary") {
   1299         event = event.with_summary(value)?;
   1300     }
   1301     if let Some(value) = input.get("image") {
   1302         event = event.with_image(authored_image(value, vector_id))?;
   1303     }
   1304     if let Some(value) = input.get("participants") {
   1305         event = event.with_participants(participants(value, vector_id))?;
   1306     }
   1307     if let Some(value) = map_optional_string_vec(input, "categories") {
   1308         event = event.with_categories(value)?;
   1309     }
   1310     if let Some(value) = map_optional_string_vec(input, "references") {
   1311         event = event.with_references(
   1312             value
   1313                 .into_iter()
   1314                 .map(CalendarUri::parse)
   1315                 .collect::<Result<Vec<_>, _>>()?,
   1316         )?;
   1317     }
   1318     if let Some(value) = input.get("calendar_requests") {
   1319         event = event.with_calendar_requests(calendar_requests(value, vector_id))?;
   1320     }
   1321     Ok(event)
   1322 }
   1323 
   1324 fn authored_time_event(
   1325     input: &Map<String, Value>,
   1326     vector_id: &str,
   1327 ) -> Result<AuthoredCalendarTimeEvent, CalendarEventError> {
   1328     let mut event = AuthoredCalendarTimeEvent::new(
   1329         map_str(input, "d"),
   1330         map_str(input, "title"),
   1331         map_u64(input, "start"),
   1332     )?;
   1333     if let Some(value) = input.get("end").and_then(Value::as_u64) {
   1334         event = event.with_end(value)?;
   1335     }
   1336     if let Some(value) = map_optional_str(input, "description") {
   1337         event = event.with_description(value)?;
   1338     }
   1339     if let Some(value) = map_optional_str(input, "start_tzid") {
   1340         event = event.with_start_tzid(value)?;
   1341     }
   1342     if let Some(value) = map_optional_str(input, "end_tzid") {
   1343         event = event.with_end_tzid(value)?;
   1344     }
   1345     if let Some(value) = map_optional_string_vec(input, "locations") {
   1346         event = event.with_locations(value)?;
   1347     }
   1348     if let Some(value) = map_optional_str(input, "geohash") {
   1349         event = event.with_geohash(value)?;
   1350     }
   1351     if let Some(value) = map_optional_str(input, "summary") {
   1352         event = event.with_summary(value)?;
   1353     }
   1354     if let Some(value) = input.get("image") {
   1355         event = event.with_image(authored_image(value, vector_id))?;
   1356     }
   1357     if let Some(value) = input.get("participants") {
   1358         event = event.with_participants(participants(value, vector_id))?;
   1359     }
   1360     if let Some(value) = map_optional_string_vec(input, "categories") {
   1361         event = event.with_categories(value)?;
   1362     }
   1363     if let Some(value) = map_optional_string_vec(input, "references") {
   1364         event = event.with_references(
   1365             value
   1366                 .into_iter()
   1367                 .map(CalendarUri::parse)
   1368                 .collect::<Result<Vec<_>, _>>()?,
   1369         )?;
   1370     }
   1371     if let Some(value) = input.get("calendar_requests") {
   1372         event = event.with_calendar_requests(calendar_requests(value, vector_id))?;
   1373     }
   1374     Ok(event)
   1375 }
   1376 
   1377 fn authored_image(input: &Value, vector_id: &str) -> AuthoredImage {
   1378     AuthoredImage::try_from(verified_descriptor(input, vector_id))
   1379         .unwrap_or_else(|error| panic!("{vector_id} image failed: {error}"))
   1380 }
   1381 
   1382 fn verified_descriptor(input: &Value, vector_id: &str) -> ByteVerifiedDescriptor {
   1383     verified_descriptor_result(input)
   1384         .unwrap_or_else(|error| panic!("{vector_id} byte verification failed: {error}"))
   1385 }
   1386 
   1387 fn verified_descriptor_result(input: &Value) -> Result<ByteVerifiedDescriptor, Error> {
   1388     let descriptor: BlobDescriptor =
   1389         serde_json::from_value(input["descriptor"].clone()).expect("image descriptor must parse");
   1390     let media_type = descriptor.media_type().clone();
   1391     descriptor.approve_reference()?.verify_bytes(
   1392         input["bytes_utf8"]
   1393             .as_str()
   1394             .expect("image.bytes_utf8")
   1395             .as_bytes(),
   1396         &media_type,
   1397     )
   1398 }
   1399 
   1400 fn participants(input: &Value, vector_id: &str) -> Vec<CalendarParticipant> {
   1401     input
   1402         .as_array()
   1403         .unwrap_or_else(|| panic!("{vector_id} participants must be an array"))
   1404         .iter()
   1405         .map(|value| CalendarParticipant {
   1406             pubkey: value["pubkey"]
   1407                 .as_str()
   1408                 .expect("participant.pubkey")
   1409                 .to_string(),
   1410             relay: value
   1411                 .get("relay")
   1412                 .and_then(Value::as_str)
   1413                 .map(str::to_string),
   1414             role: value
   1415                 .get("role")
   1416                 .and_then(Value::as_str)
   1417                 .map(str::to_string),
   1418         })
   1419         .collect()
   1420 }
   1421 
   1422 fn calendar_requests(input: &Value, vector_id: &str) -> Vec<CalendarRequest> {
   1423     input
   1424         .as_array()
   1425         .unwrap_or_else(|| panic!("{vector_id} calendar_requests must be an array"))
   1426         .iter()
   1427         .map(|value| {
   1428             CalendarRequest::new(
   1429                 value["calendar"].as_str().expect("request.calendar"),
   1430                 value.get("relay").and_then(Value::as_str),
   1431             )
   1432             .unwrap_or_else(|error| panic!("{vector_id} request failed: {error}"))
   1433         })
   1434         .collect()
   1435 }
   1436 
   1437 fn assert_authored_media_claims(vector: &Value, image_expected: bool) {
   1438     let expected = expected(vector);
   1439     let Some(verification) = expected.get("media_verification") else {
   1440         assert!(
   1441             !image_expected,
   1442             "{} omits its media claim",
   1443             vector_id(vector)
   1444         );
   1445         return;
   1446     };
   1447     assert!(image_expected, "{} claims absent media", vector_id(vector));
   1448     assert_eq!(verification, "byte_verified", "{}", vector_id(vector));
   1449     assert_eq!(
   1450         expected_str(vector, "upload_completion"),
   1451         "not_attested_by_codec",
   1452         "{}",
   1453         vector_id(vector)
   1454     );
   1455 }
   1456 
   1457 fn assert_calendar_event_error(vector: &Value, error: &CalendarEventError) {
   1458     assert_eq!(
   1459         error.code(),
   1460         expected_str(vector, "error"),
   1461         "{}",
   1462         vector_id(vector)
   1463     );
   1464     assert!(!error.to_string().is_empty());
   1465     if let CalendarEventError::CoveredDayLimitExceeded { max, actual } = error {
   1466         assert_eq!(*max, expected_u64(vector, "max"));
   1467         assert_eq!(*actual, expected_u64(vector, "actual"));
   1468     }
   1469 }
   1470 
   1471 fn assert_admission_error(vector: &Value, error: &CalendarAdmissionError) {
   1472     assert_eq!(
   1473         error.code(),
   1474         expected_str(vector, "error"),
   1475         "{}",
   1476         vector_id(vector)
   1477     );
   1478     assert!(!error.to_string().is_empty());
   1479     if let Some(expected_field) = expected(vector).get("field").and_then(Value::as_str) {
   1480         let CalendarAdmissionError::NonCanonicalField(actual_field) = error else {
   1481             panic!(
   1482                 "{} expected a field-bearing admission error",
   1483                 vector_id(vector)
   1484             );
   1485         };
   1486         assert_eq!(*actual_field, expected_field, "{}", vector_id(vector));
   1487     }
   1488     if let CalendarAdmissionError::CoveredDayLimitExceeded { max, actual } = error {
   1489         assert_eq!(*max, expected_u64(vector, "max"));
   1490         assert_eq!(*actual, expected_u64(vector, "actual"));
   1491     }
   1492 }
   1493 
   1494 fn assert_parse_error(vector: &Value, error: &EventParseError) {
   1495     assert_eq!(
   1496         error.code(),
   1497         expected_str(vector, "error"),
   1498         "{}",
   1499         vector_id(vector)
   1500     );
   1501     assert!(!error.to_string().is_empty());
   1502     if let Some(expected_tag) = expected(vector).get("tag").and_then(Value::as_str) {
   1503         assert_eq!(
   1504             parse_error_tag(error),
   1505             Some(expected_tag),
   1506             "{}",
   1507             vector_id(vector)
   1508         );
   1509     }
   1510 }
   1511 
   1512 fn parse_error_tag(error: &EventParseError) -> Option<&'static str> {
   1513     match error {
   1514         EventParseError::MissingTag(tag)
   1515         | EventParseError::InvalidTag(tag)
   1516         | EventParseError::DuplicateTag(tag)
   1517         | EventParseError::InvalidNumber(tag, _)
   1518         | EventParseError::InvalidJson(tag) => Some(tag),
   1519         EventParseError::InvalidEnvelope | EventParseError::InvalidKind { .. } => None,
   1520     }
   1521 }
   1522 
   1523 fn assert_wire_parts(
   1524     wire: &radroots_event::wire::Nip01EventWireParts,
   1525     expected: &Value,
   1526     vector_id: &str,
   1527 ) {
   1528     assert_eq!(
   1529         u64::from(wire.kind),
   1530         expected["kind"].as_u64().unwrap(),
   1531         "{vector_id}"
   1532     );
   1533     assert_eq!(
   1534         wire.content,
   1535         expected["content"].as_str().unwrap(),
   1536         "{vector_id}"
   1537     );
   1538     assert_eq!(
   1539         serde_json::to_value(&wire.tags).unwrap(),
   1540         expected["tags"],
   1541         "{vector_id}"
   1542     );
   1543 }
   1544 
   1545 fn inbound_parts(vector: &Value) -> (u32, Vec<Vec<String>>, &str) {
   1546     let input = input(vector);
   1547     let kind = map_u64(input, "kind") as u32;
   1548     let tags = serde_json::from_value(input["tags"].clone())
   1549         .unwrap_or_else(|error| panic!("{} tags failed: {error}", vector_id(vector)));
   1550     let content = map_str(input, "content");
   1551     (kind, tags, content)
   1552 }
   1553 
   1554 fn assert_optional_str(actual: Option<&str>, expected: &Value, key: &str, vector: &Value) {
   1555     assert_eq!(
   1556         actual,
   1557         expected.get(key).and_then(Value::as_str),
   1558         "{} expected.{key}",
   1559         vector_id(vector)
   1560     );
   1561 }
   1562 
   1563 fn input(vector: &Value) -> &Map<String, Value> {
   1564     vector["input"].as_object().expect("vector.input")
   1565 }
   1566 
   1567 fn expected(vector: &Value) -> &Value {
   1568     &vector["expected"]
   1569 }
   1570 
   1571 fn vector_id(vector: &Value) -> &str {
   1572     vector["id"].as_str().expect("vector.id")
   1573 }
   1574 
   1575 fn vector_kind(vector: &Value) -> &str {
   1576     vector["kind"].as_str().expect("vector.kind")
   1577 }
   1578 
   1579 fn input_str<'a>(vector: &'a Value, field: &str) -> &'a str {
   1580     map_str(input(vector), field)
   1581 }
   1582 
   1583 fn expected_str<'a>(vector: &'a Value, field: &str) -> &'a str {
   1584     expected(vector)[field]
   1585         .as_str()
   1586         .unwrap_or_else(|| panic!("{} expected.{field}", vector_id(vector)))
   1587 }
   1588 
   1589 fn expected_u64(vector: &Value, field: &str) -> u64 {
   1590     expected(vector)[field]
   1591         .as_u64()
   1592         .unwrap_or_else(|| panic!("{} expected.{field}", vector_id(vector)))
   1593 }
   1594 
   1595 fn value_str<'a>(value: &'a Value, field: &str) -> &'a str {
   1596     value[field]
   1597         .as_str()
   1598         .unwrap_or_else(|| panic!("expected.{field} must be a string"))
   1599 }
   1600 
   1601 fn value_u64(value: &Value, field: &str) -> u64 {
   1602     value[field]
   1603         .as_u64()
   1604         .unwrap_or_else(|| panic!("expected.{field} must be an integer"))
   1605 }
   1606 
   1607 fn map_str<'a>(map: &'a Map<String, Value>, field: &str) -> &'a str {
   1608     map[field]
   1609         .as_str()
   1610         .unwrap_or_else(|| panic!("input.{field} must be a string"))
   1611 }
   1612 
   1613 fn map_optional_str<'a>(map: &'a Map<String, Value>, field: &str) -> Option<&'a str> {
   1614     map.get(field).and_then(Value::as_str)
   1615 }
   1616 
   1617 fn map_u64(map: &Map<String, Value>, field: &str) -> u64 {
   1618     map[field]
   1619         .as_u64()
   1620         .unwrap_or_else(|| panic!("input.{field} must be an integer"))
   1621 }
   1622 
   1623 fn map_optional_string_vec(map: &Map<String, Value>, field: &str) -> Option<Vec<String>> {
   1624     map.get(field).map(|value| {
   1625         value
   1626             .as_array()
   1627             .unwrap_or_else(|| panic!("input.{field} must be an array"))
   1628             .iter()
   1629             .map(|value| {
   1630                 value
   1631                     .as_str()
   1632                     .unwrap_or_else(|| panic!("input.{field} values must be strings"))
   1633                     .to_string()
   1634             })
   1635             .collect()
   1636     })
   1637 }