io-addressbook 0.0.1

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

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

pub use calcard::vcard::*;

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

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct Card {
    pub id: String,
    pub addressbook_id: String,
    #[serde(serialize_with = "Card::serialize_vcard")]
    pub vcard: VCard,
}

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

    pub fn serialize_vcard<S: Serializer>(vcard: &VCard, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(&vcard.to_string())
    }

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

    pub fn entries(&self) -> impl Iterator<Item = &VCardEntry> {
        self.vcard.entries.iter()
    }
}

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

impl ToString for Card {
    fn to_string(&self) -> String {
        self.vcard.to_string()
    }
}