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 }