io-calendar 0.0.1

Set of I/O-free coroutines to manage calendars
use std::{
    borrow::Cow,
    hash::{Hash, Hasher},
};

use calcard::Entry;
use serde::{Serialize, Serializer};
use thiserror::Error;
use uuid::Uuid;

pub use calcard::icalendar::*;

#[derive(Clone, Debug, Error)]
pub enum ParseCalendarItemError {
    #[error("Invalid iCal format: parsed iCal instead")]
    InvalidFormat,
    #[error("Invalid iCal line: {0}")]
    InvalidLine(String),
    #[error("Unexpected iCal EOF")]
    UnexpectedEof,
    #[error("Too many iCal components")]
    TooManyComponents,
    #[error("Unexpected iCal component end: expected {0:?} got {1:?}")]
    UnexpectedComponentEnd(ICalendarComponentType, ICalendarComponentType),
    #[error("Unterminated iCal component: {0}")]
    UnterminatedComponent(Cow<'static, str>),
    #[error("Unknown iCal error: {0:?}")]
    Unknown(Entry),
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CalendarItem {
    pub id: String,
    pub calendar_id: String,
    #[serde(serialize_with = "CalendarItem::serialize_ical")]
    pub ical: ICalendar,
}

impl CalendarItem {
    pub fn new_uuid() -> Uuid {
        Uuid::new_v4()
    }

    pub fn serialize_ical<S: Serializer>(ical: &ICalendar, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(&ical.to_string())
    }

    pub fn parse(contents: impl AsRef<str>) -> Result<ICalendar, ParseCalendarItemError> {
        match ICalendar::parse(contents) {
            Ok(ical) => Ok(ical),
            Err(Entry::ICalendar(ical)) => Ok(ical),
            Err(Entry::VCard(_)) => Err(ParseCalendarItemError::InvalidFormat),
            Err(Entry::InvalidLine(line)) => Err(ParseCalendarItemError::InvalidLine(line)),
            Err(Entry::Eof) => Err(ParseCalendarItemError::UnexpectedEof),
            Err(Entry::TooManyComponents) => Err(ParseCalendarItemError::TooManyComponents),
            Err(Entry::UnexpectedComponentEnd { expected, found }) => Err(
                ParseCalendarItemError::UnexpectedComponentEnd(expected, found),
            ),
            Err(Entry::UnterminatedComponent(component)) => {
                Err(ParseCalendarItemError::UnterminatedComponent(component))
            }
            Err(err) => Err(ParseCalendarItemError::Unknown(err)),
        }
    }

    pub fn components(&self) -> impl Iterator<Item = &ICalendarComponent> {
        self.ical.components.iter()
    }
}

impl Hash for CalendarItem {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.calendar_id.hash(state);
    }
}

impl ToString for CalendarItem {
    fn to_string(&self) -> String {
        self.ical.to_string()
    }
}