Skip to main content

io_gmail/v1/rest/
messages.rs

1//! Gmail messages (`users.messages`), including attachments
2//! (`users.messages.attachments`).
3//!
4//! <https://developers.google.com/gmail/api/reference/rest/v1/users.messages>
5
6use alloc::{string::String, vec::Vec};
7
8use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
9use serde::{Deserialize, Serialize};
10
11pub mod attachments;
12pub mod batch_delete;
13pub mod batch_modify;
14pub mod delete;
15pub mod get;
16pub mod import;
17pub mod insert;
18pub mod list;
19pub mod modify;
20pub mod send;
21pub mod trash;
22pub mod untrash;
23
24/// A Gmail message resource.
25///
26/// Populated fields depend on the requested [`GmailMessageFormat`]:
27/// `payload` comes with the full format, `raw` with the raw format.
28#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
29#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
30#[serde(rename_all = "camelCase")]
31pub struct GmailMessage {
32    /// The immutable id of the message.
33    #[serde(default, skip_serializing_if = "String::is_empty")]
34    pub id: String,
35    /// The id of the thread the message belongs to.
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub thread_id: Option<String>,
38    /// The ids of the labels applied to the message.
39    #[serde(default, skip_serializing_if = "Vec::is_empty")]
40    pub label_ids: Vec<String>,
41    /// The internal message creation timestamp (epoch milliseconds),
42    /// which determines ordering in the inbox.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub internal_date: Option<String>,
45    /// A short part of the message text.
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub snippet: Option<String>,
48    /// The parsed email structure in the message parts.
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub payload: Option<GmailMessagePayload>,
51    /// The entire message as a base64url-encoded RFC 5322 string.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub raw: Option<String>,
54    /// The estimated size of the message in bytes.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub size_estimate: Option<u64>,
57    /// The id of the last history record that modified the message.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub history_id: Option<String>,
60}
61
62/// Decodes a base64url-encoded (URL-safe, no padding) raw string back
63/// into RFC 5322 message bytes.
64///
65/// Whitespace and trailing padding are stripped beforehand, so both
66/// padded and unpadded inputs decode.
67pub fn decode_raw(raw: &str) -> Result<Vec<u8>, base64::DecodeError> {
68    let normalized: String = raw.chars().filter(|c| !c.is_ascii_whitespace()).collect();
69    let normalized = normalized.trim_end_matches('=');
70    URL_SAFE_NO_PAD.decode(normalized)
71}
72
73/// Encodes raw RFC 5322 message bytes into the base64url (URL-safe,
74/// no padding) string expected by the `raw` field.
75pub fn encode_raw(raw: &[u8]) -> String {
76    URL_SAFE_NO_PAD.encode(raw)
77}
78
79/// A lightweight Gmail message resource carrying only its ids.
80#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
81#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
82#[serde(rename_all = "camelCase")]
83pub struct GmailMessageId {
84    /// The immutable id of the message.
85    pub id: String,
86    /// The id of the thread the message belongs to.
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub thread_id: Option<String>,
89}
90
91/// A single MIME part of a Gmail message.
92///
93/// The top-level part is exposed as the payload of a [`GmailMessage`];
94/// multipart containers nest their children in `parts`.
95#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
96#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
97#[serde(rename_all = "camelCase")]
98pub struct GmailMessagePayload {
99    /// The immutable id of the message part.
100    #[serde(default)]
101    pub part_id: Option<String>,
102    /// The MIME type of the part.
103    #[serde(default)]
104    pub mime_type: Option<String>,
105    /// The body of the part, which may be empty for container MIME
106    /// parts.
107    #[serde(default)]
108    pub body: Option<GmailMessagePartBody>,
109    /// The filename of the attachment; empty when the part is not an
110    /// attachment.
111    #[serde(default)]
112    pub filename: String,
113    /// The headers of the part, such as To, From or Subject.
114    #[serde(default)]
115    pub headers: Vec<GmailMessageHeader>,
116    /// The child parts of a container MIME part.
117    #[serde(default)]
118    pub parts: Vec<GmailMessagePayload>,
119}
120
121impl GmailMessagePayload {
122    /// Returns the value of the first header matching the given name,
123    /// case-insensitively.
124    pub fn header(&self, name: &str) -> Option<&str> {
125        self.headers
126            .iter()
127            .find(|header| header.name.eq_ignore_ascii_case(name))
128            .map(|header| header.value.as_str())
129    }
130}
131
132/// The body of a single MIME part of a Gmail message.
133#[derive(Debug, Clone, Default, Deserialize, Serialize, Eq, PartialEq)]
134#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
135#[serde(rename_all = "camelCase")]
136pub struct GmailMessagePartBody {
137    /// The id of an external attachment, retrievable via a separate
138    /// `users.messages.attachments.get` request.
139    #[serde(default)]
140    pub attachment_id: Option<String>,
141    /// The number of bytes of the message part data.
142    #[serde(default)]
143    pub size: u32,
144    /// The body data as a base64url-encoded string; absent when the
145    /// data lives in an external attachment.
146    #[serde(default)]
147    pub data: Option<String>,
148}
149
150/// A single header of a Gmail message part.
151#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
152#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
153pub struct GmailMessageHeader {
154    /// The name of the header.
155    pub name: String,
156    /// The value of the header.
157    pub value: String,
158}
159
160/// Amount of message detail to return (`format` query parameter).
161#[derive(Debug, Clone, Copy, Deserialize, Serialize, Eq, PartialEq)]
162#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
163pub enum GmailMessageFormat {
164    /// Returns only the message id and labels; no headers, body or
165    /// payload.
166    Minimal,
167    /// Returns the full message data, with the body parsed in the
168    /// payload field.
169    Full,
170    /// Returns the full message data as a base64url-encoded string in
171    /// the raw field.
172    Raw,
173    /// Returns only the message id, labels and headers.
174    Metadata,
175}
176
177/// Whether messages carrying a label show up in the message list.
178#[derive(Debug, Clone, Copy, Deserialize, Serialize, Eq, PartialEq)]
179#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
180#[serde(rename_all = "camelCase")]
181pub enum GmailMessageListVisibility {
182    /// Messages with the label show in the message list.
183    Show,
184    /// Messages with the label are hidden from the message list.
185    Hide,
186}
187
188/// Source of the internal date when importing or inserting a message
189/// (`internalDateSource` query parameter).
190#[derive(Debug, Clone, Copy, Deserialize, Serialize, Eq, PartialEq)]
191#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
192pub enum GmailInternalDateSource {
193    /// The internal date is the time the message was received.
194    ReceivedTime,
195    /// The internal date comes from the Date header of the message.
196    DateHeader,
197}