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
//! IMAP protocol state.
//!
//! "Once the connection between client and server is established, an IMAP4rev1 connection is in one of four states.
//! The initial state is identified in the server greeting.
//! Most commands are only valid in certain states.
//! It is a protocol error for the client to attempt a command while the connection is in an inappropriate state,
//! and the server will respond with a BAD or NO (depending upon server implementation) command completion result." ([RFC 3501](https://www.rfc-editor.org/rfc/rfc3501.html))
//!
//! ```text
//!           +----------------------+
//!           |connection established|
//!           +----------------------+
//!                      ||
//!                      \/
//!    +--------------------------------------+
//!    |          server greeting             |
//!    +--------------------------------------+
//!              || (1)       || (2)        || (3)
//!              \/           ||            ||
//!    +-----------------+    ||            ||
//!    |Not Authenticated|    ||            ||
//!    +-----------------+    ||            ||
//!     || (7)   || (4)       ||            ||
//!     ||       \/           \/            ||
//!     ||     +----------------+           ||
//!     ||     | Authenticated  |<=++       ||
//!     ||     +----------------+  ||       ||
//!     ||       || (7)   || (5)   || (6)   ||
//!     ||       ||       \/       ||       ||
//!     ||       ||    +--------+  ||       ||
//!     ||       ||    |Selected|==++       ||
//!     ||       ||    +--------+           ||
//!     ||       ||       || (7)            ||
//!     \/       \/       \/                \/
//!    +--------------------------------------+
//!    |               Logout                 |
//!    +--------------------------------------+
//!                      ||
//!                      \/
//!        +-------------------------------+
//!        |both sides close the connection|
//!        +-------------------------------+
//!
//! (1) connection without pre-authentication (OK greeting)
//! (2) pre-authenticated connection (PREAUTH greeting)
//! (3) rejected connection (BYE greeting)
//! (4) successful LOGIN or AUTHENTICATE command
//! (5) successful SELECT or EXAMINE command
//! (6) CLOSE command, or failed SELECT or EXAMINE command
//! (7) LOGOUT command, server shutdown, or connection closed
//! ```

#[cfg(feature = "bounded-static")]
use bounded_static::ToStatic;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "ext_idle")]
use crate::core::Tag;
use crate::mailbox::Mailbox;

/// State of the IMAP4rev1 connection.
#[cfg_attr(feature = "bounded-static", derive(ToStatic))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum State<'a> {
    Greeting,

    /// The client MUST supply authentication credentials before most commands will be permitted.
    /// This state is entered when a connection starts unless the connection has been pre-authenticated.
    NotAuthenticated,

    /// The client is authenticated and MUST select a mailbox to access before commands that affect messages will be permitted.
    /// This state is entered when a pre-authenticated connection starts, when acceptable authentication credentials have been provided,
    /// after an error in selecting a mailbox, or after a successful CLOSE command.
    Authenticated,

    /// A mailbox has been selected to access.
    /// This state is entered when a mailbox has been successfully selected.
    Selected(Mailbox<'a>),

    /// The connection is being terminated.
    /// This state can be entered as a result of a client request (via the LOGOUT command) or by unilateral action on the part of either the client or server.
    ///
    /// If the client requests the logout state, the server MUST send an untagged BYE response and a tagged OK response to the LOGOUT command before the server closes the connection;
    /// and the client MUST read the tagged OK response to the LOGOUT command before the client closes the connection.
    ///
    /// A server MUST NOT unilaterally close the connection without sending an untagged BYE response that contains the reason for having done so.
    /// A client SHOULD NOT unilaterally close the connection, and instead SHOULD issue a LOGOUT command.
    /// If the server detects that the client has unilaterally closed the connection, the server MAY omit the untagged BYE response and simply close its connection.
    Logout,

    #[cfg(feature = "ext_idle")]
    #[cfg_attr(docsrs, doc(cfg(feature = "ext_idle")))]
    IdleAuthenticated(Tag<'a>),

    #[cfg(feature = "ext_idle")]
    #[cfg_attr(docsrs, doc(cfg(feature = "ext_idle")))]
    IdleSelected(Tag<'a>, Mailbox<'a>),
}

#[cfg(test)]
mod tests {
    #[cfg(feature = "bounded-static")]
    use bounded_static::{IntoBoundedStatic, ToBoundedStatic};

    use super::*;
    #[cfg(feature = "ext_idle")]
    use crate::core::Tag;
    use crate::mailbox::Mailbox;

    #[test]
    fn test_conversion() {
        let tests = [
            State::Greeting,
            State::NotAuthenticated,
            State::Authenticated,
            State::Selected(Mailbox::Inbox),
            State::Logout,
            #[cfg(feature = "ext_idle")]
            State::IdleAuthenticated(Tag::try_from("A").unwrap()),
            #[cfg(feature = "ext_idle")]
            State::IdleSelected(Tag::try_from("A").unwrap(), Mailbox::Inbox),
        ];

        for _test in tests {
            #[cfg(feature = "bounded-static")]
            {
                let test_to_static = _test.to_static();
                assert_eq!(_test, test_to_static);

                let test_into_static = _test.into_static();
                assert_eq!(test_to_static, test_into_static);
            }
        }
    }
}