1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Adds support for the IMAP SORT extension specificed in [RFC
//! 5464](https://tools.ietf.org/html/rfc5256#section-3).
//!
//! The SORT command is a variant of SEARCH with sorting semantics for
//! the results. There are two arguments before the searching
//! criteria argument: a parenthesized list of sort criteria, and the
//! searching charset.

use std::{borrow::Cow, fmt};

pub(crate) struct SortCriteria<'c>(pub(crate) &'c [SortCriterion<'c>]);

impl<'c> fmt::Display for SortCriteria<'c> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.0.is_empty() {
            write!(f, "")
        } else {
            let criteria: Vec<String> = self.0.iter().map(|c| c.to_string()).collect();
            write!(f, "({})", criteria.join(" "))
        }
    }
}

/// Message sorting preferences used for [`Session::sort`](crate::Session::sort)
/// and [`Session::uid_sort`](crate::Session::uid_sort).
///
/// Any sorting criterion that refers to an address (`From`, `To`, etc.) sorts according to the
/// "addr-mailbox" of the indicated address. You can find the formal syntax for addr-mailbox [in
/// the IMAP spec](https://tools.ietf.org/html/rfc3501#section-9), and a more detailed discussion
/// of the relevant semantics [in RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.4.1).
/// Essentially, the address refers _either_ to the name of the contact _or_ to its local-part (the
/// left part of the email address, before the `@`).
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum SortCriterion<'c> {
    /// Internal date and time of the message. This differs from the
    /// ON criteria in SEARCH, which uses just the internal date.
    Arrival,

    /// IMAP addr-mailbox of the first "Cc" address.
    Cc,

    /// Sent date and time, as described in
    /// [section 2.2](https://tools.ietf.org/html/rfc5256#section-2.2).
    Date,

    /// IMAP addr-mailbox of the first "From" address.
    From,

    /// Followed by another sort criterion, has the effect of that
    /// criterion but in reverse (descending) order.
    Reverse(&'c SortCriterion<'c>),

    /// Size of the message in octets.
    Size,

    /// Base subject text.
    Subject,

    /// IMAP addr-mailbox of the first "To" address.
    To,
}

impl<'c> fmt::Display for SortCriterion<'c> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use SortCriterion::*;

        match self {
            Arrival => write!(f, "ARRIVAL"),
            Cc => write!(f, "CC"),
            Date => write!(f, "DATE"),
            From => write!(f, "FROM"),
            Reverse(c) => write!(f, "REVERSE {}", c),
            Size => write!(f, "SIZE"),
            Subject => write!(f, "SUBJECT"),
            To => write!(f, "TO"),
        }
    }
}

/// The character encoding to use for strings that are subject to a [`SortCriterion`].
///
/// Servers are only required to implement [`SortCharset::UsAscii`] and [`SortCharset::Utf8`].
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SortCharset<'c> {
    /// Strings are UTF-8 encoded.
    Utf8,

    /// Strings are encoded with ASCII.
    UsAscii,

    /// Strings are encoded using some other character set.
    ///
    /// Note that this option is subject to server support for the specified character set.
    Custom(Cow<'c, str>),
}

impl<'c> fmt::Display for SortCharset<'c> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use SortCharset::*;

        match self {
            Utf8 => write!(f, "UTF-8"),
            UsAscii => write!(f, "US-ASCII"),
            Custom(c) => write!(f, "{}", c),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_criterion_to_string() {
        use SortCriterion::*;

        assert_eq!("ARRIVAL", Arrival.to_string());
        assert_eq!("CC", Cc.to_string());
        assert_eq!("DATE", Date.to_string());
        assert_eq!("FROM", From.to_string());
        assert_eq!("SIZE", Size.to_string());
        assert_eq!("SUBJECT", Subject.to_string());
        assert_eq!("TO", To.to_string());
        assert_eq!("REVERSE TO", Reverse(&To).to_string());
        assert_eq!("REVERSE REVERSE TO", Reverse(&Reverse(&To)).to_string());
    }

    #[test]
    fn test_criteria_to_string() {
        use SortCriterion::*;

        assert_eq!("", SortCriteria(&[]).to_string());
        assert_eq!("(ARRIVAL)", SortCriteria(&[Arrival]).to_string());
        assert_eq!(
            "(ARRIVAL REVERSE FROM)",
            SortCriteria(&[Arrival, Reverse(&From)]).to_string()
        );
        assert_eq!(
            "(ARRIVAL REVERSE REVERSE REVERSE FROM)",
            SortCriteria(&[Arrival, Reverse(&Reverse(&Reverse(&From)))]).to_string()
        );
    }

    #[test]
    fn test_charset_to_string() {
        use SortCharset::*;

        assert_eq!("UTF-8", Utf8.to_string());
        assert_eq!("US-ASCII", UsAscii.to_string());
        assert_eq!("CHARSET", Custom("CHARSET".into()).to_string());
    }
}