lib

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

decode.rs (22306B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::{
      3     string::{String, ToString},
      4     vec::Vec,
      5 };
      6 
      7 use radroots_event::{
      8     calendar::{
      9         AdmittedCalendar, AdmittedCalendarDateEvent, AdmittedCalendarEventRsvp,
     10         AdmittedCalendarTimeEvent, CalendarDate, CalendarEventAuthorReference,
     11         CalendarEventFreeBusy, CalendarEventReference, CalendarEventRevisionReference,
     12         CalendarEventRsvpStatus, CalendarParticipant, CalendarRequest, CalendarUri, IanaTimeZoneId,
     13         ObservedUtcDay, ParsedNip52Calendar, ParsedNip52CalendarCommon,
     14         ParsedNip52CalendarCommonParts, ParsedNip52CalendarDateEvent, ParsedNip52CalendarEventRsvp,
     15         ParsedNip52CalendarEventRsvpParts, ParsedNip52CalendarParts, ParsedNip52CalendarTimeEvent,
     16         RADROOTS_CALENDAR_SECONDS_PER_DAY, calendar_geohash_is_valid, calendar_relay_url_is_valid,
     17         calendar_tag_text_is_valid,
     18     },
     19     envelope::kind::{
     20         KIND_CALENDAR, KIND_CALENDAR_DATE_EVENT, KIND_CALENDAR_EVENT_RSVP, KIND_CALENDAR_TIME_EVENT,
     21     },
     22     tag::name::{
     23         TAG_A, TAG_D, TAG_D_DAY, TAG_DESCRIPTION, TAG_E, TAG_END, TAG_END_TZID, TAG_FREE_BUSY,
     24         TAG_G, TAG_IMAGE, TAG_LOCATION, TAG_P, TAG_R, TAG_START, TAG_START_TZID, TAG_STATUS,
     25         TAG_SUMMARY, TAG_T, TAG_TITLE,
     26     },
     27     wire::{
     28         DEFAULT_CONTENT_MAX_BYTES, DEFAULT_TAG_ELEMENT_MAX_BYTES, DEFAULT_TAG_MAX_COUNT,
     29         DEFAULT_TAG_TOTAL_MAX_BYTES,
     30     },
     31 };
     32 use radroots_identity::PublicKey;
     33 
     34 use crate::error::EventParseError;
     35 use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent};
     36 
     37 const EXPECTED_DATE_KIND: &str = "31922";
     38 const EXPECTED_TIME_KIND: &str = "31923";
     39 const EXPECTED_CALENDAR_KIND: &str = "31924";
     40 const EXPECTED_RSVP_KIND: &str = "31925";
     41 
     42 pub fn parse_nip52_calendar_date_event(
     43     kind: u32,
     44     tags: &[Vec<String>],
     45     content: &str,
     46 ) -> Result<ParsedNip52CalendarDateEvent, EventParseError> {
     47     validate_bounded_calendar_parts(tags, content)?;
     48     if kind != KIND_CALENDAR_DATE_EVENT {
     49         return Err(EventParseError::InvalidKind {
     50             expected: EXPECTED_DATE_KIND,
     51             got: kind,
     52         });
     53     }
     54     let common = parse_nip52_calendar_common(tags, content)?;
     55     let start = parse_calendar_date(&required_exact_tag_value(tags, TAG_START)?, TAG_START)?;
     56     let end = optional_exact_tag_value(tags, TAG_END)?
     57         .map(|value| parse_calendar_date(&value, TAG_END))
     58         .transpose()?;
     59     let extension_day_tags = tags
     60         .iter()
     61         .filter(|tag| tag.first().map(String::as_str) == Some(TAG_D_DAY))
     62         .cloned()
     63         .collect();
     64     ParsedNip52CalendarDateEvent::try_new(common, start, end, extension_day_tags)
     65         .map_err(|_| EventParseError::InvalidTag(TAG_END))
     66 }
     67 
     68 pub fn parse_nip52_calendar_time_event(
     69     kind: u32,
     70     tags: &[Vec<String>],
     71     content: &str,
     72 ) -> Result<ParsedNip52CalendarTimeEvent, EventParseError> {
     73     validate_bounded_calendar_parts(tags, content)?;
     74     if kind != KIND_CALENDAR_TIME_EVENT {
     75         return Err(EventParseError::InvalidKind {
     76             expected: EXPECTED_TIME_KIND,
     77             got: kind,
     78         });
     79     }
     80     let common = parse_nip52_calendar_common(tags, content)?;
     81     let start_wire = required_exact_tag_value(tags, TAG_START)?;
     82     let start = parse_standard_u64(&start_wire, TAG_START)?;
     83     let end_wire = optional_exact_tag_value(tags, TAG_END)?;
     84     let end = end_wire
     85         .as_deref()
     86         .map(|value| parse_standard_u64(value, TAG_END))
     87         .transpose()?;
     88     let observed_day_indices = repeated_exact_tag_values(tags, TAG_D_DAY)?
     89         .into_iter()
     90         .map(|value| {
     91             ObservedUtcDay::parse(value).map_err(|_| EventParseError::InvalidTag(TAG_D_DAY))
     92         })
     93         .collect::<Result<Vec<_>, _>>()?;
     94     if end.is_some_and(|end| end <= start) {
     95         return Err(EventParseError::InvalidTag(TAG_END));
     96     }
     97     if observed_day_indices.is_empty() {
     98         return Err(EventParseError::MissingTag(TAG_D_DAY));
     99     }
    100     let first_day = start / RADROOTS_CALENDAR_SECONDS_PER_DAY;
    101     let last_day = end
    102         .map(|end| (end - 1) / RADROOTS_CALENDAR_SECONDS_PER_DAY)
    103         .unwrap_or(first_day);
    104     if observed_day_indices
    105         .iter()
    106         .any(|day| !(first_day..=last_day).contains(&day.index()))
    107     {
    108         return Err(EventParseError::InvalidTag(TAG_D_DAY));
    109     }
    110     let start_tzid = optional_iana_tzid(tags, TAG_START_TZID)?;
    111     let end_tzid = optional_iana_tzid(tags, TAG_END_TZID)?;
    112     ParsedNip52CalendarTimeEvent::try_new(
    113         common,
    114         start_wire,
    115         start,
    116         end_wire,
    117         end,
    118         observed_day_indices,
    119         start_tzid,
    120         end_tzid,
    121     )
    122     .map_err(|_| EventParseError::InvalidTag(TAG_END))
    123 }
    124 
    125 pub fn admit_radroots_calendar_date_event(
    126     parsed: ParsedNip52CalendarDateEvent,
    127 ) -> Result<AdmittedCalendarDateEvent, radroots_event::calendar::CalendarAdmissionError> {
    128     AdmittedCalendarDateEvent::try_from_parsed(parsed)
    129 }
    130 
    131 pub fn admit_radroots_calendar_time_event(
    132     parsed: ParsedNip52CalendarTimeEvent,
    133 ) -> Result<AdmittedCalendarTimeEvent, radroots_event::calendar::CalendarAdmissionError> {
    134     AdmittedCalendarTimeEvent::try_from_parsed(parsed)
    135 }
    136 
    137 pub fn parse_nip52_calendar(
    138     kind: u32,
    139     tags: &[Vec<String>],
    140     content: &str,
    141 ) -> Result<ParsedNip52Calendar, EventParseError> {
    142     validate_bounded_calendar_parts(tags, content)?;
    143     if kind != KIND_CALENDAR {
    144         return Err(EventParseError::InvalidKind {
    145             expected: EXPECTED_CALENDAR_KIND,
    146             got: kind,
    147         });
    148     }
    149     let d_tag = required_exact_tag_value(tags, TAG_D)?;
    150     validate_calendar_text_tag(&d_tag, TAG_D)?;
    151     let title = required_exact_tag_value(tags, TAG_TITLE)?;
    152     validate_calendar_text_tag(&title, TAG_TITLE)?;
    153     let event_references = calendar_event_references_from_tags(tags)?;
    154     let list_description = optional_calendar_text_tag(tags, TAG_DESCRIPTION)?;
    155     let image = optional_calendar_uri_tag(tags, TAG_IMAGE)?;
    156     ParsedNip52Calendar::try_new(ParsedNip52CalendarParts {
    157         d_tag,
    158         title,
    159         content: content.to_string(),
    160         event_references,
    161         list_description,
    162         image,
    163     })
    164     .map_err(|_| EventParseError::InvalidTag("calendar"))
    165 }
    166 
    167 pub fn admit_radroots_calendar(
    168     parsed: ParsedNip52Calendar,
    169 ) -> Result<AdmittedCalendar, radroots_event::calendar::CalendarAdmissionError> {
    170     AdmittedCalendar::try_from_parsed(parsed)
    171 }
    172 
    173 pub fn parse_nip52_calendar_event_rsvp(
    174     kind: u32,
    175     tags: &[Vec<String>],
    176     content: &str,
    177 ) -> Result<ParsedNip52CalendarEventRsvp, EventParseError> {
    178     validate_bounded_calendar_parts(tags, content)?;
    179     if kind != KIND_CALENDAR_EVENT_RSVP {
    180         return Err(EventParseError::InvalidKind {
    181             expected: EXPECTED_RSVP_KIND,
    182             got: kind,
    183         });
    184     }
    185     let d_tag = required_exact_tag_value(tags, TAG_D)?;
    186     validate_calendar_text_tag(&d_tag, TAG_D)?;
    187     let event_reference = calendar_event_reference_from_required_tag(tags)?;
    188     let revision_reference = optional_reference_tag(tags, TAG_E)?
    189         .map(|tag| {
    190             CalendarEventRevisionReference::parse(&tag[1], tag.get(2).map(String::as_str))
    191                 .map_err(|_| EventParseError::InvalidTag(TAG_E))
    192         })
    193         .transpose()?;
    194     let status = parse_rsvp_status(&required_exact_tag_value(tags, TAG_STATUS)?)?;
    195     let observed_free_busy = optional_exact_tag_value(tags, TAG_FREE_BUSY)?
    196         .map(|value| parse_free_busy(&value))
    197         .transpose()?;
    198     let author_hint = optional_reference_tag(tags, TAG_P)?
    199         .map(|tag| {
    200             CalendarEventAuthorReference::parse(&tag[1], tag.get(2).map(String::as_str))
    201                 .map_err(|_| EventParseError::InvalidTag(TAG_P))
    202         })
    203         .transpose()?;
    204     ParsedNip52CalendarEventRsvp::try_new(ParsedNip52CalendarEventRsvpParts {
    205         d_tag,
    206         event_reference,
    207         revision_reference,
    208         status,
    209         observed_free_busy,
    210         author_hint,
    211         note: optional_content(content),
    212     })
    213     .map_err(|_| EventParseError::InvalidTag("calendar_rsvp"))
    214 }
    215 
    216 pub fn admit_radroots_calendar_event_rsvp(
    217     parsed: ParsedNip52CalendarEventRsvp,
    218 ) -> Result<AdmittedCalendarEventRsvp, radroots_event::calendar::CalendarAdmissionError> {
    219     AdmittedCalendarEventRsvp::try_from_parsed(parsed)
    220 }
    221 
    222 pub fn nip52_date_data_from_event(
    223     id: String,
    224     author: String,
    225     published_at: u64,
    226     kind: u32,
    227     content: String,
    228     tags: Vec<Vec<String>>,
    229 ) -> Result<RadrootsParsedData<ParsedNip52CalendarDateEvent>, EventParseError> {
    230     let event = parse_nip52_calendar_date_event(kind, &tags, &content)?;
    231     Ok(RadrootsParsedData::new(
    232         id,
    233         author,
    234         published_at,
    235         kind,
    236         event,
    237     ))
    238 }
    239 
    240 pub fn nip52_time_data_from_event(
    241     id: String,
    242     author: String,
    243     published_at: u64,
    244     kind: u32,
    245     content: String,
    246     tags: Vec<Vec<String>>,
    247 ) -> Result<RadrootsParsedData<ParsedNip52CalendarTimeEvent>, EventParseError> {
    248     let event = parse_nip52_calendar_time_event(kind, &tags, &content)?;
    249     Ok(RadrootsParsedData::new(
    250         id,
    251         author,
    252         published_at,
    253         kind,
    254         event,
    255     ))
    256 }
    257 
    258 pub fn nip52_calendar_data_from_event(
    259     id: String,
    260     author: String,
    261     published_at: u64,
    262     kind: u32,
    263     content: String,
    264     tags: Vec<Vec<String>>,
    265 ) -> Result<RadrootsParsedData<ParsedNip52Calendar>, EventParseError> {
    266     let calendar = parse_nip52_calendar(kind, &tags, &content)?;
    267     Ok(RadrootsParsedData::new(
    268         id,
    269         author,
    270         published_at,
    271         kind,
    272         calendar,
    273     ))
    274 }
    275 
    276 pub fn nip52_calendar_event_rsvp_data_from_event(
    277     id: String,
    278     author: String,
    279     published_at: u64,
    280     kind: u32,
    281     content: String,
    282     tags: Vec<Vec<String>>,
    283 ) -> Result<RadrootsParsedData<ParsedNip52CalendarEventRsvp>, EventParseError> {
    284     let rsvp = parse_nip52_calendar_event_rsvp(kind, &tags, &content)?;
    285     Ok(RadrootsParsedData::new(
    286         id,
    287         author,
    288         published_at,
    289         kind,
    290         rsvp,
    291     ))
    292 }
    293 
    294 pub fn nip52_date_parsed_from_event(
    295     id: String,
    296     author: String,
    297     published_at: u64,
    298     kind: u32,
    299     content: String,
    300     tags: Vec<Vec<String>>,
    301     sig: String,
    302 ) -> Result<RadrootsParsedEvent<ParsedNip52CalendarDateEvent>, EventParseError> {
    303     let data = nip52_date_data_from_event(
    304         id.clone(),
    305         author.clone(),
    306         published_at,
    307         kind,
    308         content.clone(),
    309         tags.clone(),
    310     )?;
    311     RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data)
    312 }
    313 
    314 pub fn nip52_time_parsed_from_event(
    315     id: String,
    316     author: String,
    317     published_at: u64,
    318     kind: u32,
    319     content: String,
    320     tags: Vec<Vec<String>>,
    321     sig: String,
    322 ) -> Result<RadrootsParsedEvent<ParsedNip52CalendarTimeEvent>, EventParseError> {
    323     let data = nip52_time_data_from_event(
    324         id.clone(),
    325         author.clone(),
    326         published_at,
    327         kind,
    328         content.clone(),
    329         tags.clone(),
    330     )?;
    331     RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data)
    332 }
    333 
    334 pub fn nip52_calendar_parsed_from_event(
    335     id: String,
    336     author: String,
    337     published_at: u64,
    338     kind: u32,
    339     content: String,
    340     tags: Vec<Vec<String>>,
    341     sig: String,
    342 ) -> Result<RadrootsParsedEvent<ParsedNip52Calendar>, EventParseError> {
    343     let data = nip52_calendar_data_from_event(
    344         id.clone(),
    345         author.clone(),
    346         published_at,
    347         kind,
    348         content.clone(),
    349         tags.clone(),
    350     )?;
    351     RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data)
    352 }
    353 
    354 pub fn nip52_calendar_event_rsvp_parsed_from_event(
    355     id: String,
    356     author: String,
    357     published_at: u64,
    358     kind: u32,
    359     content: String,
    360     tags: Vec<Vec<String>>,
    361     sig: String,
    362 ) -> Result<RadrootsParsedEvent<ParsedNip52CalendarEventRsvp>, EventParseError> {
    363     let data = nip52_calendar_event_rsvp_data_from_event(
    364         id.clone(),
    365         author.clone(),
    366         published_at,
    367         kind,
    368         content.clone(),
    369         tags.clone(),
    370     )?;
    371     RadrootsParsedEvent::from_event_parts(id, author, published_at, kind, content, tags, sig, data)
    372 }
    373 
    374 fn parse_nip52_calendar_common(
    375     tags: &[Vec<String>],
    376     content: &str,
    377 ) -> Result<ParsedNip52CalendarCommon, EventParseError> {
    378     let d_tag = required_exact_tag_value(tags, TAG_D)?;
    379     validate_calendar_text_tag(&d_tag, TAG_D)?;
    380     let title = required_exact_tag_value(tags, TAG_TITLE)?;
    381     validate_calendar_text_tag(&title, TAG_TITLE)?;
    382     let locations = repeated_calendar_text_values(tags, TAG_LOCATION)?;
    383     let geohash = optional_exact_tag_value(tags, TAG_G)?;
    384     if geohash
    385         .as_deref()
    386         .is_some_and(|value| !calendar_geohash_is_valid(value))
    387     {
    388         return Err(EventParseError::InvalidTag(TAG_G));
    389     }
    390     let summary = optional_calendar_text_tag(tags, TAG_SUMMARY)?;
    391     let image = optional_calendar_uri_tag(tags, TAG_IMAGE)?;
    392     let participants = calendar_participants_from_tags(tags)?;
    393     let categories = repeated_calendar_text_values(tags, TAG_T)?;
    394     let references = repeated_exact_tag_values(tags, TAG_R)?
    395         .into_iter()
    396         .map(|value| CalendarUri::parse(value).map_err(|_| EventParseError::InvalidTag(TAG_R)))
    397         .collect::<Result<Vec<_>, _>>()?;
    398     let calendar_requests = calendar_requests_from_tags(tags)?;
    399     let legacy_name = optional_calendar_text_tag(tags, "name")?;
    400     ParsedNip52CalendarCommon::try_new(ParsedNip52CalendarCommonParts {
    401         d_tag,
    402         title,
    403         description: optional_content(content),
    404         locations,
    405         geohash,
    406         summary,
    407         image,
    408         participants,
    409         categories,
    410         references,
    411         calendar_requests,
    412         legacy_name,
    413     })
    414     .map_err(|_| EventParseError::InvalidTag("calendar"))
    415 }
    416 
    417 fn validate_bounded_calendar_parts(
    418     tags: &[Vec<String>],
    419     content: &str,
    420 ) -> Result<(), EventParseError> {
    421     if content.len() > DEFAULT_CONTENT_MAX_BYTES {
    422         return Err(EventParseError::InvalidEnvelope);
    423     }
    424     if tags.len() > DEFAULT_TAG_MAX_COUNT {
    425         return Err(EventParseError::InvalidEnvelope);
    426     }
    427     let mut total_bytes = 0usize;
    428     for tag in tags {
    429         let Some(key) = tag.first() else {
    430             return Err(EventParseError::InvalidEnvelope);
    431         };
    432         if key.is_empty() || key.chars().any(char::is_control) {
    433             return Err(EventParseError::InvalidEnvelope);
    434         }
    435         for value in tag {
    436             if value.len() > DEFAULT_TAG_ELEMENT_MAX_BYTES {
    437                 return Err(EventParseError::InvalidEnvelope);
    438             }
    439             total_bytes = total_bytes.saturating_add(value.len());
    440             if total_bytes > DEFAULT_TAG_TOTAL_MAX_BYTES {
    441                 return Err(EventParseError::InvalidEnvelope);
    442             }
    443         }
    444     }
    445     Ok(())
    446 }
    447 
    448 fn required_exact_tag_value(
    449     tags: &[Vec<String>],
    450     key: &'static str,
    451 ) -> Result<String, EventParseError> {
    452     optional_exact_tag_value(tags, key)?.ok_or(EventParseError::MissingTag(key))
    453 }
    454 
    455 fn repeated_exact_tag_values(
    456     tags: &[Vec<String>],
    457     key: &'static str,
    458 ) -> Result<Vec<String>, EventParseError> {
    459     tags.iter()
    460         .filter(|tag| tag.first().map(String::as_str) == Some(key))
    461         .map(|tag| {
    462             if tag.len() != 2 || tag[1].is_empty() {
    463                 Err(EventParseError::InvalidTag(key))
    464             } else {
    465                 Ok(tag[1].clone())
    466             }
    467         })
    468         .collect()
    469 }
    470 
    471 fn optional_exact_tag_value(
    472     tags: &[Vec<String>],
    473     key: &'static str,
    474 ) -> Result<Option<String>, EventParseError> {
    475     let mut matching = tags
    476         .iter()
    477         .filter(|tag| tag.first().map(String::as_str) == Some(key));
    478     let Some(tag) = matching.next() else {
    479         return Ok(None);
    480     };
    481     if matching.next().is_some() {
    482         return Err(EventParseError::DuplicateTag(key));
    483     }
    484     if tag.len() != 2 {
    485         return Err(EventParseError::InvalidTag(key));
    486     }
    487     let value = tag[1].clone();
    488     if value.is_empty() {
    489         return Err(EventParseError::InvalidTag(key));
    490     }
    491     Ok(Some(value))
    492 }
    493 
    494 fn parse_calendar_date(value: &str, key: &'static str) -> Result<CalendarDate, EventParseError> {
    495     CalendarDate::parse(value).map_err(|_| EventParseError::InvalidTag(key))
    496 }
    497 
    498 fn parse_standard_u64(value: &str, key: &'static str) -> Result<u64, EventParseError> {
    499     if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
    500         return Err(EventParseError::InvalidTag(key));
    501     }
    502     value
    503         .parse::<u64>()
    504         .map_err(|error| EventParseError::InvalidNumber(key, error))
    505 }
    506 
    507 fn optional_iana_tzid(
    508     tags: &[Vec<String>],
    509     key: &'static str,
    510 ) -> Result<Option<IanaTimeZoneId>, EventParseError> {
    511     optional_exact_tag_value(tags, key)?
    512         .map(|value| IanaTimeZoneId::parse(value).map_err(|_| EventParseError::InvalidTag(key)))
    513         .transpose()
    514 }
    515 
    516 fn optional_calendar_text_tag(
    517     tags: &[Vec<String>],
    518     key: &'static str,
    519 ) -> Result<Option<String>, EventParseError> {
    520     let value = optional_exact_tag_value(tags, key)?;
    521     if let Some(value) = value.as_deref() {
    522         validate_calendar_text_tag(value, key)?;
    523     }
    524     Ok(value)
    525 }
    526 
    527 fn validate_calendar_text_tag(value: &str, key: &'static str) -> Result<(), EventParseError> {
    528     if !calendar_tag_text_is_valid(value) {
    529         Err(EventParseError::InvalidTag(key))
    530     } else {
    531         Ok(())
    532     }
    533 }
    534 
    535 fn repeated_calendar_text_values(
    536     tags: &[Vec<String>],
    537     key: &'static str,
    538 ) -> Result<Vec<String>, EventParseError> {
    539     let values = repeated_exact_tag_values(tags, key)?;
    540     for value in &values {
    541         validate_calendar_text_tag(value, key)?;
    542     }
    543     Ok(values)
    544 }
    545 
    546 fn optional_calendar_uri_tag(
    547     tags: &[Vec<String>],
    548     key: &'static str,
    549 ) -> Result<Option<CalendarUri>, EventParseError> {
    550     optional_exact_tag_value(tags, key)?
    551         .map(|value| CalendarUri::parse(value).map_err(|_| EventParseError::InvalidTag(key)))
    552         .transpose()
    553 }
    554 
    555 fn calendar_participants_from_tags(
    556     tags: &[Vec<String>],
    557 ) -> Result<Vec<CalendarParticipant>, EventParseError> {
    558     let mut participants = Vec::new();
    559     for tag in tags
    560         .iter()
    561         .filter(|tag| tag.first().map(String::as_str) == Some("p"))
    562     {
    563         if !(2..=4).contains(&tag.len()) {
    564             return Err(EventParseError::InvalidTag("p"));
    565         }
    566         PublicKey::from_hex(&tag[1]).map_err(|_| EventParseError::InvalidTag(TAG_P))?;
    567         if tag.len() == 3 && tag[2].is_empty() {
    568             return Err(EventParseError::InvalidTag(TAG_P));
    569         }
    570         let relay = tag.get(2).filter(|value| !value.is_empty()).cloned();
    571         if relay
    572             .as_deref()
    573             .is_some_and(|value| !calendar_relay_url_is_valid(value))
    574         {
    575             return Err(EventParseError::InvalidTag(TAG_P));
    576         }
    577         let role = tag.get(3).cloned();
    578         if let Some(role) = role.as_deref() {
    579             validate_calendar_text_tag(role, TAG_P)?;
    580         }
    581         participants.push(CalendarParticipant {
    582             pubkey: tag[1].clone(),
    583             relay,
    584             role,
    585         });
    586     }
    587     Ok(participants)
    588 }
    589 
    590 fn calendar_requests_from_tags(
    591     tags: &[Vec<String>],
    592 ) -> Result<Vec<CalendarRequest>, EventParseError> {
    593     tags.iter()
    594         .filter(|tag| tag.first().map(String::as_str) == Some(TAG_A))
    595         .map(|tag| {
    596             if !(2..=3).contains(&tag.len())
    597                 || tag[1].is_empty()
    598                 || tag.get(2).is_some_and(String::is_empty)
    599             {
    600                 return Err(EventParseError::InvalidTag(TAG_A));
    601             }
    602             CalendarRequest::new(&tag[1], tag.get(2).map(String::as_str))
    603                 .map_err(|_| EventParseError::InvalidTag(TAG_A))
    604         })
    605         .collect()
    606 }
    607 
    608 fn optional_content(content: &str) -> Option<String> {
    609     if content.is_empty() {
    610         None
    611     } else {
    612         Some(content.to_string())
    613     }
    614 }
    615 
    616 fn calendar_event_references_from_tags(
    617     tags: &[Vec<String>],
    618 ) -> Result<Vec<CalendarEventReference>, EventParseError> {
    619     tags.iter()
    620         .filter(|tag| tag.first().map(String::as_str) == Some(TAG_A))
    621         .map(|tag| parse_calendar_event_reference_tag(tag))
    622         .collect()
    623 }
    624 
    625 fn calendar_event_reference_from_required_tag(
    626     tags: &[Vec<String>],
    627 ) -> Result<CalendarEventReference, EventParseError> {
    628     let tag = optional_reference_tag(tags, TAG_A)?.ok_or(EventParseError::MissingTag(TAG_A))?;
    629     parse_calendar_event_reference_tag(tag)
    630 }
    631 
    632 fn parse_calendar_event_reference_tag(
    633     tag: &[String],
    634 ) -> Result<CalendarEventReference, EventParseError> {
    635     validate_reference_tag_shape(tag, TAG_A)?;
    636     CalendarEventReference::parse(&tag[1], tag.get(2).map(String::as_str))
    637         .map_err(|_| EventParseError::InvalidTag(TAG_A))
    638 }
    639 
    640 fn optional_reference_tag<'a>(
    641     tags: &'a [Vec<String>],
    642     key: &'static str,
    643 ) -> Result<Option<&'a Vec<String>>, EventParseError> {
    644     let mut matching = tags
    645         .iter()
    646         .filter(|tag| tag.first().map(String::as_str) == Some(key));
    647     let Some(tag) = matching.next() else {
    648         return Ok(None);
    649     };
    650     if matching.next().is_some() {
    651         return Err(EventParseError::DuplicateTag(key));
    652     }
    653     validate_reference_tag_shape(tag, key)?;
    654     Ok(Some(tag))
    655 }
    656 
    657 fn validate_reference_tag_shape(tag: &[String], key: &'static str) -> Result<(), EventParseError> {
    658     if !(2..=3).contains(&tag.len())
    659         || tag[1].is_empty()
    660         || tag.get(2).is_some_and(String::is_empty)
    661     {
    662         return Err(EventParseError::InvalidTag(key));
    663     }
    664     Ok(())
    665 }
    666 
    667 fn parse_rsvp_status(value: &str) -> Result<CalendarEventRsvpStatus, EventParseError> {
    668     match value {
    669         "accepted" => Ok(CalendarEventRsvpStatus::Accepted),
    670         "declined" => Ok(CalendarEventRsvpStatus::Declined),
    671         "tentative" => Ok(CalendarEventRsvpStatus::Tentative),
    672         _ => Err(EventParseError::InvalidTag(TAG_STATUS)),
    673     }
    674 }
    675 
    676 fn parse_free_busy(value: &str) -> Result<CalendarEventFreeBusy, EventParseError> {
    677     match value {
    678         "free" => Ok(CalendarEventFreeBusy::Free),
    679         "busy" => Ok(CalendarEventFreeBusy::Busy),
    680         _ => Err(EventParseError::InvalidTag(TAG_FREE_BUSY)),
    681     }
    682 }