1use core::fmt;
42
43use alloc::{collections::BTreeSet, string::ToString};
44
45use thiserror::Error;
46
47use crate::{coroutine::*, item::locate::*, path::VdirPath, vdir_try};
48
49#[derive(Clone, Debug, Error)]
51pub enum VdirItemDeleteError {
52 #[error("Vdir item delete failed: unexpected arg {0:?}")]
55 UnexpectedArg(Option<VdirReply>),
56
57 #[error(transparent)]
59 Locate(#[from] VdirItemLocateError),
60}
61
62#[derive(Clone, Debug, Default, Eq, PartialEq)]
64pub struct VdirItemDeleteOptions {}
65
66#[derive(Debug)]
68pub struct VdirItemDelete {
69 state: State,
70 #[allow(dead_code)]
71 opts: VdirItemDeleteOptions,
72}
73
74impl VdirItemDelete {
75 pub fn new(
78 collection: impl Into<VdirPath>,
79 id: impl ToString,
80 opts: VdirItemDeleteOptions,
81 ) -> Self {
82 Self {
83 opts,
84 state: State::Locate(VdirItemLocate::new(
85 collection,
86 id,
87 VdirItemLocateOptions::default(),
88 )),
89 }
90 }
91}
92
93impl VdirCoroutine for VdirItemDelete {
94 type Yield = VdirYield;
95 type Return = Result<(), VdirItemDeleteError>;
96
97 fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
98 match (&mut self.state, arg) {
99 (State::Locate(c), arg) => {
100 let out = vdir_try!(c, arg);
101 let paths = BTreeSet::from_iter([out.path]);
102 self.state = State::AwaitRemove;
103 VdirCoroutineState::Yielded(VdirYield::WantsFileRemove(paths))
104 }
105 (State::AwaitRemove, Some(VdirReply::FileRemove)) => {
106 VdirCoroutineState::Complete(Ok(()))
107 }
108 (_, arg) => {
109 let err = VdirItemDeleteError::UnexpectedArg(arg);
110 VdirCoroutineState::Complete(Err(err))
111 }
112 }
113 }
114}
115
116#[derive(Debug)]
117enum State {
118 Locate(VdirItemLocate),
119 AwaitRemove,
120}
121
122impl fmt::Display for State {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match self {
125 Self::Locate(_) => f.write_str("locate item"),
126 Self::AwaitRemove => f.write_str("await remove reply"),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use alloc::collections::BTreeMap;
134
135 use super::*;
136
137 #[test]
138 fn removes_located_item() {
139 let mut cor =
140 VdirItemDelete::new("root/contacts", "alice", VdirItemDeleteOptions::default());
141
142 match cor.resume(None) {
143 VdirCoroutineState::Yielded(VdirYield::WantsFileExists(_)) => {}
144 state => panic!("expected WantsFileExists, got {state:?}"),
145 }
146
147 let vcf = VdirPath::from("root/contacts/alice.vcf");
148 let mut exists = BTreeMap::new();
149 exists.insert(vcf.clone(), true);
150 exists.insert(VdirPath::from("root/contacts/alice.ics"), false);
151
152 let paths = match cor.resume(Some(VdirReply::FileExists(exists))) {
153 VdirCoroutineState::Yielded(VdirYield::WantsFileRemove(paths)) => paths,
154 state => panic!("expected WantsFileRemove, got {state:?}"),
155 };
156 assert!(paths.contains(&vcf));
157
158 match cor.resume(Some(VdirReply::FileRemove)) {
159 VdirCoroutineState::Complete(Ok(())) => {}
160 state => panic!("expected Complete(Ok), got {state:?}"),
161 }
162 }
163
164 #[test]
165 fn locate_error_is_forwarded() {
166 let mut cor =
167 VdirItemDelete::new("root/contacts", "alice", VdirItemDeleteOptions::default());
168 let _ = cor.resume(None);
169
170 let err = match cor.resume(Some(VdirReply::DirCreate)) {
171 VdirCoroutineState::Complete(Err(err)) => err,
172 state => panic!("expected Complete(Err), got {state:?}"),
173 };
174 assert!(matches!(err, VdirItemDeleteError::Locate(_)));
175 }
176}