1use core::sync::atomic::AtomicBool;
28
29use alloc::{string::String, sync::Arc, vec::Vec};
30
31use std::sync::mpsc::Sender;
32
33use thiserror::Error;
34
35#[cfg(feature = "imap")]
36use crate::imap::client::{ImapClientError, ImapClientStd};
37#[cfg(feature = "jmap")]
38use crate::jmap::client::{JmapClientError, JmapClientStd};
39#[cfg(feature = "m2dir")]
40use crate::m2dir::client::{M2dirClient, M2dirClientError};
41#[cfg(feature = "maildir")]
42use crate::maildir::client::{MaildirClient, MaildirClientError};
43#[cfg(feature = "search")]
44use crate::search::query::SearchEmailsQuery;
45#[cfg(feature = "smtp")]
46use crate::smtp::client::{SmtpClientError, SmtpClientStd};
47use crate::{
48 envelope::event::WatchEvent,
49 envelope::types::{Envelope, EnvelopeDiff},
50 flag::types::{Flag, FlagOp},
51 mailbox::types::{Mailbox, MailboxDiff},
52};
53
54#[cfg(feature = "imap")]
55use io_imap::types::core::{IString, NString};
56#[cfg(feature = "smtp")]
57#[cfg(any(
58 feature = "rustls-ring",
59 feature = "rustls-aws",
60 feature = "native-tls"
61))]
62use io_smtp::rfc5321::types::ehlo_domain::EhloDomain;
63#[cfg(feature = "imap")]
64#[cfg(any(
65 feature = "rustls-ring",
66 feature = "rustls-aws",
67 feature = "native-tls"
68))]
69use pimalaya_stream::sasl::Sasl as ImapSasl;
70#[cfg(feature = "jmap")]
71#[cfg(any(
72 feature = "rustls-ring",
73 feature = "rustls-aws",
74 feature = "native-tls"
75))]
76use secrecy::SecretString;
77#[cfg(any(feature = "imap", feature = "jmap", feature = "smtp"))]
78#[cfg(any(
79 feature = "rustls-ring",
80 feature = "rustls-aws",
81 feature = "native-tls"
82))]
83use {pimalaya_stream::tls::Tls, url::Url};
84
85#[derive(Debug, Error)]
90pub enum EmailClientStdError {
91 #[cfg(feature = "imap")]
92 #[error(transparent)]
93 Imap(#[from] ImapClientError),
94 #[cfg(feature = "jmap")]
95 #[error(transparent)]
96 Jmap(#[from] JmapClientError),
97 #[cfg(feature = "smtp")]
98 #[error(transparent)]
99 Smtp(#[from] SmtpClientError),
100 #[cfg(feature = "maildir")]
101 #[error(transparent)]
102 Maildir(#[from] MaildirClientError),
103 #[cfg(feature = "m2dir")]
104 #[error(transparent)]
105 M2dir(#[from] M2dirClientError),
106 #[error("No backend supporting this operation is registered")]
107 NoBackendRegistered,
108 #[error("Registered backend does not support this operation")]
109 UnsupportedOperation,
110}
111
112#[derive(Default)]
120pub struct EmailClientStd {
121 #[cfg(feature = "imap")]
122 pub imap: Option<ImapClientStd>,
123 #[cfg(feature = "jmap")]
124 pub jmap: Option<JmapClientStd>,
125 #[cfg(feature = "smtp")]
126 pub smtp: Option<SmtpClientStd>,
127 #[cfg(feature = "maildir")]
128 pub maildir: Option<MaildirClient>,
129 #[cfg(feature = "m2dir")]
130 pub m2dir: Option<M2dirClient>,
131}
132
133impl EmailClientStd {
134 pub fn new() -> Self {
136 Self::default()
137 }
138
139 #[cfg(feature = "imap")]
141 pub fn with_imap(mut self, client: ImapClientStd) -> Self {
142 self.imap = Some(client);
143 self
144 }
145
146 #[cfg(feature = "jmap")]
148 pub fn with_jmap(mut self, client: JmapClientStd) -> Self {
149 self.jmap = Some(client);
150 self
151 }
152
153 #[cfg(feature = "smtp")]
155 pub fn with_smtp(mut self, client: SmtpClientStd) -> Self {
156 self.smtp = Some(client);
157 self
158 }
159
160 #[cfg(feature = "maildir")]
162 pub fn with_maildir(mut self, client: MaildirClient) -> Self {
163 self.maildir = Some(client);
164 self
165 }
166
167 #[cfg(feature = "m2dir")]
169 pub fn with_m2dir(mut self, client: M2dirClient) -> Self {
170 self.m2dir = Some(client);
171 self
172 }
173
174 #[cfg(feature = "imap")]
178 #[cfg(any(
179 feature = "rustls-ring",
180 feature = "rustls-aws",
181 feature = "native-tls"
182 ))]
183 pub fn connect_imap(
184 self,
185 url: &Url,
186 tls: &Tls,
187 starttls: bool,
188 sasl: Option<impl Into<ImapSasl>>,
189 auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
190 ) -> Result<Self, EmailClientStdError> {
191 Ok(self.with_imap(ImapClientStd::connect(url, tls, starttls, sasl, auto_id)?))
192 }
193
194 #[cfg(feature = "jmap")]
197 #[cfg(any(
198 feature = "rustls-ring",
199 feature = "rustls-aws",
200 feature = "native-tls"
201 ))]
202 pub fn connect_jmap(
203 self,
204 url: &Url,
205 tls: &Tls,
206 http_auth: SecretString,
207 ) -> Result<Self, EmailClientStdError> {
208 Ok(self.with_jmap(JmapClientStd::connect(url, tls, http_auth)?))
209 }
210
211 #[cfg(feature = "smtp")]
214 #[cfg(any(
215 feature = "rustls-ring",
216 feature = "rustls-aws",
217 feature = "native-tls"
218 ))]
219 pub fn connect_smtp(
220 self,
221 url: &Url,
222 tls: &Tls,
223 starttls: bool,
224 domain: EhloDomain<'_>,
225 sasl: Option<impl Into<pimalaya_stream::sasl::Sasl>>,
226 ) -> Result<Self, EmailClientStdError> {
227 Ok(self.with_smtp(SmtpClientStd::connect(url, tls, starttls, domain, sasl)?))
228 }
229
230 pub fn ping(&mut self) -> Result<(), EmailClientStdError> {
236 #[cfg(feature = "imap")]
237 if let Some(c) = self.imap.as_mut() {
238 c.ping()?;
239 }
240 #[cfg(feature = "smtp")]
241 if let Some(c) = self.smtp.as_mut() {
242 c.ping()?;
243 }
244 Ok(())
245 }
246
247 pub fn list_mailboxes(
252 &mut self,
253 with_counts: bool,
254 ) -> Result<Vec<Mailbox>, EmailClientStdError> {
255 #[cfg(feature = "maildir")]
256 if let Some(c) = &self.maildir {
257 return Ok(c.list_mailboxes(with_counts)?);
258 }
259 #[cfg(feature = "m2dir")]
260 if let Some(c) = &self.m2dir {
261 return Ok(c.list_mailboxes(with_counts)?);
262 }
263 #[cfg(feature = "jmap")]
264 if let Some(c) = self.jmap.as_mut() {
265 return Ok(c.list_mailboxes(with_counts)?);
266 }
267 #[cfg(feature = "imap")]
268 if let Some(c) = self.imap.as_mut() {
269 return Ok(c.list_mailboxes(with_counts)?);
270 }
271 Err(EmailClientStdError::NoBackendRegistered)
272 }
273
274 pub fn list_envelopes(
278 &mut self,
279 mailbox: &str,
280 page: Option<u32>,
281 page_size: Option<u32>,
282 with_attachment: bool,
283 ) -> Result<Vec<Envelope>, EmailClientStdError> {
284 #[cfg(feature = "maildir")]
285 if let Some(c) = &self.maildir {
286 return Ok(c.list_envelopes(mailbox, page, page_size, with_attachment)?);
287 }
288 #[cfg(feature = "m2dir")]
289 if let Some(c) = &self.m2dir {
290 return Ok(c.list_envelopes(mailbox, page, page_size, with_attachment)?);
291 }
292 #[cfg(feature = "jmap")]
293 if let Some(c) = self.jmap.as_mut() {
294 return Ok(c.list_envelopes(mailbox, page, page_size)?);
295 }
296 #[cfg(feature = "imap")]
297 if let Some(c) = self.imap.as_mut() {
298 return Ok(c.list_envelopes(mailbox, page, page_size, with_attachment)?);
299 }
300 let _ = (mailbox, page, page_size, with_attachment);
301 Err(EmailClientStdError::NoBackendRegistered)
302 }
303
304 #[cfg(feature = "search")]
306 pub fn search_envelopes(
307 &mut self,
308 mailbox: &str,
309 query: Option<&SearchEmailsQuery>,
310 page: Option<u32>,
311 page_size: Option<u32>,
312 with_attachment: bool,
313 ) -> Result<Vec<Envelope>, EmailClientStdError> {
314 #[cfg(feature = "maildir")]
315 if let Some(c) = &self.maildir {
316 return Ok(c.search_envelopes(mailbox, query, page, page_size, with_attachment)?);
317 }
318 #[cfg(feature = "m2dir")]
319 if let Some(c) = &self.m2dir {
320 return Ok(c.search_envelopes(mailbox, query, page, page_size, with_attachment)?);
321 }
322 #[cfg(feature = "jmap")]
323 if let Some(c) = self.jmap.as_mut() {
324 return Ok(c.search_envelopes(mailbox, query, page, page_size)?);
325 }
326 #[cfg(feature = "imap")]
327 if let Some(c) = self.imap.as_mut() {
328 return Ok(c.search_envelopes(mailbox, query, page, page_size, with_attachment)?);
329 }
330 let _ = (mailbox, query, page, page_size, with_attachment);
331 Err(EmailClientStdError::NoBackendRegistered)
332 }
333
334 pub fn store_flags(
336 &mut self,
337 mailbox: &str,
338 ids: &[&str],
339 flags: &[Flag],
340 op: FlagOp,
341 ) -> Result<(), EmailClientStdError> {
342 #[cfg(feature = "maildir")]
343 if let Some(c) = &self.maildir {
344 return Ok(c.store_flags(mailbox, ids, flags, op)?);
345 }
346 #[cfg(feature = "m2dir")]
347 if let Some(c) = &self.m2dir {
348 return Ok(c.store_flags(mailbox, ids, flags, op)?);
349 }
350 #[cfg(feature = "jmap")]
351 if let Some(c) = self.jmap.as_mut() {
352 return Ok(c.store_flags(mailbox, ids, flags, op)?);
353 }
354 #[cfg(feature = "imap")]
355 if let Some(c) = self.imap.as_mut() {
356 return Ok(c.store_flags(mailbox, ids, flags, op)?);
357 }
358 let _ = (mailbox, ids, flags, op);
359 Err(EmailClientStdError::NoBackendRegistered)
360 }
361
362 pub fn get_message(&mut self, mailbox: &str, id: &str) -> Result<Vec<u8>, EmailClientStdError> {
364 #[cfg(feature = "maildir")]
365 if let Some(c) = &self.maildir {
366 return Ok(c.get_message(mailbox, id)?);
367 }
368 #[cfg(feature = "m2dir")]
369 if let Some(c) = &self.m2dir {
370 return Ok(c.get_message(mailbox, id)?);
371 }
372 #[cfg(feature = "jmap")]
373 if let Some(c) = self.jmap.as_mut() {
374 return Ok(c.get_message(mailbox, id)?);
375 }
376 #[cfg(feature = "imap")]
377 if let Some(c) = self.imap.as_mut() {
378 return Ok(c.get_message(mailbox, id)?);
379 }
380 let _ = (mailbox, id);
381 Err(EmailClientStdError::NoBackendRegistered)
382 }
383
384 pub fn add_message(
387 &mut self,
388 mailbox: &str,
389 flags: &[Flag],
390 raw: Vec<u8>,
391 ) -> Result<String, EmailClientStdError> {
392 #[cfg(feature = "maildir")]
393 if let Some(c) = &self.maildir {
394 return Ok(c.add_message(mailbox, flags, raw)?);
395 }
396 #[cfg(feature = "m2dir")]
397 if let Some(c) = &self.m2dir {
398 return Ok(c.add_message(mailbox, flags, raw)?);
399 }
400 #[cfg(feature = "jmap")]
401 if let Some(c) = self.jmap.as_mut() {
402 return Ok(c.add_message(mailbox, flags, raw)?);
403 }
404 #[cfg(feature = "imap")]
405 if let Some(c) = self.imap.as_mut() {
406 return Ok(c.add_message(mailbox, flags, raw)?);
407 }
408 let _ = (mailbox, flags, raw);
409 Err(EmailClientStdError::NoBackendRegistered)
410 }
411
412 pub fn create_mailbox(&mut self, name: &str) -> Result<(), EmailClientStdError> {
414 #[cfg(feature = "maildir")]
415 if let Some(c) = &self.maildir {
416 return Ok(c.create_mailbox(name)?);
417 }
418 #[cfg(feature = "m2dir")]
419 if let Some(c) = &self.m2dir {
420 return Ok(c.create_mailbox(name)?);
421 }
422 #[cfg(feature = "jmap")]
423 if let Some(c) = self.jmap.as_mut() {
424 return Ok(c.create_mailbox(name)?);
425 }
426 #[cfg(feature = "imap")]
427 if let Some(c) = self.imap.as_mut() {
428 return Ok(c.create_mailbox(name)?);
429 }
430 let _ = name;
431 Err(EmailClientStdError::NoBackendRegistered)
432 }
433
434 pub fn delete_mailbox(&mut self, name: &str) -> Result<(), EmailClientStdError> {
436 #[cfg(feature = "maildir")]
437 if let Some(c) = &self.maildir {
438 return Ok(c.delete_mailbox(name)?);
439 }
440 #[cfg(feature = "m2dir")]
441 if let Some(c) = &self.m2dir {
442 return Ok(c.delete_mailbox(name)?);
443 }
444 #[cfg(feature = "jmap")]
445 if let Some(c) = self.jmap.as_mut() {
446 return Ok(c.delete_mailbox(name)?);
447 }
448 #[cfg(feature = "imap")]
449 if let Some(c) = self.imap.as_mut() {
450 return Ok(c.delete_mailbox(name)?);
451 }
452 let _ = name;
453 Err(EmailClientStdError::NoBackendRegistered)
454 }
455
456 pub fn delete_message(&mut self, mailbox: &str, id: &str) -> Result<(), EmailClientStdError> {
458 #[cfg(feature = "maildir")]
459 if let Some(c) = &self.maildir {
460 return Ok(c.delete_message(mailbox, id)?);
461 }
462 #[cfg(feature = "m2dir")]
463 if let Some(c) = &self.m2dir {
464 return Ok(c.delete_message(mailbox, id)?);
465 }
466 #[cfg(feature = "jmap")]
467 if let Some(c) = self.jmap.as_mut() {
468 return Ok(c.delete_message(mailbox, id)?);
469 }
470 #[cfg(feature = "imap")]
471 if let Some(c) = self.imap.as_mut() {
472 return Ok(c.delete_message(mailbox, id)?);
473 }
474 let _ = (mailbox, id);
475 Err(EmailClientStdError::NoBackendRegistered)
476 }
477
478 pub fn copy_messages(
480 &mut self,
481 from: &str,
482 to: &str,
483 ids: &[&str],
484 ) -> Result<(), EmailClientStdError> {
485 #[cfg(feature = "maildir")]
486 if let Some(c) = &self.maildir {
487 return Ok(c.copy_messages(from, to, ids)?);
488 }
489 #[cfg(feature = "m2dir")]
490 if let Some(c) = &self.m2dir {
491 return Ok(c.copy_messages(from, to, ids)?);
492 }
493 #[cfg(feature = "jmap")]
494 if let Some(c) = self.jmap.as_mut() {
495 return Ok(c.copy_messages(from, to, ids)?);
496 }
497 #[cfg(feature = "imap")]
498 if let Some(c) = self.imap.as_mut() {
499 return Ok(c.copy_messages(from, to, ids)?);
500 }
501 let _ = (from, to, ids);
502 Err(EmailClientStdError::NoBackendRegistered)
503 }
504
505 pub fn move_messages(
507 &mut self,
508 from: &str,
509 to: &str,
510 ids: &[&str],
511 ) -> Result<(), EmailClientStdError> {
512 #[cfg(feature = "maildir")]
513 if let Some(c) = &self.maildir {
514 return Ok(c.move_messages(from, to, ids)?);
515 }
516 #[cfg(feature = "m2dir")]
517 if let Some(c) = &self.m2dir {
518 return Ok(c.move_messages(from, to, ids)?);
519 }
520 #[cfg(feature = "jmap")]
521 if let Some(c) = self.jmap.as_mut() {
522 return Ok(c.move_messages(from, to, ids)?);
523 }
524 #[cfg(feature = "imap")]
525 if let Some(c) = self.imap.as_mut() {
526 return Ok(c.move_messages(from, to, ids)?);
527 }
528 let _ = (from, to, ids);
529 Err(EmailClientStdError::NoBackendRegistered)
530 }
531
532 pub fn diff_envelopes(
546 &mut self,
547 mailbox: &str,
548 state: Option<&[u8]>,
549 ) -> Result<EnvelopeDiff, EmailClientStdError> {
550 #[cfg(feature = "jmap")]
551 if let Some(c) = self.jmap.as_mut() {
552 return Ok(c.diff_envelopes(mailbox, state)?);
553 }
554 #[cfg(feature = "imap")]
555 if let Some(c) = self.imap.as_mut() {
556 return Ok(c.diff_envelopes(mailbox, state)?);
557 }
558 let _ = (mailbox, state);
559 Err(EmailClientStdError::UnsupportedOperation)
560 }
561
562 pub fn diff_mailboxes(
568 &mut self,
569 state: Option<&[u8]>,
570 ) -> Result<MailboxDiff, EmailClientStdError> {
571 #[cfg(feature = "jmap")]
572 if let Some(c) = self.jmap.as_mut() {
573 return Ok(c.diff_mailboxes(state)?);
574 }
575 let _ = state;
576 Err(EmailClientStdError::UnsupportedOperation)
577 }
578
579 #[cfg(any(feature = "imap", feature = "jmap"))]
582 pub fn watch_mailbox(
583 &mut self,
584 mailbox: &str,
585 shutdown: Arc<AtomicBool>,
586 tx: Sender<WatchEvent>,
587 ) -> Result<(), EmailClientStdError> {
588 #[cfg(feature = "jmap")]
589 if let Some(c) = self.jmap.as_mut() {
590 return Ok(c.watch_mailbox(mailbox, shutdown, tx)?);
591 }
592 #[cfg(feature = "imap")]
593 if let Some(c) = self.imap.as_mut() {
594 return Ok(c.watch_mailbox(mailbox, shutdown, tx)?);
595 }
596 let _ = (mailbox, shutdown, tx);
597 Err(EmailClientStdError::NoBackendRegistered)
598 }
599
600 #[cfg(any(feature = "jmap", feature = "smtp"))]
605 pub fn send_message(&mut self, raw: Vec<u8>) -> Result<(), EmailClientStdError> {
606 #[cfg(feature = "jmap")]
607 if let Some(c) = self.jmap.as_mut() {
608 return Ok(c.send_message(raw)?);
609 }
610 #[cfg(feature = "smtp")]
611 if let Some(c) = self.smtp.as_mut() {
612 return Ok(c.send_message(raw)?);
613 }
614 let _ = raw;
615 Err(EmailClientStdError::NoBackendRegistered)
616 }
617}