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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]

//! # A Rust library to interact with the google Gmail API using a service account.
//!
//! Currently focused only on support for sending emails but this may change in the future.
//! Available both as async (default) or as blocking using the "blocking" feature.
//!
//! Note that usage of this lib will require a google API service account with domain wide delegation for gmail setup with a google cloud project with the gmail API enabled.
//! Links for more information:
//!  - <https://cloud.google.com/iam/docs/service-account-overview>
//!  - <https://support.google.com/a/answer/162106?hl=en&fl=1&sjid=12697421685211806668-NA>
//!  - <https://developers.google.com/gmail/api/guides>
//!
//! ## Features
//! There is currently only one feature, `blocking` which will add blocking alternatives to all async functions with the same name suffixed with `_blocking`.
//! E.g. `send_email_blocking` instead of `send_email`.
//!
//! ## Examples
//! Examples of how to use this crate.
//!
//! ### Async Example
//! ```rust
//! let email_client = GmailClient::builder(
//!     "service_account.json",
//!     "noreply@example.test",
//! )?
//! .build()
//! .await?;
//!
//! email_client
//!     .send_email("some_user@domain.test")
//!     .await?;
//! ```
//!
//! ### Blocking Example
//! Note: Requires the `blocking` feature.
//! ```rust
//! let email_client = GmailClient::builder(
//!     "service_account.json",
//!     "noreply@example.test",
//! )?
//! .build_blocking()?;
//!
//! email_client
//!     .send_email_blocking("some_user@domain.test")?;
//! ```

use std::path::Path;

use async_impl::{send_email::send_email, token::retrieve_token};
use error::Result;
use service_account::ServiceAccount;

mod async_impl;
mod common;
mod error;
mod service_account;

#[cfg(feature = "blocking")]
mod blocking;

/// The `GmailClientBuilder` is the intended way of creating a [`GmailClient`].
#[derive(Debug, Clone)]
pub struct GmailClientBuilder {
    service_account: ServiceAccount,
    send_from_email: String,
    mock_mode: bool,
}

impl<'a> GmailClientBuilder {
    /// Create a new `GmailClientBuilder`.
    /// Will return an error if unable to read & parse the `service_account_path` if, for example, the file does not exist or has an incorrect format.
    pub fn new<P: AsRef<Path>, S: Into<String>>(
        service_account_path: P,
        send_from_email: S,
    ) -> Result<Self> {
        Ok(Self {
            service_account: ServiceAccount::load_from_file(service_account_path)?,
            send_from_email: send_from_email.into(),
            mock_mode: false,
        })
    }

    /// Enables "mock mode" which will log print the email instead of sending it.
    pub fn mock_mode(mut self) -> Self {
        self.mock_mode = true;
        self
    }

    /// Build a [`GmailClient`] from this `GmailClientBuilder`.
    /// Note: This function will retrieve an access token from the Google API and as such make an API request.
    pub async fn build(self) -> Result<GmailClient> {
        let token = retrieve_token(&self.service_account, &self.send_from_email).await?;

        Ok(GmailClient {
            send_from_email: self.send_from_email,
            token,
            mock_mode: self.mock_mode,
        })
    }

    /// A blocking alternative to the [`build()`] function.
    #[cfg(feature = "blocking")]
    pub fn build_blocking(self) -> Result<GmailClient> {
        use blocking::token::retrieve_token_blocking;

        let token = retrieve_token_blocking(&self.service_account, &self.send_from_email)?;

        Ok(GmailClient {
            send_from_email: self.send_from_email,
            token,
            mock_mode: self.mock_mode,
        })
    }
}

/// A client ready to send emails through the Gmail API.
#[derive(Debug, Clone)]
pub struct GmailClient {
    send_from_email: String,
    token: String,
    mock_mode: bool,
}

impl GmailClient {
    /// Alias for [`GmailClientBuilder::new()`].
    pub fn builder<P: AsRef<Path>, S: Into<String>>(
        service_account_path: P,
        send_from_email: S,
    ) -> Result<GmailClientBuilder> {
        GmailClientBuilder::new(service_account_path, send_from_email)
    }

    /// Send an email to `send_to_email` with the specified `subject` and `content`.
    pub async fn send_email(
        &self,
        send_to_email: &str,
        subject: &str,
        content: &str,
    ) -> Result<()> {
        send_email(
            send_to_email,
            subject,
            content,
            &self.token,
            &self.send_from_email,
            self.mock_mode,
        )
        .await
    }

    /// A blocking alternative to [`send_email()`].
    #[cfg(feature = "blocking")]
    pub fn send_email_blocking(
        &self,
        send_to_email: &str,
        subject: &str,
        content: &str,
    ) -> Result<()> {
        use blocking::send_email::send_email_blocking;

        send_email_blocking(
            send_to_email,
            subject,
            content,
            &self.token,
            &self.send_from_email,
            self.mock_mode,
        )
    }
}