use core::{fmt, mem};
use alloc::{collections::BTreeSet, string::ToString};
use thiserror::Error;
use crate::{
coroutine::*,
item::{VdirItem, VdirItemKind, locate::*},
path::VdirPath,
vdir_try,
};
#[derive(Clone, Debug, Error)]
pub enum VdirItemGetError {
#[error("Vdir item get failed: unexpected arg {0:?}")]
UnexpectedArg(Option<VdirReply>),
#[error(transparent)]
Locate(#[from] VdirItemLocateError),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VdirItemGetOptions {}
#[derive(Debug)]
pub struct VdirItemGet {
state: State,
#[allow(dead_code)]
opts: VdirItemGetOptions,
}
impl VdirItemGet {
pub fn new(
collection: impl Into<VdirPath>,
id: impl ToString,
opts: VdirItemGetOptions,
) -> Self {
Self {
opts,
state: State::Locate(VdirItemLocate::new(
collection,
id,
VdirItemLocateOptions::default(),
)),
}
}
}
impl VdirCoroutine for VdirItemGet {
type Yield = VdirYield;
type Return = Result<VdirItem, VdirItemGetError>;
fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
match (&mut self.state, arg) {
(State::Locate(c), arg) => {
let out = vdir_try!(c, arg);
let paths = BTreeSet::from_iter([out.path.clone()]);
self.state = State::AwaitRead {
path: out.path,
kind: out.kind,
};
VdirCoroutineState::Yielded(VdirYield::WantsFileRead(paths))
}
(State::AwaitRead { path, kind }, Some(VdirReply::FileRead(mut map))) => {
let path = mem::take(path);
let kind = *kind;
let contents = map.remove(&path).unwrap_or_default();
VdirCoroutineState::Complete(Ok(VdirItem {
path,
kind,
contents,
}))
}
(_, arg) => {
let err = VdirItemGetError::UnexpectedArg(arg);
VdirCoroutineState::Complete(Err(err))
}
}
}
}
#[derive(Debug)]
enum State {
Locate(VdirItemLocate),
AwaitRead { path: VdirPath, kind: VdirItemKind },
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Locate(_) => f.write_str("locate item"),
Self::AwaitRead { .. } => f.write_str("await read reply"),
}
}
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeMap;
use super::*;
#[test]
fn locates_then_reads_contents() {
let mut cor = VdirItemGet::new("root/contacts", "alice", VdirItemGetOptions::default());
match cor.resume(None) {
VdirCoroutineState::Yielded(VdirYield::WantsFileExists(_)) => {}
state => panic!("expected WantsFileExists, got {state:?}"),
}
let vcf = VdirPath::from("root/contacts/alice.vcf");
let mut exists = BTreeMap::new();
exists.insert(vcf.clone(), true);
exists.insert(VdirPath::from("root/contacts/alice.ics"), false);
let paths = match cor.resume(Some(VdirReply::FileExists(exists))) {
VdirCoroutineState::Yielded(VdirYield::WantsFileRead(paths)) => paths,
state => panic!("expected WantsFileRead, got {state:?}"),
};
assert!(paths.contains(&vcf));
let mut contents = BTreeMap::new();
contents.insert(vcf.clone(), b"BEGIN:VCARD".to_vec());
let item = match cor.resume(Some(VdirReply::FileRead(contents))) {
VdirCoroutineState::Complete(Ok(item)) => item,
state => panic!("expected Complete(Ok), got {state:?}"),
};
assert_eq!(item.path, vcf);
assert_eq!(item.kind, VdirItemKind::Vcard);
assert_eq!(item.contents, b"BEGIN:VCARD");
}
#[test]
fn locate_error_is_forwarded() {
let mut cor = VdirItemGet::new("root/contacts", "alice", VdirItemGetOptions::default());
let _ = cor.resume(None);
let err = match cor.resume(Some(VdirReply::DirCreate)) {
VdirCoroutineState::Complete(Err(err)) => err,
state => panic!("expected Complete(Err), got {state:?}"),
};
assert!(matches!(err, VdirItemGetError::Locate(_)));
}
}