1use core::{fmt, mem};
48
49use alloc::{collections::BTreeSet, string::ToString};
50
51use thiserror::Error;
52
53use crate::{
54 coroutine::*,
55 item::{VdirItem, VdirItemKind, locate::*},
56 path::VdirPath,
57 vdir_try,
58};
59
60#[derive(Clone, Debug, Error)]
62pub enum VdirItemGetError {
63 #[error("Vdir item get failed: unexpected arg {0:?}")]
66 UnexpectedArg(Option<VdirReply>),
67
68 #[error(transparent)]
70 Locate(#[from] VdirItemLocateError),
71}
72
73#[derive(Clone, Debug, Default, Eq, PartialEq)]
75pub struct VdirItemGetOptions {}
76
77#[derive(Debug)]
79pub struct VdirItemGet {
80 state: State,
81 #[allow(dead_code)]
82 opts: VdirItemGetOptions,
83}
84
85impl VdirItemGet {
86 pub fn new(
89 collection: impl Into<VdirPath>,
90 id: impl ToString,
91 opts: VdirItemGetOptions,
92 ) -> Self {
93 Self {
94 opts,
95 state: State::Locate(VdirItemLocate::new(
96 collection,
97 id,
98 VdirItemLocateOptions::default(),
99 )),
100 }
101 }
102}
103
104impl VdirCoroutine for VdirItemGet {
105 type Yield = VdirYield;
106 type Return = Result<VdirItem, VdirItemGetError>;
107
108 fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return> {
109 match (&mut self.state, arg) {
110 (State::Locate(c), arg) => {
111 let out = vdir_try!(c, arg);
112 let paths = BTreeSet::from_iter([out.path.clone()]);
113 self.state = State::AwaitRead {
114 path: out.path,
115 kind: out.kind,
116 };
117 VdirCoroutineState::Yielded(VdirYield::WantsFileRead(paths))
118 }
119 (State::AwaitRead { path, kind }, Some(VdirReply::FileRead(mut map))) => {
120 let path = mem::take(path);
121 let kind = *kind;
122 let contents = map.remove(&path).unwrap_or_default();
123 VdirCoroutineState::Complete(Ok(VdirItem {
124 path,
125 kind,
126 contents,
127 }))
128 }
129 (_, arg) => {
130 let err = VdirItemGetError::UnexpectedArg(arg);
131 VdirCoroutineState::Complete(Err(err))
132 }
133 }
134 }
135}
136
137#[derive(Debug)]
138enum State {
139 Locate(VdirItemLocate),
140 AwaitRead { path: VdirPath, kind: VdirItemKind },
141}
142
143impl fmt::Display for State {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::Locate(_) => f.write_str("locate item"),
147 Self::AwaitRead { .. } => f.write_str("await read reply"),
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use alloc::collections::BTreeMap;
155
156 use super::*;
157
158 #[test]
159 fn locates_then_reads_contents() {
160 let mut cor = VdirItemGet::new("root/contacts", "alice", VdirItemGetOptions::default());
161
162 match cor.resume(None) {
163 VdirCoroutineState::Yielded(VdirYield::WantsFileExists(_)) => {}
164 state => panic!("expected WantsFileExists, got {state:?}"),
165 }
166
167 let vcf = VdirPath::from("root/contacts/alice.vcf");
168 let mut exists = BTreeMap::new();
169 exists.insert(vcf.clone(), true);
170 exists.insert(VdirPath::from("root/contacts/alice.ics"), false);
171
172 let paths = match cor.resume(Some(VdirReply::FileExists(exists))) {
173 VdirCoroutineState::Yielded(VdirYield::WantsFileRead(paths)) => paths,
174 state => panic!("expected WantsFileRead, got {state:?}"),
175 };
176 assert!(paths.contains(&vcf));
177
178 let mut contents = BTreeMap::new();
179 contents.insert(vcf.clone(), b"BEGIN:VCARD".to_vec());
180 let item = match cor.resume(Some(VdirReply::FileRead(contents))) {
181 VdirCoroutineState::Complete(Ok(item)) => item,
182 state => panic!("expected Complete(Ok), got {state:?}"),
183 };
184 assert_eq!(item.path, vcf);
185 assert_eq!(item.kind, VdirItemKind::Vcard);
186 assert_eq!(item.contents, b"BEGIN:VCARD");
187 }
188
189 #[test]
190 fn locate_error_is_forwarded() {
191 let mut cor = VdirItemGet::new("root/contacts", "alice", VdirItemGetOptions::default());
192 let _ = cor.resume(None);
193
194 let err = match cor.resume(Some(VdirReply::DirCreate)) {
195 VdirCoroutineState::Complete(Err(err)) => err,
196 state => panic!("expected Complete(Err), got {state:?}"),
197 };
198 assert!(matches!(err, VdirItemGetError::Locate(_)));
199 }
200}