use core::{fmt, mem};
use alloc::collections::{BTreeMap, BTreeSet};
use thiserror::Error;
use crate::{
collection::{COLOR, DESCRIPTION, DISPLAYNAME, VdirCollection},
coroutine::*,
};
#[derive(Clone, Debug, Error)]
pub enum VdirCollectionCreateError {
#[error("Vdir collection create failed: unexpected arg {0:?}")]
UnexpectedArg(Option<VdirReply>),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VdirCollectionCreateOptions {}
#[derive(Debug)]
pub struct VdirCollectionCreate {
state: State,
#[allow(dead_code)]
opts: VdirCollectionCreateOptions,
}
impl VdirCollectionCreate {
pub fn new(collection: VdirCollection, opts: VdirCollectionCreateOptions) -> Self {
Self {
opts,
state: State::Start(collection),
}
}
}
impl VdirCoroutine for VdirCollectionCreate {
type Yield = VdirYield;
type Return = Result<(), VdirCollectionCreateError>;
fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
match (&mut self.state, arg) {
(State::Start(collection), None) => {
let collection = mem::take(collection);
let dirs = BTreeSet::from_iter([collection.path.clone()]);
self.state = State::AwaitDirCreate(collection);
VdirCoroutineState::Yielded(VdirYield::WantsDirCreate(dirs))
}
(State::AwaitDirCreate(collection), Some(VdirReply::DirCreate)) => {
let collection = mem::take(collection);
let mut files = BTreeMap::new();
if let Some(name) = collection.display_name.filter(|s| !s.is_empty()) {
files.insert(collection.path.join(DISPLAYNAME), name.into_bytes());
}
if let Some(desc) = collection.description.filter(|s| !s.is_empty()) {
files.insert(collection.path.join(DESCRIPTION), desc.into_bytes());
}
if let Some(color) = collection.color.filter(|s| !s.is_empty()) {
files.insert(collection.path.join(COLOR), color.into_bytes());
}
if files.is_empty() {
return VdirCoroutineState::Complete(Ok(()));
}
self.state = State::AwaitFileCreate;
VdirCoroutineState::Yielded(VdirYield::WantsFileCreate(files))
}
(State::AwaitFileCreate, Some(VdirReply::FileCreate)) => {
VdirCoroutineState::Complete(Ok(()))
}
(_, arg) => {
let err = VdirCollectionCreateError::UnexpectedArg(arg);
VdirCoroutineState::Complete(Err(err))
}
}
}
}
#[derive(Debug)]
enum State {
Start(VdirCollection),
AwaitDirCreate(VdirCollection),
AwaitFileCreate,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Start(_) => f.write_str("start"),
Self::AwaitDirCreate(_) => f.write_str("await dir create reply"),
Self::AwaitFileCreate => f.write_str("await file create reply"),
}
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use crate::path::VdirPath;
use super::*;
#[test]
fn bare_collection_creates_dir_only() {
let collection = VdirCollection::from_path("root/contacts");
let mut cor = VdirCollectionCreate::new(collection, VdirCollectionCreateOptions::default());
let dirs = expect_wants_dir_create(&mut cor);
assert!(dirs.contains(&VdirPath::from("root/contacts")));
expect_complete_ok(&mut cor, Some(VdirReply::DirCreate));
}
#[test]
fn metadata_collection_writes_marker_files() {
let collection = VdirCollection {
path: VdirPath::from("root/contacts"),
display_name: Some("Contacts".into()),
description: None,
color: Some("#3366ff".into()),
};
let mut cor = VdirCollectionCreate::new(collection, VdirCollectionCreateOptions::default());
let _ = expect_wants_dir_create(&mut cor);
let files = match cor.resume(Some(VdirReply::DirCreate)) {
VdirCoroutineState::Yielded(VdirYield::WantsFileCreate(files)) => files,
state => panic!("expected WantsFileCreate, got {state:?}"),
};
assert!(files.contains_key(&VdirPath::from("root/contacts/displayname")));
assert!(files.contains_key(&VdirPath::from("root/contacts/color")));
assert!(!files.contains_key(&VdirPath::from("root/contacts/description")));
expect_complete_ok(&mut cor, Some(VdirReply::FileCreate));
}
#[test]
fn empty_metadata_fields_are_skipped() {
let collection = VdirCollection {
path: VdirPath::from("root/contacts"),
display_name: Some(String::new()),
description: None,
color: None,
};
let mut cor = VdirCollectionCreate::new(collection, VdirCollectionCreateOptions::default());
let _ = expect_wants_dir_create(&mut cor);
expect_complete_ok(&mut cor, Some(VdirReply::DirCreate));
}
#[test]
fn unexpected_reply_returns_error() {
let mut cor = VdirCollectionCreate::new(
VdirCollection::from_path("root/contacts"),
VdirCollectionCreateOptions::default(),
);
let _ = expect_wants_dir_create(&mut cor);
let err = expect_complete_err(&mut cor, Some(VdirReply::FileCreate));
assert!(matches!(err, VdirCollectionCreateError::UnexpectedArg(_)));
}
fn expect_wants_dir_create(cor: &mut VdirCollectionCreate) -> BTreeSet<VdirPath> {
match cor.resume(None) {
VdirCoroutineState::Yielded(VdirYield::WantsDirCreate(paths)) => paths,
state => panic!("expected WantsDirCreate, got {state:?}"),
}
}
fn expect_complete_ok(cor: &mut VdirCollectionCreate, arg: Option<VdirReply>) {
match cor.resume(arg) {
VdirCoroutineState::Complete(Ok(())) => {}
state => panic!("expected Complete(Ok), got {state:?}"),
}
}
fn expect_complete_err(
cor: &mut VdirCollectionCreate,
arg: Option<VdirReply>,
) -> VdirCollectionCreateError {
match cor.resume(arg) {
VdirCoroutineState::Complete(Err(err)) => err,
state => panic!("expected Complete(Err), got {state:?}"),
}
}
}