1#[cfg(any(
7 feature = "rustls-aws",
8 feature = "rustls-ring",
9 feature = "native-tls"
10))]
11use core::time::Duration;
12use core::{any::Any, fmt};
13
14use alloc::{
15 boxed::Box,
16 string::{String, ToString},
17};
18use std::io::{self, Read, Write};
19
20use io_http::rfc6750::bearer::HttpAuthBearer;
21#[cfg(any(
22 feature = "rustls-aws",
23 feature = "rustls-ring",
24 feature = "native-tls"
25))]
26use pimalaya_stream::{std::stream::StreamStd, tls::Tls};
27use thiserror::Error;
28#[cfg(any(
29 feature = "rustls-aws",
30 feature = "rustls-ring",
31 feature = "native-tls"
32))]
33use url::Url;
34
35#[cfg(any(
36 feature = "rustls-aws",
37 feature = "rustls-ring",
38 feature = "native-tls"
39))]
40use crate::v1::send::GMAIL_API_BASE;
41use crate::{
42 coroutine::*,
43 v1::rest::labels::{
44 GmailLabel,
45 create::GmailLabelCreate,
46 delete::GmailLabelDelete,
47 get::GmailLabelGet,
48 list::{GmailLabelsList, GmailLabelsListResponse},
49 patch::GmailLabelPatch,
50 update::GmailLabelUpdate,
51 },
52 v1::rest::messages::{
53 GmailMessage, GmailMessageFormat, GmailMessageId, delete::GmailMessageDelete,
54 get::GmailMessageGet, list::GmailMessagesList, list::GmailMessagesListParams,
55 list::GmailMessagesListResponse, modify::GmailMessageModify, send::GmailMessageSend,
56 trash::GmailMessageTrash, untrash::GmailMessageUntrash,
57 },
58 v1::rest::users::{
59 get_profile::{GmailProfile, GmailProfileGet},
60 stop::GmailStop,
61 watch::{GmailWatch, GmailWatchRequest, GmailWatchResponse},
62 },
63 v1::send::{GmailNoResponse, GmailSendError, GmailSendOutput},
64};
65
66#[derive(Debug, Error)]
68pub enum GmailClientStdError {
69 #[error(transparent)]
71 Send(#[from] GmailSendError),
72 #[error(transparent)]
74 Io(#[from] io::Error),
75 #[cfg(any(
77 feature = "rustls-aws",
78 feature = "rustls-ring",
79 feature = "native-tls"
80 ))]
81 #[error(transparent)]
82 Tls(#[from] anyhow::Error),
83 #[cfg(any(
85 feature = "rustls-aws",
86 feature = "rustls-ring",
87 feature = "native-tls"
88 ))]
89 #[error("Gmail URL `{0}` has no host")]
90 UrlMissingHost(String),
91 #[cfg(any(
93 feature = "rustls-aws",
94 feature = "rustls-ring",
95 feature = "native-tls"
96 ))]
97 #[error("Gmail URL `{url}` has unsupported scheme `{scheme}` (expected `http` or `https`)")]
98 UrlUnsupportedScheme {
99 url: String,
101 scheme: String,
103 },
104}
105
106pub struct GmailClientStdConnectOptions {
109 #[cfg(any(
111 feature = "rustls-aws",
112 feature = "rustls-ring",
113 feature = "native-tls"
114 ))]
115 pub tls: Tls,
116 pub user_id: String,
118}
119
120impl Default for GmailClientStdConnectOptions {
121 fn default() -> Self {
122 Self {
123 #[cfg(any(
124 feature = "rustls-aws",
125 feature = "rustls-ring",
126 feature = "native-tls"
127 ))]
128 tls: Tls::default(),
129 user_id: String::from("me"),
130 }
131 }
132}
133
134const READ_BUFFER_SIZE: usize = 16 * 1024;
135
136pub struct GmailClientStd {
143 pub stream: Box<dyn GmailStream>,
145 pub auth: HttpAuthBearer,
147 pub user_id: String,
149}
150
151impl GmailClientStd {
152 pub fn new<S: Read + Write + Send + 'static>(
154 stream: S,
155 token: impl ToString,
156 options: GmailClientStdConnectOptions,
157 ) -> Self {
158 Self {
159 stream: Box::new(stream),
160 auth: HttpAuthBearer::new(token.to_string()),
161 user_id: options.user_id,
162 }
163 }
164
165 #[cfg(any(
168 feature = "rustls-aws",
169 feature = "rustls-ring",
170 feature = "native-tls"
171 ))]
172 pub fn connect(
173 token: impl ToString,
174 options: GmailClientStdConnectOptions,
175 ) -> Result<Self, GmailClientStdError> {
176 let GmailClientStdConnectOptions { tls, user_id } = options;
177
178 let url = Url::parse(GMAIL_API_BASE).expect("Gmail API base URL is valid");
179 let host = url
180 .host_str()
181 .ok_or_else(|| GmailClientStdError::UrlMissingHost(url.to_string()))?;
182
183 let stream = match url.scheme() {
184 "http" => StreamStd::connect_tcp(host, url.port().unwrap_or(80))?,
185 "https" => StreamStd::connect_tls(host, url.port().unwrap_or(443), &tls)?,
186 scheme => {
187 return Err(GmailClientStdError::UrlUnsupportedScheme {
188 url: url.to_string(),
189 scheme: scheme.to_string(),
190 });
191 }
192 };
193
194 stream.set_read_timeout(Some(Duration::from_secs(30)))?;
195
196 Ok(Self {
197 stream: Box::new(stream),
198 auth: HttpAuthBearer::new(token.to_string()),
199 user_id,
200 })
201 }
202
203 pub fn set_stream<S: Read + Write + Send + 'static>(&mut self, stream: S) {
205 self.stream = Box::new(stream);
206 }
207
208 pub fn run<C, T>(&mut self, mut coroutine: C) -> Result<GmailSendOutput<T>, GmailClientStdError>
211 where
212 C: GmailCoroutine<Yield = GmailYield, Return = Result<GmailSendOutput<T>, GmailSendError>>,
213 {
214 let mut buf = [0u8; READ_BUFFER_SIZE];
215 let mut arg: Option<&[u8]> = None;
216
217 loop {
218 match coroutine.resume(arg.take()) {
219 GmailCoroutineState::Complete(Ok(out)) => return Ok(out),
220 GmailCoroutineState::Complete(Err(err)) => return Err(err.into()),
221 GmailCoroutineState::Yielded(GmailYield::WantsRead) => {
222 let n = self.stream.read(&mut buf)?;
223 arg = Some(&buf[..n]);
224 }
225 GmailCoroutineState::Yielded(GmailYield::WantsWrite(bytes)) => {
226 self.stream.write_all(&bytes)?;
227 arg = None;
228 }
229 }
230 }
231 }
232
233 pub fn profile_get(&mut self) -> Result<GmailSendOutput<GmailProfile>, GmailClientStdError> {
235 let coroutine = GmailProfileGet::new(&self.auth, &self.user_id)?;
236 self.run(coroutine)
237 }
238
239 pub fn watch(
241 &mut self,
242 request: &GmailWatchRequest,
243 ) -> Result<GmailSendOutput<GmailWatchResponse>, GmailClientStdError> {
244 let coroutine = GmailWatch::new(&self.auth, &self.user_id, request)?;
245 self.run(coroutine)
246 }
247
248 pub fn stop(&mut self) -> Result<GmailSendOutput<GmailNoResponse>, GmailClientStdError> {
250 let coroutine = GmailStop::new(&self.auth, &self.user_id)?;
251 self.run(coroutine)
252 }
253
254 pub fn labels_list(
256 &mut self,
257 ) -> Result<GmailSendOutput<GmailLabelsListResponse>, GmailClientStdError> {
258 let coroutine = GmailLabelsList::new(&self.auth, &self.user_id)?;
259 self.run(coroutine)
260 }
261
262 pub fn label_get(
264 &mut self,
265 id: &str,
266 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
267 let coroutine = GmailLabelGet::new(&self.auth, &self.user_id, id)?;
268 self.run(coroutine)
269 }
270
271 pub fn label_create(
273 &mut self,
274 label: &GmailLabel,
275 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
276 let coroutine = GmailLabelCreate::new(&self.auth, &self.user_id, label)?;
277 self.run(coroutine)
278 }
279
280 pub fn label_update(
282 &mut self,
283 label: &GmailLabel,
284 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
285 let coroutine = GmailLabelUpdate::new(&self.auth, &self.user_id, label)?;
286 self.run(coroutine)
287 }
288
289 pub fn label_patch(
291 &mut self,
292 label: &GmailLabel,
293 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
294 let coroutine = GmailLabelPatch::new(&self.auth, &self.user_id, label)?;
295 self.run(coroutine)
296 }
297
298 pub fn label_delete(
300 &mut self,
301 id: &str,
302 ) -> Result<GmailSendOutput<GmailNoResponse>, GmailClientStdError> {
303 let coroutine = GmailLabelDelete::new(&self.auth, &self.user_id, id)?;
304 self.run(coroutine)
305 }
306
307 pub fn messages_list(
309 &mut self,
310 params: &GmailMessagesListParams,
311 ) -> Result<GmailSendOutput<GmailMessagesListResponse>, GmailClientStdError> {
312 let coroutine = GmailMessagesList::new(&self.auth, &self.user_id, params)?;
313 self.run(coroutine)
314 }
315
316 pub fn message_get(
318 &mut self,
319 id: &str,
320 format: GmailMessageFormat,
321 metadata_headers: &[&str],
322 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
323 let coroutine =
324 GmailMessageGet::new(&self.auth, &self.user_id, id, format, metadata_headers)?;
325 self.run(coroutine)
326 }
327
328 pub fn message_send(
330 &mut self,
331 message: &GmailMessage,
332 ) -> Result<GmailSendOutput<GmailMessageId>, GmailClientStdError> {
333 let coroutine = GmailMessageSend::new(&self.auth, &self.user_id, message)?;
334 self.run(coroutine)
335 }
336
337 pub fn message_modify(
339 &mut self,
340 id: &str,
341 add_label_ids: &[String],
342 remove_label_ids: &[String],
343 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
344 let coroutine = GmailMessageModify::new(
345 &self.auth,
346 &self.user_id,
347 id,
348 add_label_ids,
349 remove_label_ids,
350 )?;
351 self.run(coroutine)
352 }
353
354 pub fn message_trash(
356 &mut self,
357 id: &str,
358 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
359 let coroutine = GmailMessageTrash::new(&self.auth, &self.user_id, id)?;
360 self.run(coroutine)
361 }
362
363 pub fn message_untrash(
365 &mut self,
366 id: &str,
367 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
368 let coroutine = GmailMessageUntrash::new(&self.auth, &self.user_id, id)?;
369 self.run(coroutine)
370 }
371
372 pub fn message_delete(
374 &mut self,
375 id: &str,
376 ) -> Result<GmailSendOutput<GmailNoResponse>, GmailClientStdError> {
377 let coroutine = GmailMessageDelete::new(&self.auth, &self.user_id, id)?;
378 self.run(coroutine)
379 }
380}
381
382impl fmt::Debug for GmailClientStd {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 f.debug_struct("GmailClientStd")
385 .field("auth", &self.auth)
386 .field("user_id", &self.user_id)
387 .finish_non_exhaustive()
388 }
389}
390
391pub trait GmailStream: Read + Write + Send + Any {
394 fn as_any_mut(&mut self) -> &mut dyn Any;
396}
397
398impl<T: Read + Write + Send + Any> GmailStream for T {
399 fn as_any_mut(&mut self) -> &mut dyn Any {
400 self
401 }
402}