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
use ::{ExecFuture, Cmd, Io, EhloData};
use ::error::{MissingCapabilities};

/// An either of two commands
///
/// Useful for cases when we need either of two commands but wouldn't like to use [`BoxedCmd`].
///
/// For example, `ConnectionConfig<EitherCmd<command::Noop, command::auth::Plain>>` would implement `Clone` trait, but ConnectionConfig<BoxedCmd> would not. So we can use once created config for connection several times.
///
/// ```
/// extern crate new_tokio_smtp;
///
/// use new_tokio_smtp::{command::{auth, Noop, EitherCmd}, ConnectionConfig};
///
/// fn main() {
///     let address = "127.0.0.1:25".parse().unwrap();
///     let hostname = "smtp.example.com".parse().unwrap();
///     let username = "user@example.com";
///     let password = "top-secret";
///
///     let auth_command = match auth::Plain::from_username(username, password) {
///         Ok(plain_auth) => EitherCmd::B(plain_auth),
///         Err(_) => EitherCmd::A(Noop),
///     };
///
///     let config = ConnectionConfig::builder_with_addr(address, hostname)
///         .auth(auth_command)
///         .build();
///
///     {
///         let config = config.clone();
///         // ...connect and send emails
///     }
/// }
/// ```
#[derive(Debug, Clone)]
pub enum EitherCmd<A, B> {
    A(A),
    B(B),
}

impl<A, B> Cmd for EitherCmd<A, B>
where
    A: Cmd,
    B: Cmd,
{
    fn check_cmd_availability(&self, caps: Option<&EhloData>) -> Result<(), MissingCapabilities> {
        match self {
            EitherCmd::A(a) => a.check_cmd_availability(caps),
            EitherCmd::B(b) => b.check_cmd_availability(caps),
        }
    }
    fn exec(self, con: Io) -> ExecFuture {
        match self {
            EitherCmd::A(a) => a.exec(con),
            EitherCmd::B(b) => b.exec(con),
        }
    }
}

/// An alternative of two commands
///
/// Useful for cases when we need execute only one of two commands depending from availability.
///
/// For example, `ConnectionConfig<SelectCmd<command::auth::Plain, command::auth::Login>>` can help alternate two auth methods.
///
/// ```
/// extern crate new_tokio_smtp;
///
/// use new_tokio_smtp::{command::{auth, SelectCmd}, ConnectionConfig};
///
/// fn main() {
///     let address = "127.0.0.1:25".parse().unwrap();
///     let hostname = "smtp.example.com".parse().unwrap();
///     let username = "user@example.com";
///     let password = "top-secret";
///
///     let plain_auth = auth::Plain::from_username(username, password).unwrap();
///     let login_auth = auth::Login::new(username, password);
///
///     let config = ConnectionConfig::builder_with_addr(address, hostname)
///         .auth(SelectCmd(plain_auth, login_auth))
///         .build();
///     // ...connect and send emails
/// }
/// ```
#[derive(Debug, Clone)]
pub struct SelectCmd<A, B>(pub A, pub B);

impl<A, B> Cmd for SelectCmd<A, B>
where
    A: Cmd,
    B: Cmd,
{
    fn check_cmd_availability(&self, caps: Option<&EhloData>) -> Result<(), MissingCapabilities> {
        self.0
            .check_cmd_availability(caps)
            .or_else(|_| self.1.check_cmd_availability(caps))
    }
    fn exec(self, con: Io) -> ExecFuture {
        if self.0.check_cmd_availability(con.ehlo_data()).is_ok() {
            Box::new(self.0.exec(con))
        } else {
            Box::new(self.1.exec(con))
        }
    }
}