Skip to main content

io_vdir/item/
delete.rs

1//! I/O-free coroutine deleting a Vdir item by its ID.
2//!
3//! Locates the item file via [`VdirItemLocate`], then removes it.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! use std::fs;
9//!
10//! use io_vdir::{coroutine::*, item::delete::*};
11//!
12//! let opts = VdirItemDeleteOptions::default();
13//! let mut coroutine = VdirItemDelete::new("/tmp/vdir/contacts", "alice", opts);
14//! let mut arg = None;
15//!
16//! loop {
17//!     match coroutine.resume(arg.take()) {
18//!         VdirCoroutineState::Yielded(VdirYield::WantsFileExists(paths)) => {
19//!             let map = paths
20//!                 .into_iter()
21//!                 .map(|p| {
22//!                     let ok = fs::metadata(p.as_str()).map(|m| m.is_file()).unwrap_or(false);
23//!                     (p, ok)
24//!                 })
25//!                 .collect();
26//!             arg = Some(VdirReply::FileExists(map));
27//!         }
28//!         VdirCoroutineState::Yielded(VdirYield::WantsFileRemove(paths)) => {
29//!             for path in paths {
30//!                 fs::remove_file(path.as_str()).unwrap();
31//!             }
32//!             arg = Some(VdirReply::FileRemove);
33//!         }
34//!         VdirCoroutineState::Complete(Ok(())) => break,
35//!         VdirCoroutineState::Complete(Err(err)) => panic!("{err}"),
36//!         state => panic!("unexpected state {state:?}"),
37//!     }
38//! }
39//! ```
40
41use 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/// Failure causes during a [`VdirItemDelete`] step.
50#[derive(Clone, Debug, Error)]
51pub enum VdirItemDeleteError {
52    /// The driver fed back a reply that does not match the pending
53    /// request.
54    #[error("Vdir item delete failed: unexpected arg {0:?}")]
55    UnexpectedArg(Option<VdirReply>),
56
57    /// The inner locate coroutine failed.
58    #[error(transparent)]
59    Locate(#[from] VdirItemLocateError),
60}
61
62/// Options for [`VdirItemDelete::new`].
63#[derive(Clone, Debug, Default, Eq, PartialEq)]
64pub struct VdirItemDeleteOptions {}
65
66/// Locates a Vdir item by its ID and removes it.
67#[derive(Debug)]
68pub struct VdirItemDelete {
69    state: State,
70    #[allow(dead_code)]
71    opts: VdirItemDeleteOptions,
72}
73
74impl VdirItemDelete {
75    /// Creates a new coroutine that will delete item `id` from
76    /// `collection`.
77    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}