Skip to main content

rusmes_storage/
types.rs

1//! Storage-related types
2
3use rusmes_proto::{MessageId, Username};
4use serde::{Deserialize, Serialize};
5use std::collections::HashSet;
6use std::fmt;
7use uuid::Uuid;
8
9/// Unique identifier for a mailbox
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct MailboxId(Uuid);
12
13impl MailboxId {
14    /// Create a new unique mailbox ID
15    pub fn new() -> Self {
16        Self(Uuid::new_v4())
17    }
18
19    /// Create a MailboxId from an existing UUID
20    pub fn from_uuid(uuid: Uuid) -> Self {
21        Self(uuid)
22    }
23
24    /// Get the UUID value
25    pub fn as_uuid(&self) -> &Uuid {
26        &self.0
27    }
28}
29
30impl Default for MailboxId {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl std::fmt::Display for MailboxId {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        write!(f, "{}", self.0)
39    }
40}
41
42/// Mailbox path (hierarchical)
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub struct MailboxPath {
45    user: Username,
46    path: Vec<String>,
47}
48
49impl MailboxPath {
50    /// Create a new mailbox path
51    pub fn new(user: Username, path: Vec<String>) -> Self {
52        Self { user, path }
53    }
54
55    /// Get the user
56    pub fn user(&self) -> &Username {
57        &self.user
58    }
59
60    /// Get the path components
61    pub fn path(&self) -> &[String] {
62        &self.path
63    }
64
65    /// Get the mailbox name (last component)
66    pub fn name(&self) -> Option<&str> {
67        self.path.last().map(|s| s.as_str())
68    }
69}
70
71impl std::fmt::Display for MailboxPath {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(f, "{}/{}", self.user, self.path.join("/"))
74    }
75}
76
77/// Mailbox metadata
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Mailbox {
80    id: MailboxId,
81    path: MailboxPath,
82    uid_validity: u32,
83    uid_next: u32,
84    special_use: Option<String>,
85}
86
87impl Mailbox {
88    /// Create a new mailbox
89    pub fn new(path: MailboxPath) -> Self {
90        Self {
91            id: MailboxId::new(),
92            path,
93            uid_validity: 1, // Would be more sophisticated in production
94            uid_next: 1,
95            special_use: None,
96        }
97    }
98
99    /// Create a new mailbox with special-use attribute
100    pub fn new_with_special_use(path: MailboxPath, special_use: String) -> Self {
101        Self {
102            id: MailboxId::new(),
103            path,
104            uid_validity: 1,
105            uid_next: 1,
106            special_use: Some(special_use),
107        }
108    }
109
110    /// Get the mailbox ID
111    pub fn id(&self) -> &MailboxId {
112        &self.id
113    }
114
115    /// Get the mailbox path
116    pub fn path(&self) -> &MailboxPath {
117        &self.path
118    }
119
120    /// Set the mailbox path (for rename operations)
121    pub fn set_path(&mut self, path: MailboxPath) {
122        self.path = path;
123    }
124
125    /// Get UID validity
126    pub fn uid_validity(&self) -> u32 {
127        self.uid_validity
128    }
129
130    /// Get next UID
131    pub fn uid_next(&self) -> u32 {
132        self.uid_next
133    }
134
135    /// Get special-use attribute
136    pub fn special_use(&self) -> Option<&str> {
137        self.special_use.as_deref()
138    }
139
140    /// Set special-use attribute
141    pub fn set_special_use(&mut self, special_use: Option<String>) {
142        self.special_use = special_use;
143    }
144}
145
146/// Message metadata in a mailbox
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct MessageMetadata {
149    message_id: MessageId,
150    mailbox_id: MailboxId,
151    uid: u32,
152    flags: MessageFlags,
153    size: usize,
154}
155
156impl MessageMetadata {
157    /// Create new message metadata
158    #[allow(clippy::too_many_arguments)]
159    pub fn new(
160        message_id: MessageId,
161        mailbox_id: MailboxId,
162        uid: u32,
163        flags: MessageFlags,
164        size: usize,
165    ) -> Self {
166        Self {
167            message_id,
168            mailbox_id,
169            uid,
170            flags,
171            size,
172        }
173    }
174
175    /// Get message ID
176    pub fn message_id(&self) -> &MessageId {
177        &self.message_id
178    }
179
180    /// Get mailbox ID
181    pub fn mailbox_id(&self) -> &MailboxId {
182        &self.mailbox_id
183    }
184
185    /// Get UID
186    pub fn uid(&self) -> u32 {
187        self.uid
188    }
189
190    /// Get flags
191    pub fn flags(&self) -> &MessageFlags {
192        &self.flags
193    }
194
195    /// Get size
196    pub fn size(&self) -> usize {
197        self.size
198    }
199}
200
201/// Message flags (IMAP standard flags)
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct MessageFlags {
204    seen: bool,
205    answered: bool,
206    flagged: bool,
207    deleted: bool,
208    draft: bool,
209    recent: bool,
210    custom: HashSet<String>,
211}
212
213impl MessageFlags {
214    /// Create new empty flags
215    pub fn new() -> Self {
216        Self::default()
217    }
218
219    /// Check if message is seen
220    pub fn is_seen(&self) -> bool {
221        self.seen
222    }
223
224    /// Set seen flag
225    pub fn set_seen(&mut self, value: bool) {
226        self.seen = value;
227    }
228
229    /// Check if message is answered
230    pub fn is_answered(&self) -> bool {
231        self.answered
232    }
233
234    /// Set answered flag
235    pub fn set_answered(&mut self, value: bool) {
236        self.answered = value;
237    }
238
239    /// Check if message is flagged
240    pub fn is_flagged(&self) -> bool {
241        self.flagged
242    }
243
244    /// Set flagged flag
245    pub fn set_flagged(&mut self, value: bool) {
246        self.flagged = value;
247    }
248
249    /// Check if message is deleted
250    pub fn is_deleted(&self) -> bool {
251        self.deleted
252    }
253
254    /// Set deleted flag
255    pub fn set_deleted(&mut self, value: bool) {
256        self.deleted = value;
257    }
258
259    /// Check if message is draft
260    pub fn is_draft(&self) -> bool {
261        self.draft
262    }
263
264    /// Set draft flag
265    pub fn set_draft(&mut self, value: bool) {
266        self.draft = value;
267    }
268
269    /// Check if message is recent
270    pub fn is_recent(&self) -> bool {
271        self.recent
272    }
273
274    /// Set recent flag
275    pub fn set_recent(&mut self, value: bool) {
276        self.recent = value;
277    }
278
279    /// Add custom flag
280    pub fn add_custom(&mut self, flag: String) {
281        self.custom.insert(flag);
282    }
283
284    /// Remove custom flag
285    pub fn remove_custom(&mut self, flag: &str) -> bool {
286        self.custom.remove(flag)
287    }
288
289    /// Get custom flags
290    pub fn custom(&self) -> &HashSet<String> {
291        &self.custom
292    }
293}
294
295/// Search criteria for messages
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub enum SearchCriteria {
298    All,
299    Unseen,
300    Seen,
301    Flagged,
302    Unflagged,
303    Deleted,
304    Undeleted,
305    From(String),
306    To(String),
307    Subject(String),
308    Body(String),
309    And(Vec<SearchCriteria>),
310    Or(Vec<SearchCriteria>),
311    Not(Box<SearchCriteria>),
312}
313
314/// Mailbox counters
315#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
316pub struct MailboxCounters {
317    pub exists: u32,
318    pub recent: u32,
319    pub unseen: u32,
320}
321
322/// User quota information
323#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
324pub struct Quota {
325    pub used: u64,
326    pub limit: u64,
327}
328
329impl Quota {
330    /// Create new quota
331    pub fn new(used: u64, limit: u64) -> Self {
332        Self { used, limit }
333    }
334
335    /// Check if quota is exceeded
336    pub fn is_exceeded(&self) -> bool {
337        self.used >= self.limit
338    }
339
340    /// Get remaining quota
341    pub fn remaining(&self) -> u64 {
342        self.limit.saturating_sub(self.used)
343    }
344}
345
346/// Special-use mailbox attributes (RFC 6154)
347#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
348pub struct SpecialUseAttributes {
349    attributes: HashSet<String>,
350}
351
352impl SpecialUseAttributes {
353    /// Create new empty special-use attributes
354    pub fn new() -> Self {
355        Self {
356            attributes: HashSet::new(),
357        }
358    }
359
360    /// Create with a single attribute
361    pub fn single(attribute: String) -> Self {
362        let mut attrs = HashSet::new();
363        attrs.insert(attribute);
364        Self { attributes: attrs }
365    }
366
367    /// Add an attribute
368    pub fn add(&mut self, attribute: String) {
369        self.attributes.insert(attribute);
370    }
371
372    /// Remove an attribute
373    pub fn remove(&mut self, attribute: &str) -> bool {
374        self.attributes.remove(attribute)
375    }
376
377    /// Check if a specific attribute is set
378    pub fn has_attribute(&self, attribute: &str) -> bool {
379        self.attributes.contains(attribute)
380    }
381
382    /// Check if any attributes are set
383    pub fn is_empty(&self) -> bool {
384        self.attributes.is_empty()
385    }
386
387    /// Get the number of attributes
388    pub fn len(&self) -> usize {
389        self.attributes.len()
390    }
391
392    /// Get an iterator over the attributes
393    pub fn iter(&self) -> impl Iterator<Item = &String> {
394        self.attributes.iter()
395    }
396
397    /// Convert to a vector of attributes
398    pub fn to_vec(&self) -> Vec<String> {
399        self.attributes.iter().cloned().collect()
400    }
401
402    /// Parse from a list of attribute strings
403    pub fn from_vec(attributes: Vec<String>) -> Self {
404        Self {
405            attributes: attributes.into_iter().collect(),
406        }
407    }
408
409    /// Common special-use attributes
410    pub fn drafts() -> Self {
411        Self::single("\\Drafts".to_string())
412    }
413
414    pub fn sent() -> Self {
415        Self::single("\\Sent".to_string())
416    }
417
418    pub fn trash() -> Self {
419        Self::single("\\Trash".to_string())
420    }
421
422    pub fn junk() -> Self {
423        Self::single("\\Junk".to_string())
424    }
425
426    pub fn archive() -> Self {
427        Self::single("\\Archive".to_string())
428    }
429
430    pub fn all() -> Self {
431        Self::single("\\All".to_string())
432    }
433
434    pub fn flagged() -> Self {
435        Self::single("\\Flagged".to_string())
436    }
437}
438
439impl FromIterator<String> for SpecialUseAttributes {
440    fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
441        Self {
442            attributes: iter.into_iter().collect(),
443        }
444    }
445}
446
447impl fmt::Display for SpecialUseAttributes {
448    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449        let attrs: Vec<&String> = self.attributes.iter().collect();
450        write!(
451            f,
452            "{}",
453            attrs
454                .iter()
455                .map(|s| s.as_str())
456                .collect::<Vec<_>>()
457                .join(" ")
458        )
459    }
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465
466    #[test]
467    fn test_special_use_attributes_new() {
468        let attrs = SpecialUseAttributes::new();
469        assert!(attrs.is_empty());
470        assert_eq!(attrs.len(), 0);
471    }
472
473    #[test]
474    fn test_special_use_attributes_single() {
475        let attrs = SpecialUseAttributes::single("\\Drafts".to_string());
476        assert!(!attrs.is_empty());
477        assert_eq!(attrs.len(), 1);
478        assert!(attrs.has_attribute("\\Drafts"));
479    }
480
481    #[test]
482    fn test_special_use_attributes_add_remove() {
483        let mut attrs = SpecialUseAttributes::new();
484        attrs.add("\\Drafts".to_string());
485        assert!(attrs.has_attribute("\\Drafts"));
486        assert_eq!(attrs.len(), 1);
487
488        attrs.add("\\Sent".to_string());
489        assert!(attrs.has_attribute("\\Sent"));
490        assert_eq!(attrs.len(), 2);
491
492        assert!(attrs.remove("\\Drafts"));
493        assert!(!attrs.has_attribute("\\Drafts"));
494        assert_eq!(attrs.len(), 1);
495
496        assert!(!attrs.remove("\\Drafts"));
497    }
498
499    #[test]
500    fn test_special_use_attributes_from_vec() {
501        let vec = vec!["\\Drafts".to_string(), "\\Sent".to_string()];
502        let attrs = SpecialUseAttributes::from_vec(vec);
503        assert_eq!(attrs.len(), 2);
504        assert!(attrs.has_attribute("\\Drafts"));
505        assert!(attrs.has_attribute("\\Sent"));
506    }
507
508    #[test]
509    fn test_special_use_attributes_to_vec() {
510        let mut attrs = SpecialUseAttributes::new();
511        attrs.add("\\Drafts".to_string());
512        attrs.add("\\Sent".to_string());
513
514        let vec = attrs.to_vec();
515        assert_eq!(vec.len(), 2);
516        assert!(vec.contains(&"\\Drafts".to_string()));
517        assert!(vec.contains(&"\\Sent".to_string()));
518    }
519
520    #[test]
521    fn test_special_use_attributes_iter() {
522        let mut attrs = SpecialUseAttributes::new();
523        attrs.add("\\Drafts".to_string());
524        attrs.add("\\Sent".to_string());
525
526        let mut count = 0;
527        for _attr in attrs.iter() {
528            count += 1;
529        }
530        assert_eq!(count, 2);
531    }
532
533    #[test]
534    fn test_special_use_attributes_from_iter() {
535        let attrs: SpecialUseAttributes = vec!["\\Drafts".to_string(), "\\Sent".to_string()]
536            .into_iter()
537            .collect();
538        assert_eq!(attrs.len(), 2);
539        assert!(attrs.has_attribute("\\Drafts"));
540        assert!(attrs.has_attribute("\\Sent"));
541    }
542
543    #[test]
544    fn test_special_use_attributes_display() {
545        let mut attrs = SpecialUseAttributes::new();
546        attrs.add("\\Drafts".to_string());
547
548        let display = attrs.to_string();
549        assert!(display.contains("\\Drafts"));
550    }
551
552    #[test]
553    fn test_special_use_attributes_drafts() {
554        let attrs = SpecialUseAttributes::drafts();
555        assert!(attrs.has_attribute("\\Drafts"));
556        assert_eq!(attrs.len(), 1);
557    }
558
559    #[test]
560    fn test_special_use_attributes_sent() {
561        let attrs = SpecialUseAttributes::sent();
562        assert!(attrs.has_attribute("\\Sent"));
563        assert_eq!(attrs.len(), 1);
564    }
565
566    #[test]
567    fn test_special_use_attributes_trash() {
568        let attrs = SpecialUseAttributes::trash();
569        assert!(attrs.has_attribute("\\Trash"));
570        assert_eq!(attrs.len(), 1);
571    }
572
573    #[test]
574    fn test_special_use_attributes_junk() {
575        let attrs = SpecialUseAttributes::junk();
576        assert!(attrs.has_attribute("\\Junk"));
577        assert_eq!(attrs.len(), 1);
578    }
579
580    #[test]
581    fn test_special_use_attributes_archive() {
582        let attrs = SpecialUseAttributes::archive();
583        assert!(attrs.has_attribute("\\Archive"));
584        assert_eq!(attrs.len(), 1);
585    }
586
587    #[test]
588    fn test_special_use_attributes_all() {
589        let attrs = SpecialUseAttributes::all();
590        assert!(attrs.has_attribute("\\All"));
591        assert_eq!(attrs.len(), 1);
592    }
593
594    #[test]
595    fn test_special_use_attributes_flagged() {
596        let attrs = SpecialUseAttributes::flagged();
597        assert!(attrs.has_attribute("\\Flagged"));
598        assert_eq!(attrs.len(), 1);
599    }
600
601    #[test]
602    fn test_special_use_attributes_default() {
603        let attrs = SpecialUseAttributes::default();
604        assert!(attrs.is_empty());
605    }
606
607    #[test]
608    fn test_special_use_attributes_equality() {
609        let mut attrs1 = SpecialUseAttributes::new();
610        attrs1.add("\\Drafts".to_string());
611        attrs1.add("\\Sent".to_string());
612
613        let mut attrs2 = SpecialUseAttributes::new();
614        attrs2.add("\\Sent".to_string());
615        attrs2.add("\\Drafts".to_string());
616
617        assert_eq!(attrs1, attrs2);
618    }
619}