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, GmailLabelsListResponse, create::GmailLabelCreate, delete::GmailLabelDelete,
45 get::GmailLabelGet, list::GmailLabelsList, patch::GmailLabelPatch,
46 update::GmailLabelUpdate,
47 },
48 v1::rest::messages::{
49 GmailMessage, GmailMessageFormat, GmailMessageId, delete::GmailMessageDelete,
50 get::GmailMessageGet, list::GmailMessagesList, list::GmailMessagesListParams,
51 list::GmailMessagesListResponse, modify::GmailMessageModify, send::GmailMessageSend,
52 trash::GmailMessageTrash, untrash::GmailMessageUntrash,
53 },
54 v1::rest::users::{
55 GmailProfile, GmailWatchRequest, GmailWatchResponse, get_profile::GmailProfileGet,
56 stop::GmailStop, watch::GmailWatch,
57 },
58 v1::send::{GmailNoResponse, GmailSendError, GmailSendOutput},
59};
60
61#[derive(Debug, Error)]
63pub enum GmailClientStdError {
64 #[error(transparent)]
66 Send(#[from] GmailSendError),
67 #[error(transparent)]
69 Io(#[from] io::Error),
70 #[cfg(any(
72 feature = "rustls-aws",
73 feature = "rustls-ring",
74 feature = "native-tls"
75 ))]
76 #[error(transparent)]
77 Tls(#[from] anyhow::Error),
78 #[cfg(any(
80 feature = "rustls-aws",
81 feature = "rustls-ring",
82 feature = "native-tls"
83 ))]
84 #[error("Gmail URL `{0}` has no host")]
85 UrlMissingHost(String),
86 #[cfg(any(
88 feature = "rustls-aws",
89 feature = "rustls-ring",
90 feature = "native-tls"
91 ))]
92 #[error("Gmail URL `{url}` has unsupported scheme `{scheme}` (expected `http` or `https`)")]
93 UrlUnsupportedScheme {
94 url: String,
96 scheme: String,
98 },
99}
100
101pub struct GmailClientStdConnectOptions {
104 #[cfg(any(
106 feature = "rustls-aws",
107 feature = "rustls-ring",
108 feature = "native-tls"
109 ))]
110 pub tls: Tls,
111 pub user_id: String,
113}
114
115impl Default for GmailClientStdConnectOptions {
116 fn default() -> Self {
117 Self {
118 #[cfg(any(
119 feature = "rustls-aws",
120 feature = "rustls-ring",
121 feature = "native-tls"
122 ))]
123 tls: Tls::default(),
124 user_id: String::from("me"),
125 }
126 }
127}
128
129const READ_BUFFER_SIZE: usize = 16 * 1024;
130
131pub struct GmailClientStd {
138 pub stream: Box<dyn GmailStream>,
140 pub auth: HttpAuthBearer,
142 pub user_id: String,
144}
145
146impl GmailClientStd {
147 pub fn new<S: Read + Write + Send + 'static>(
149 stream: S,
150 token: impl ToString,
151 options: GmailClientStdConnectOptions,
152 ) -> Self {
153 Self {
154 stream: Box::new(stream),
155 auth: HttpAuthBearer::new(token.to_string()),
156 user_id: options.user_id,
157 }
158 }
159
160 #[cfg(any(
163 feature = "rustls-aws",
164 feature = "rustls-ring",
165 feature = "native-tls"
166 ))]
167 pub fn connect(
168 token: impl ToString,
169 options: GmailClientStdConnectOptions,
170 ) -> Result<Self, GmailClientStdError> {
171 let GmailClientStdConnectOptions { tls, user_id } = options;
172
173 let url = Url::parse(GMAIL_API_BASE).expect("Gmail API base URL is valid");
174 let host = url
175 .host_str()
176 .ok_or_else(|| GmailClientStdError::UrlMissingHost(url.to_string()))?;
177
178 let stream = match url.scheme() {
179 "http" => StreamStd::connect_tcp(host, url.port().unwrap_or(80))?,
180 "https" => StreamStd::connect_tls(host, url.port().unwrap_or(443), &tls)?,
181 scheme => {
182 return Err(GmailClientStdError::UrlUnsupportedScheme {
183 url: url.to_string(),
184 scheme: scheme.to_string(),
185 });
186 }
187 };
188
189 stream.set_read_timeout(Some(Duration::from_secs(30)))?;
190
191 Ok(Self {
192 stream: Box::new(stream),
193 auth: HttpAuthBearer::new(token.to_string()),
194 user_id,
195 })
196 }
197
198 pub fn set_stream<S: Read + Write + Send + 'static>(&mut self, stream: S) {
200 self.stream = Box::new(stream);
201 }
202
203 pub fn run<C, T>(&mut self, mut coroutine: C) -> Result<GmailSendOutput<T>, GmailClientStdError>
206 where
207 C: GmailCoroutine<Yield = GmailYield, Return = Result<GmailSendOutput<T>, GmailSendError>>,
208 {
209 let mut buf = [0u8; READ_BUFFER_SIZE];
210 let mut arg: Option<&[u8]> = None;
211
212 loop {
213 match coroutine.resume(arg.take()) {
214 GmailCoroutineState::Complete(Ok(out)) => return Ok(out),
215 GmailCoroutineState::Complete(Err(err)) => return Err(err.into()),
216 GmailCoroutineState::Yielded(GmailYield::WantsRead) => {
217 let n = self.stream.read(&mut buf)?;
218 arg = Some(&buf[..n]);
219 }
220 GmailCoroutineState::Yielded(GmailYield::WantsWrite(bytes)) => {
221 self.stream.write_all(&bytes)?;
222 arg = None;
223 }
224 }
225 }
226 }
227
228 pub fn profile_get(&mut self) -> Result<GmailSendOutput<GmailProfile>, GmailClientStdError> {
230 let coroutine = GmailProfileGet::new(&self.auth, &self.user_id)?;
231 self.run(coroutine)
232 }
233
234 pub fn watch(
236 &mut self,
237 request: &GmailWatchRequest,
238 ) -> Result<GmailSendOutput<GmailWatchResponse>, GmailClientStdError> {
239 let coroutine = GmailWatch::new(&self.auth, &self.user_id, request)?;
240 self.run(coroutine)
241 }
242
243 pub fn stop(&mut self) -> Result<GmailSendOutput<GmailNoResponse>, GmailClientStdError> {
245 let coroutine = GmailStop::new(&self.auth, &self.user_id)?;
246 self.run(coroutine)
247 }
248
249 pub fn labels_list(
251 &mut self,
252 ) -> Result<GmailSendOutput<GmailLabelsListResponse>, GmailClientStdError> {
253 let coroutine = GmailLabelsList::new(&self.auth, &self.user_id)?;
254 self.run(coroutine)
255 }
256
257 pub fn label_get(
259 &mut self,
260 id: &str,
261 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
262 let coroutine = GmailLabelGet::new(&self.auth, &self.user_id, id)?;
263 self.run(coroutine)
264 }
265
266 pub fn label_create(
268 &mut self,
269 label: &GmailLabel,
270 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
271 let coroutine = GmailLabelCreate::new(&self.auth, &self.user_id, label)?;
272 self.run(coroutine)
273 }
274
275 pub fn label_update(
277 &mut self,
278 label: &GmailLabel,
279 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
280 let coroutine = GmailLabelUpdate::new(&self.auth, &self.user_id, label)?;
281 self.run(coroutine)
282 }
283
284 pub fn label_patch(
286 &mut self,
287 label: &GmailLabel,
288 ) -> Result<GmailSendOutput<GmailLabel>, GmailClientStdError> {
289 let coroutine = GmailLabelPatch::new(&self.auth, &self.user_id, label)?;
290 self.run(coroutine)
291 }
292
293 pub fn label_delete(
295 &mut self,
296 id: &str,
297 ) -> Result<GmailSendOutput<GmailNoResponse>, GmailClientStdError> {
298 let coroutine = GmailLabelDelete::new(&self.auth, &self.user_id, id)?;
299 self.run(coroutine)
300 }
301
302 pub fn messages_list(
304 &mut self,
305 params: &GmailMessagesListParams,
306 ) -> Result<GmailSendOutput<GmailMessagesListResponse>, GmailClientStdError> {
307 let coroutine = GmailMessagesList::new(&self.auth, &self.user_id, params)?;
308 self.run(coroutine)
309 }
310
311 pub fn message_get(
313 &mut self,
314 id: &str,
315 format: GmailMessageFormat,
316 metadata_headers: &[&str],
317 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
318 let coroutine =
319 GmailMessageGet::new(&self.auth, &self.user_id, id, format, metadata_headers)?;
320 self.run(coroutine)
321 }
322
323 pub fn message_send(
325 &mut self,
326 message: &GmailMessage,
327 ) -> Result<GmailSendOutput<GmailMessageId>, GmailClientStdError> {
328 let coroutine = GmailMessageSend::new(&self.auth, &self.user_id, message)?;
329 self.run(coroutine)
330 }
331
332 pub fn message_modify(
334 &mut self,
335 id: &str,
336 add_label_ids: &[String],
337 remove_label_ids: &[String],
338 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
339 let coroutine = GmailMessageModify::new(
340 &self.auth,
341 &self.user_id,
342 id,
343 add_label_ids,
344 remove_label_ids,
345 )?;
346 self.run(coroutine)
347 }
348
349 pub fn message_trash(
351 &mut self,
352 id: &str,
353 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
354 let coroutine = GmailMessageTrash::new(&self.auth, &self.user_id, id)?;
355 self.run(coroutine)
356 }
357
358 pub fn message_untrash(
360 &mut self,
361 id: &str,
362 ) -> Result<GmailSendOutput<GmailMessage>, GmailClientStdError> {
363 let coroutine = GmailMessageUntrash::new(&self.auth, &self.user_id, id)?;
364 self.run(coroutine)
365 }
366
367 pub fn message_delete(
369 &mut self,
370 id: &str,
371 ) -> Result<GmailSendOutput<GmailNoResponse>, GmailClientStdError> {
372 let coroutine = GmailMessageDelete::new(&self.auth, &self.user_id, id)?;
373 self.run(coroutine)
374 }
375}
376
377impl fmt::Debug for GmailClientStd {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 f.debug_struct("GmailClientStd")
380 .field("auth", &self.auth)
381 .field("user_id", &self.user_id)
382 .finish_non_exhaustive()
383 }
384}
385
386pub trait GmailStream: Read + Write + Send + Any {
389 fn as_any_mut(&mut self) -> &mut dyn Any;
391}
392
393impl<T: Read + Write + Send + Any> GmailStream for T {
394 fn as_any_mut(&mut self) -> &mut dyn Any {
395 self
396 }
397}