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