1use std::collections::HashMap;
32use std::sync::RwLock;
33
34use async_trait::async_trait;
35use serde_json::Value;
36
37use crate::dispatch::JmapRequest;
38use crate::store::{MailStore, StoreError};
39use crate::types::{
40 Attachment, FLAG_SEEN, Mailbox, MailboxCounts, Message, ParsedBody, SubmissionResult,
41};
42
43pub const EXAMPLE_USER: &str = "alice@example.com";
46
47pub struct InMemoryStore {
52 inner: RwLock<Inner>,
53}
54
55struct Inner {
56 mailboxes: Vec<Mailbox>,
57 messages: Vec<Message>,
58 raw_bytes: HashMap<i64, Vec<u8>>,
59 parsed_bodies: HashMap<Vec<u8>, ParsedBody>,
60 mailbox_counts: HashMap<i64, MailboxCounts>,
61
62 list_mailboxes_error: Option<String>,
63 mailbox_status_error: Option<String>,
64 list_messages_error: Option<String>,
65 get_message_error: Option<String>,
66 list_thread_messages_error: Option<String>,
67 update_flags_error: Option<String>,
68 add_flags_error: Option<String>,
69
70 submission_result: SubmissionResult,
71}
72
73impl InMemoryStore {
74 pub fn new() -> Self {
76 Self {
77 inner: RwLock::new(Inner {
78 mailboxes: Vec::new(),
79 messages: Vec::new(),
80 raw_bytes: HashMap::new(),
81 parsed_bodies: HashMap::new(),
82 mailbox_counts: HashMap::new(),
83 list_mailboxes_error: None,
84 mailbox_status_error: None,
85 list_messages_error: None,
86 get_message_error: None,
87 list_thread_messages_error: None,
88 update_flags_error: None,
89 add_flags_error: None,
90 submission_result: SubmissionResult {
91 success: true,
92 message: None,
93 },
94 }),
95 }
96 }
97
98 pub fn with_mailbox(self, id: i64, name: &str) -> Self {
100 self.inner.write().unwrap().mailboxes.push(Mailbox {
101 id,
102 name: name.to_string(),
103 });
104 self
105 }
106
107 pub fn with_message(self, msg: Message) -> Self {
110 self.inner.write().unwrap().messages.push(msg);
111 self
112 }
113
114 pub fn with_message_raw(self, msg_id: i64, raw: Vec<u8>) -> Self {
117 self.inner.write().unwrap().raw_bytes.insert(msg_id, raw);
118 self
119 }
120
121 pub fn with_parsed_body(self, raw: Vec<u8>, parsed: ParsedBody) -> Self {
125 self.inner.write().unwrap().parsed_bodies.insert(raw, parsed);
126 self
127 }
128
129 pub fn with_mailbox_counts(self, mb_id: i64, counts: MailboxCounts) -> Self {
132 self.inner
133 .write()
134 .unwrap()
135 .mailbox_counts
136 .insert(mb_id, counts);
137 self
138 }
139
140 pub fn list_mailboxes_fails(self, msg: &str) -> Self {
142 self.inner.write().unwrap().list_mailboxes_error = Some(msg.to_string());
143 self
144 }
145
146 pub fn mailbox_status_fails(self, msg: &str) -> Self {
148 self.inner.write().unwrap().mailbox_status_error = Some(msg.to_string());
149 self
150 }
151
152 pub fn list_messages_fails(self, msg: &str) -> Self {
154 self.inner.write().unwrap().list_messages_error = Some(msg.to_string());
155 self
156 }
157
158 pub fn get_message_fails(self, msg: &str) -> Self {
160 self.inner.write().unwrap().get_message_error = Some(msg.to_string());
161 self
162 }
163
164 pub fn list_thread_messages_fails(self, msg: &str) -> Self {
166 self.inner.write().unwrap().list_thread_messages_error = Some(msg.to_string());
167 self
168 }
169
170 pub fn update_flags_fails(self, msg: &str) -> Self {
172 self.inner.write().unwrap().update_flags_error = Some(msg.to_string());
173 self
174 }
175
176 pub fn add_flags_fails(self, msg: &str) -> Self {
178 self.inner.write().unwrap().add_flags_error = Some(msg.to_string());
179 self
180 }
181
182 pub fn submission_fails_with(self, msg: &str) -> Self {
184 self.inner.write().unwrap().submission_result = SubmissionResult {
185 success: false,
186 message: Some(msg.to_string()),
187 };
188 self
189 }
190
191 pub fn submission_fails_silently(self) -> Self {
193 self.inner.write().unwrap().submission_result = SubmissionResult {
194 success: false,
195 message: None,
196 };
197 self
198 }
199
200 pub fn flags_for(&self, mailbox_id: i64, uid: u32) -> Option<u32> {
204 self.inner
205 .read()
206 .unwrap()
207 .messages
208 .iter()
209 .find(|m| m.mailbox_id == mailbox_id && m.uid == uid)
210 .map(|m| m.flags)
211 }
212}
213
214impl Default for InMemoryStore {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220#[async_trait]
221impl MailStore for InMemoryStore {
222 async fn list_mailboxes(&self, _user: &str) -> Result<Vec<Mailbox>, StoreError> {
223 let inner = self.inner.read().unwrap();
224 if let Some(ref msg) = inner.list_mailboxes_error {
225 return Err(msg.clone().into());
226 }
227 Ok(inner.mailboxes.clone())
228 }
229
230 async fn mailbox_status(&self, mailbox_id: i64) -> Result<MailboxCounts, StoreError> {
231 let inner = self.inner.read().unwrap();
232 if let Some(ref msg) = inner.mailbox_status_error {
233 return Err(msg.clone().into());
234 }
235 if let Some(counts) = inner.mailbox_counts.get(&mailbox_id) {
236 return Ok(*counts);
237 }
238 let total = inner
240 .messages
241 .iter()
242 .filter(|m| m.mailbox_id == mailbox_id)
243 .count() as u32;
244 let unread = inner
245 .messages
246 .iter()
247 .filter(|m| m.mailbox_id == mailbox_id && m.flags & FLAG_SEEN == 0)
248 .count() as u32;
249 Ok(MailboxCounts { total, unread })
250 }
251
252 async fn list_messages(
253 &self,
254 mailbox_id: i64,
255 offset: u32,
256 limit: u32,
257 ) -> Result<Vec<Message>, StoreError> {
258 let inner = self.inner.read().unwrap();
259 if let Some(ref msg) = inner.list_messages_error {
260 return Err(msg.clone().into());
261 }
262 Ok(inner
263 .messages
264 .iter()
265 .filter(|m| m.mailbox_id == mailbox_id)
266 .skip(offset as usize)
267 .take(limit as usize)
268 .cloned()
269 .collect())
270 }
271
272 async fn get_message_by_db_id(
273 &self,
274 user: &str,
275 id: i64,
276 ) -> Result<Option<Message>, StoreError> {
277 let inner = self.inner.read().unwrap();
278 if let Some(ref msg) = inner.get_message_error {
279 return Err(msg.clone().into());
280 }
281 Ok(inner
282 .messages
283 .iter()
284 .find(|m| m.id == id && m.user_address == user)
285 .cloned())
286 }
287
288 async fn list_thread_messages(
289 &self,
290 user: &str,
291 thread_id: &str,
292 ) -> Result<Vec<Message>, StoreError> {
293 let inner = self.inner.read().unwrap();
294 if let Some(ref msg) = inner.list_thread_messages_error {
295 return Err(msg.clone().into());
296 }
297 Ok(inner
298 .messages
299 .iter()
300 .filter(|m| m.thread_id == thread_id && m.user_address == user)
301 .cloned()
302 .collect())
303 }
304
305 async fn update_flags(
306 &self,
307 mailbox_id: i64,
308 uid: u32,
309 flags: u32,
310 ) -> Result<(), StoreError> {
311 let mut inner = self.inner.write().unwrap();
312 if let Some(ref msg) = inner.update_flags_error {
313 return Err(msg.clone().into());
314 }
315 if let Some(m) = inner
316 .messages
317 .iter_mut()
318 .find(|m| m.mailbox_id == mailbox_id && m.uid == uid)
319 {
320 m.flags = flags;
321 }
322 Ok(())
323 }
324
325 async fn add_flags(&self, mailbox_id: i64, uid: u32, flags: u32) -> Result<(), StoreError> {
326 let mut inner = self.inner.write().unwrap();
327 if let Some(ref msg) = inner.add_flags_error {
328 return Err(msg.clone().into());
329 }
330 if let Some(m) = inner
331 .messages
332 .iter_mut()
333 .find(|m| m.mailbox_id == mailbox_id && m.uid == uid)
334 {
335 m.flags |= flags;
336 }
337 Ok(())
338 }
339
340 async fn read_message_raw(&self, message: &Message) -> Option<Vec<u8>> {
341 self.inner.read().unwrap().raw_bytes.get(&message.id).cloned()
342 }
343
344 fn parse_message(&self, raw: &[u8]) -> ParsedBody {
345 self.inner
346 .read()
347 .unwrap()
348 .parsed_bodies
349 .get(raw)
350 .cloned()
351 .unwrap_or_default()
352 }
353
354 async fn submit_message(
355 &self,
356 _user: &str,
357 _message: &Message,
358 _raw: &[u8],
359 ) -> SubmissionResult {
360 self.inner.read().unwrap().submission_result.clone()
361 }
362}
363
364pub fn make_message(id: i64, mailbox_id: i64, user: &str) -> Message {
368 Message {
369 id,
370 mailbox_id,
371 uid: id as u32,
372 sender: "Sender <sender@example.com>".to_string(),
373 recipients: user.to_string(),
374 subject: format!("message {id}"),
375 date: 1_700_000_000 + id,
376 size: 256,
377 flags: 0,
378 internal_date: 1_700_000_000 + id,
379 message_id: format!("msg-{id}@example.com"),
380 in_reply_to: String::new(),
381 thread_id: format!("thread-{id}"),
382 user_address: user.to_string(),
383 new_content: Some(format!("snippet {id}")),
384 blob_id: format!("blob-{id}"),
385 }
386}
387
388pub fn make_request(calls: &[(&str, Value, &str)]) -> JmapRequest {
391 JmapRequest {
392 using: vec!["urn:ietf:params:jmap:mail".to_string()],
393 method_calls: calls
394 .iter()
395 .map(|(m, a, c)| (m.to_string(), a.clone(), c.to_string()))
396 .collect(),
397 }
398}
399
400pub fn parsed_with_text(text: &str) -> ParsedBody {
402 ParsedBody {
403 text: Some(text.to_string()),
404 html: None,
405 attachments: vec![],
406 }
407}
408
409pub fn parsed_with_attachment(filename: &str, content_type: &str, size: u32) -> ParsedBody {
412 ParsedBody {
413 text: None,
414 html: None,
415 attachments: vec![Attachment {
416 filename: filename.to_string(),
417 content_type: content_type.to_string(),
418 size,
419 }],
420 }
421}