io-addressbook 0.0.1

Set of I/O-free coroutines to manage contacts
use std::path::{Path, PathBuf};

use io_fs::io::FsIo;
use io_vdir::{
    constants::VCF,
    coroutines::read_item::{ReadItem, ReadItemError, ReadItemResult},
    item::ItemKind,
};
use thiserror::Error;

use crate::card::Card;

#[derive(Clone, Debug, Error)]
pub enum ReadCardError {
    #[error("Read card error")]
    ReadItem(#[from] ReadItemError),
    #[error("Invalid card path {0}")]
    InvalidAddressbookPath(PathBuf),
    #[error("Invalid addressbook id at {0}")]
    InvalidAddressbookId(PathBuf),
    #[error("Invalid card id at {0}")]
    InvalidCardId(PathBuf),
    #[error("Invalid card at {0}")]
    InvalidCard(PathBuf),
}

#[derive(Clone, Debug)]
pub enum ReadCardResult {
    Ok(Card),
    Err(ReadCardError),
    Io(FsIo),
}

#[derive(Debug)]
pub struct ReadCard(ReadItem);

impl ReadCard {
    pub fn new(
        root: impl AsRef<Path>,
        addressbook_id: impl AsRef<str>,
        id: impl AsRef<str>,
    ) -> Self {
        let path = root
            .as_ref()
            .join(addressbook_id.as_ref())
            .join(id.as_ref())
            .with_extension(VCF);

        Self(ReadItem::new(path))
    }

    pub fn resume(&mut self, input: Option<FsIo>) -> ReadCardResult {
        let item = loop {
            match self.0.resume(input) {
                ReadItemResult::Ok(item) => break item,
                ReadItemResult::Err(err) => return ReadCardResult::Err(err.into()),
                ReadItemResult::Io(io) => return ReadCardResult::Io(io),
            }
        };

        let p = &item.path;

        let Some(parent) = p.parent() else {
            return ReadCardResult::Err(ReadCardError::InvalidAddressbookPath(p.to_owned()));
        };

        let Some(addressbook_id) = parent.file_stem() else {
            return ReadCardResult::Err(ReadCardError::InvalidAddressbookId(p.to_owned()));
        };

        let Some(id) = p.file_stem() else {
            return ReadCardResult::Err(ReadCardError::InvalidCardId(p.to_owned()));
        };

        let ItemKind::Vcard(vcard) = item.kind else {
            return ReadCardResult::Err(ReadCardError::InvalidCard(p.to_owned()));
        };

        let card = Card {
            id: id.to_string_lossy().to_string(),
            addressbook_id: addressbook_id.to_string_lossy().to_string(),
            vcard,
        };

        ReadCardResult::Ok(card)
    }
}