Improvements and testing.
parent
a23d97e3a4
commit
e24039ab12
|
@ -18,7 +18,8 @@ chrono = { version = "0.4", features = ["serde"]}
|
||||||
reqwest = "0.11"
|
reqwest = "0.11"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
|
||||||
#[dev-dependencies]
|
[features]
|
||||||
|
debug = []
|
||||||
|
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
debug = true
|
debug = true
|
|
@ -5,7 +5,8 @@ use reqwest::header::CONTENT_TYPE;
|
||||||
use crate::{client::Client, core::session::URLPart};
|
use crate::{client::Client, core::session::URLPart};
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub async fn download(&self, account_id: &str, blob_id: &str) -> crate::Result<Vec<u8>> {
|
pub async fn download(&self, blob_id: &str) -> crate::Result<Vec<u8>> {
|
||||||
|
let account_id = self.default_account_id();
|
||||||
let mut download_url = String::with_capacity(
|
let mut download_url = String::with_capacity(
|
||||||
self.session().download_url().len() + account_id.len() + blob_id.len(),
|
self.session().download_url().len() + account_id.len() + blob_id.len(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,10 +23,10 @@ pub struct UploadResponse {
|
||||||
impl Client {
|
impl Client {
|
||||||
pub async fn upload(
|
pub async fn upload(
|
||||||
&self,
|
&self,
|
||||||
account_id: &str,
|
|
||||||
content_type: Option<&str>,
|
|
||||||
blob: Vec<u8>,
|
blob: Vec<u8>,
|
||||||
|
content_type: Option<&str>,
|
||||||
) -> crate::Result<UploadResponse> {
|
) -> crate::Result<UploadResponse> {
|
||||||
|
let account_id = self.default_account_id();
|
||||||
let mut upload_url =
|
let mut upload_url =
|
||||||
String::with_capacity(self.session().upload_url().len() + account_id.len());
|
String::with_capacity(self.session().upload_url().len() + account_id.len());
|
||||||
|
|
||||||
|
|
|
@ -80,8 +80,9 @@ impl Client {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_timeout(&mut self, timeout: u64) {
|
pub fn set_timeout(&mut self, timeout: u64) -> &mut Self {
|
||||||
self.timeout = timeout;
|
self.timeout = timeout;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timeout(&self) -> u64 {
|
pub fn timeout(&self) -> u64 {
|
||||||
|
@ -143,8 +144,9 @@ impl Client {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_default_account_id(&mut self, defaul_account_id: impl Into<String>) {
|
pub fn set_default_account_id(&mut self, defaul_account_id: impl Into<String>) -> &mut Self {
|
||||||
self.default_account_id = defaul_account_id.into();
|
self.default_account_id = defaul_account_id.into();
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_account_id(&self) -> &str {
|
pub fn default_account_id(&self) -> &str {
|
||||||
|
@ -186,8 +188,23 @@ impl Client {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::email::{EmailBodyPart, Header, Property};
|
||||||
|
|
||||||
fn _test_serialize() {
|
#[test]
|
||||||
|
fn test_serialize() {
|
||||||
|
println!(
|
||||||
|
"{:?}",
|
||||||
|
serde_json::from_slice::<EmailBodyPart>(
|
||||||
|
br#"{
|
||||||
|
"partId": "0",
|
||||||
|
"header:X-Custom-Header": "123",
|
||||||
|
"type": "text/html",
|
||||||
|
"charset": "us-ascii",
|
||||||
|
"size": 175
|
||||||
|
}"#
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
/*let coco = request
|
/*let coco = request
|
||||||
.send()
|
.send()
|
||||||
|
|
|
@ -14,21 +14,24 @@ pub struct QueryRequest<F, S, A: Default> {
|
||||||
sort: Option<Vec<Comparator<S>>>,
|
sort: Option<Vec<Comparator<S>>>,
|
||||||
|
|
||||||
#[serde(rename = "position")]
|
#[serde(rename = "position")]
|
||||||
position: i32,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
position: Option<i32>,
|
||||||
|
|
||||||
#[serde(rename = "anchor")]
|
#[serde(rename = "anchor")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
anchor: Option<String>,
|
anchor: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "anchorOffset")]
|
#[serde(rename = "anchorOffset")]
|
||||||
anchor_offset: i32,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
anchor_offset: Option<i32>,
|
||||||
|
|
||||||
#[serde(rename = "limit")]
|
#[serde(rename = "limit")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
limit: Option<usize>,
|
limit: Option<usize>,
|
||||||
|
|
||||||
#[serde(rename = "calculateTotal")]
|
#[serde(rename = "calculateTotal")]
|
||||||
calculate_total: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
calculate_total: Option<bool>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
arguments: A,
|
arguments: A,
|
||||||
|
@ -78,7 +81,7 @@ pub struct QueryResponse {
|
||||||
query_state: String,
|
query_state: String,
|
||||||
|
|
||||||
#[serde(rename = "canCalculateChanges")]
|
#[serde(rename = "canCalculateChanges")]
|
||||||
can_calculate_changes: bool,
|
can_calculate_changes: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "position")]
|
#[serde(rename = "position")]
|
||||||
position: i32,
|
position: i32,
|
||||||
|
@ -99,11 +102,11 @@ impl<F, S, A: Default> QueryRequest<F, S, A> {
|
||||||
account_id,
|
account_id,
|
||||||
filter: None,
|
filter: None,
|
||||||
sort: None,
|
sort: None,
|
||||||
position: 0,
|
position: None,
|
||||||
anchor: None,
|
anchor: None,
|
||||||
anchor_offset: 0,
|
anchor_offset: None,
|
||||||
limit: None,
|
limit: None,
|
||||||
calculate_total: false,
|
calculate_total: None,
|
||||||
arguments: A::default(),
|
arguments: A::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +127,7 @@ impl<F, S, A: Default> QueryRequest<F, S, A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position(&mut self, position: i32) -> &mut Self {
|
pub fn position(&mut self, position: i32) -> &mut Self {
|
||||||
self.position = position;
|
self.position = position.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +137,7 @@ impl<F, S, A: Default> QueryRequest<F, S, A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn anchor_offset(&mut self, anchor_offset: i32) -> &mut Self {
|
pub fn anchor_offset(&mut self, anchor_offset: i32) -> &mut Self {
|
||||||
self.anchor_offset = anchor_offset;
|
self.anchor_offset = anchor_offset.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +177,7 @@ impl QueryResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_calculate_changes(&self) -> bool {
|
pub fn can_calculate_changes(&self) -> bool {
|
||||||
self.can_calculate_changes
|
self.can_calculate_changes.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::Get;
|
use crate::Get;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field,
|
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Header,
|
||||||
|
HeaderValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Email<Get> {
|
impl Email<Get> {
|
||||||
|
@ -109,12 +110,17 @@ impl Email<Get> {
|
||||||
*self.has_attachment.as_ref().unwrap_or(&false)
|
*self.has_attachment.as_ref().unwrap_or(&false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header(&self, id: &str) -> Option<&Field> {
|
pub fn header(&self, id: &Header) -> Option<&HeaderValue> {
|
||||||
self.others.get(id).and_then(|v| v.as_ref())
|
self.headers.get(id).and_then(|v| v.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_header(&self, id: &str) -> bool {
|
pub fn has_header(&self, id: &Header) -> bool {
|
||||||
self.others.contains_key(id)
|
self.headers.contains_key(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
pub fn into_test(self) -> super::TestEmail {
|
||||||
|
self.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +141,10 @@ impl EmailBodyPart<Get> {
|
||||||
self.headers.as_deref()
|
self.headers.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn header(&self, id: &Header) -> Option<&HeaderValue> {
|
||||||
|
self.header.as_ref().and_then(|v| v.get(id))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
self.name.as_deref()
|
self.name.as_deref()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{import::EmailImportResponse, Email, Property};
|
use super::{
|
||||||
|
import::EmailImportResponse, parse::EmailParseResponse, BodyProperty, Email, Property,
|
||||||
|
};
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub async fn email_import<T, U>(
|
pub async fn email_import<T, U>(
|
||||||
|
@ -20,10 +22,7 @@ impl Client {
|
||||||
T: IntoIterator<Item = U>,
|
T: IntoIterator<Item = U>,
|
||||||
U: Into<String>,
|
U: Into<String>,
|
||||||
{
|
{
|
||||||
let blob_id = self
|
let blob_id = self.upload(raw_message, None).await?.unwrap_blob_id();
|
||||||
.upload(self.default_account_id(), None, raw_message)
|
|
||||||
.await?
|
|
||||||
.unwrap_blob_id();
|
|
||||||
let mut request = self.build();
|
let mut request = self.build();
|
||||||
let import_request = request
|
let import_request = request
|
||||||
.import_email()
|
.import_email()
|
||||||
|
@ -107,12 +106,12 @@ impl Client {
|
||||||
pub async fn email_get(
|
pub async fn email_get(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &str,
|
id: &str,
|
||||||
properties: Option<Vec<Property>>,
|
properties: Option<impl IntoIterator<Item = Property>>,
|
||||||
) -> crate::Result<Option<Email>> {
|
) -> crate::Result<Option<Email>> {
|
||||||
let mut request = self.build();
|
let mut request = self.build();
|
||||||
let get_request = request.get_email().ids([id]);
|
let get_request = request.get_email().ids([id]);
|
||||||
if let Some(properties) = properties {
|
if let Some(properties) = properties {
|
||||||
get_request.properties(properties.into_iter());
|
get_request.properties(properties);
|
||||||
}
|
}
|
||||||
request
|
request
|
||||||
.send_single::<EmailGetResponse>()
|
.send_single::<EmailGetResponse>()
|
||||||
|
@ -135,4 +134,33 @@ impl Client {
|
||||||
}
|
}
|
||||||
request.send_single::<QueryResponse>().await
|
request.send_single::<QueryResponse>().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn email_parse(
|
||||||
|
&mut self,
|
||||||
|
blob_id: &str,
|
||||||
|
properties: Option<impl IntoIterator<Item = Property>>,
|
||||||
|
body_properties: Option<impl IntoIterator<Item = BodyProperty>>,
|
||||||
|
max_body_value_bytes: Option<usize>,
|
||||||
|
) -> crate::Result<Email> {
|
||||||
|
let mut request = self.build();
|
||||||
|
let parse_request = request.parse_email().blob_ids([blob_id]);
|
||||||
|
if let Some(properties) = properties {
|
||||||
|
parse_request.properties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(body_properties) = body_properties {
|
||||||
|
parse_request.body_properties(body_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(max_body_value_bytes) = max_body_value_bytes {
|
||||||
|
parse_request
|
||||||
|
.fetch_all_body_values(true)
|
||||||
|
.max_body_value_bytes(max_body_value_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
request
|
||||||
|
.send_single::<EmailParseResponse>()
|
||||||
|
.await
|
||||||
|
.and_then(|mut r| r.parsed(blob_id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
651
src/email/mod.rs
651
src/email/mod.rs
|
@ -7,7 +7,7 @@ pub mod search_snippet;
|
||||||
pub mod set;
|
pub mod set;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{de::Visitor, Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
|
@ -56,47 +56,67 @@ pub struct Email<State = Get> {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
received_at: Option<DateTime<Utc>>,
|
received_at: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
#[serde(rename = "messageId", alias = "header:Message-ID:asMessageIds")]
|
#[cfg_attr(
|
||||||
|
not(feature = "debug"),
|
||||||
|
serde(alias = "header:Message-ID:asMessageIds")
|
||||||
|
)]
|
||||||
|
#[serde(rename = "messageId")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
message_id: Option<Vec<String>>,
|
message_id: Option<Vec<String>>,
|
||||||
|
|
||||||
#[serde(rename = "inReplyTo", alias = "header:In-Reply-To:asMessageIds")]
|
#[serde(rename = "inReplyTo")]
|
||||||
|
#[cfg_attr(
|
||||||
|
not(feature = "debug"),
|
||||||
|
serde(alias = "header:In-Reply-To:asMessageIds")
|
||||||
|
)]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
in_reply_to: Option<Vec<String>>,
|
in_reply_to: Option<Vec<String>>,
|
||||||
|
|
||||||
#[serde(rename = "references", alias = "header:References:asMessageIds")]
|
#[serde(rename = "references")]
|
||||||
|
#[cfg_attr(
|
||||||
|
not(feature = "debug"),
|
||||||
|
serde(alias = "header:References:asMessageIds")
|
||||||
|
)]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
references: Option<Vec<String>>,
|
references: Option<Vec<String>>,
|
||||||
|
|
||||||
#[serde(rename = "sender", alias = "header:Sender:asAddresses")]
|
#[serde(rename = "sender")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:Sender:asAddresses"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
sender: Option<Vec<EmailAddress>>,
|
sender: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
#[serde(rename = "from", alias = "header:From:asAddresses")]
|
#[serde(rename = "from")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:From:asAddresses"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
from: Option<Vec<EmailAddress>>,
|
from: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
#[serde(rename = "to", alias = "header:To:asAddresses")]
|
#[serde(rename = "to")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:To:asAddresses"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
to: Option<Vec<EmailAddress>>,
|
to: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
#[serde(rename = "cc", alias = "header:Cc:asAddresses")]
|
#[serde(rename = "cc")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:Cc:asAddresses"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
cc: Option<Vec<EmailAddress>>,
|
cc: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
#[serde(rename = "bcc", alias = "header:Bcc:asAddresses")]
|
#[serde(rename = "bcc")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:Bcc:asAddresses"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
bcc: Option<Vec<EmailAddress>>,
|
bcc: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
#[serde(rename = "replyTo", alias = "header:Reply-To:asAddresses")]
|
#[serde(rename = "replyTo")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:Reply-To:asAddresses"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
reply_to: Option<Vec<EmailAddress>>,
|
reply_to: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
#[serde(rename = "subject", alias = "header:Subject:asText")]
|
#[serde(rename = "subject")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:Subject:asText"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
subject: Option<String>,
|
subject: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "sentAt", alias = "header:Date:asDate")]
|
#[serde(rename = "sentAt")]
|
||||||
|
#[cfg_attr(not(feature = "debug"), serde(alias = "header:Date:asDate"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
sent_at: Option<DateTime<Utc>>,
|
sent_at: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
|
@ -130,7 +150,12 @@ pub struct Email<State = Get> {
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
others: HashMap<String, Option<Field>>,
|
headers: HashMap<Header, Option<HeaderValue>>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
#[serde(skip_deserializing)]
|
||||||
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
patch: HashMap<String, bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -185,6 +210,10 @@ pub struct EmailBodyPart<State = Get> {
|
||||||
#[serde(rename = "subParts")]
|
#[serde(rename = "subParts")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
sub_parts: Option<Vec<EmailBodyPart>>,
|
sub_parts: Option<Vec<EmailBodyPart>>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
header: Option<HashMap<Header, HeaderValue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -202,17 +231,6 @@ pub struct EmailBodyValue<State = Get> {
|
||||||
is_truncated: bool,
|
is_truncated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum Field {
|
|
||||||
Text(String),
|
|
||||||
TextList(Vec<String>),
|
|
||||||
Date(DateTime<Utc>),
|
|
||||||
Addresses(Vec<EmailAddress>),
|
|
||||||
GroupedAddresses(Vec<EmailAddressGroup>),
|
|
||||||
Bool(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct EmailAddress<State = Get> {
|
pub struct EmailAddress<State = Get> {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
@ -240,86 +258,100 @@ pub struct EmailHeader<State = Get> {
|
||||||
value: String,
|
value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Property {
|
pub enum Property {
|
||||||
#[serde(rename = "id")]
|
|
||||||
Id,
|
Id,
|
||||||
#[serde(rename = "blobId")]
|
|
||||||
BlobId,
|
BlobId,
|
||||||
#[serde(rename = "threadId")]
|
|
||||||
ThreadId,
|
ThreadId,
|
||||||
#[serde(rename = "mailboxIds")]
|
|
||||||
MailboxIds,
|
MailboxIds,
|
||||||
#[serde(rename = "keywords")]
|
|
||||||
Keywords,
|
Keywords,
|
||||||
#[serde(rename = "size")]
|
|
||||||
Size,
|
Size,
|
||||||
#[serde(rename = "receivedAt")]
|
|
||||||
ReceivedAt,
|
ReceivedAt,
|
||||||
#[serde(rename = "messageId")]
|
|
||||||
MessageId,
|
MessageId,
|
||||||
#[serde(rename = "inReplyTo")]
|
|
||||||
InReplyTo,
|
InReplyTo,
|
||||||
#[serde(rename = "references")]
|
|
||||||
References,
|
References,
|
||||||
#[serde(rename = "sender")]
|
|
||||||
Sender,
|
Sender,
|
||||||
#[serde(rename = "from")]
|
|
||||||
From,
|
From,
|
||||||
#[serde(rename = "to")]
|
|
||||||
To,
|
To,
|
||||||
#[serde(rename = "cc")]
|
|
||||||
Cc,
|
Cc,
|
||||||
#[serde(rename = "bcc")]
|
|
||||||
Bcc,
|
Bcc,
|
||||||
#[serde(rename = "replyTo")]
|
|
||||||
ReplyTo,
|
ReplyTo,
|
||||||
#[serde(rename = "subject")]
|
|
||||||
Subject,
|
Subject,
|
||||||
#[serde(rename = "sentAt")]
|
|
||||||
SentAt,
|
SentAt,
|
||||||
#[serde(rename = "bodyStructure")]
|
|
||||||
BodyStructure,
|
BodyStructure,
|
||||||
#[serde(rename = "bodyValues")]
|
|
||||||
BodyValues,
|
BodyValues,
|
||||||
#[serde(rename = "textBody")]
|
|
||||||
TextBody,
|
TextBody,
|
||||||
#[serde(rename = "htmlBody")]
|
|
||||||
HtmlBody,
|
HtmlBody,
|
||||||
#[serde(rename = "attachments")]
|
|
||||||
Attachments,
|
Attachments,
|
||||||
#[serde(rename = "hasAttachment")]
|
|
||||||
HasAttachment,
|
HasAttachment,
|
||||||
#[serde(rename = "preview")]
|
|
||||||
Preview,
|
Preview,
|
||||||
|
Header(Header),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum BodyProperty {
|
#[serde(untagged)]
|
||||||
#[serde(rename = "partId")]
|
pub enum HeaderValue {
|
||||||
PartId,
|
AsDate(DateTime<Utc>),
|
||||||
#[serde(rename = "blobId")]
|
AsDateAll(Vec<DateTime<Utc>>),
|
||||||
BlobId,
|
AsText(String),
|
||||||
#[serde(rename = "size")]
|
AsTextAll(Vec<String>),
|
||||||
Size,
|
AsTextListAll(Vec<Vec<String>>),
|
||||||
#[serde(rename = "headers")]
|
AsAddressesAll(Vec<Vec<EmailAddress>>),
|
||||||
Headers,
|
AsAddresses(Vec<EmailAddress>),
|
||||||
#[serde(rename = "name")]
|
AsGroupedAddressesAll(Vec<Vec<EmailAddressGroup>>),
|
||||||
Name,
|
AsGroupedAddresses(Vec<EmailAddressGroup>),
|
||||||
#[serde(rename = "type")]
|
}
|
||||||
Type,
|
|
||||||
#[serde(rename = "charset")]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||||
Charset,
|
pub struct Header {
|
||||||
#[serde(rename = "disposition")]
|
pub name: String,
|
||||||
Disposition,
|
pub form: HeaderForm,
|
||||||
#[serde(rename = "cid")]
|
pub all: bool,
|
||||||
Cid,
|
}
|
||||||
#[serde(rename = "language")]
|
|
||||||
Language,
|
#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)]
|
||||||
#[serde(rename = "location")]
|
pub enum HeaderForm {
|
||||||
Location,
|
Raw,
|
||||||
#[serde(rename = "subParts")]
|
Text,
|
||||||
SubParts,
|
Addresses,
|
||||||
|
GroupedAddresses,
|
||||||
|
MessageIds,
|
||||||
|
Date,
|
||||||
|
URLs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn parse(value: &str) -> Option<Self> {
|
||||||
|
match value {
|
||||||
|
"id" => Some(Property::Id),
|
||||||
|
"blobId" => Some(Property::BlobId),
|
||||||
|
"threadId" => Some(Property::ThreadId),
|
||||||
|
"mailboxIds" => Some(Property::MailboxIds),
|
||||||
|
"keywords" => Some(Property::Keywords),
|
||||||
|
"size" => Some(Property::Size),
|
||||||
|
"receivedAt" => Some(Property::ReceivedAt),
|
||||||
|
"messageId" => Some(Property::MessageId),
|
||||||
|
"inReplyTo" => Some(Property::InReplyTo),
|
||||||
|
"references" => Some(Property::References),
|
||||||
|
"sender" => Some(Property::Sender),
|
||||||
|
"from" => Some(Property::From),
|
||||||
|
"to" => Some(Property::To),
|
||||||
|
"cc" => Some(Property::Cc),
|
||||||
|
"bcc" => Some(Property::Bcc),
|
||||||
|
"replyTo" => Some(Property::ReplyTo),
|
||||||
|
"subject" => Some(Property::Subject),
|
||||||
|
"sentAt" => Some(Property::SentAt),
|
||||||
|
"hasAttachment" => Some(Property::HasAttachment),
|
||||||
|
"preview" => Some(Property::Preview),
|
||||||
|
"bodyValues" => Some(Property::BodyValues),
|
||||||
|
"textBody" => Some(Property::TextBody),
|
||||||
|
"htmlBody" => Some(Property::HtmlBody),
|
||||||
|
"attachments" => Some(Property::Attachments),
|
||||||
|
"bodyStructure" => Some(Property::BodyStructure),
|
||||||
|
_ if value.starts_with("header:") => Some(Property::Header(Header::parse(value)?)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Property {
|
impl Display for Property {
|
||||||
|
@ -350,10 +382,303 @@ impl Display for Property {
|
||||||
Property::Attachments => write!(f, "attachments"),
|
Property::Attachments => write!(f, "attachments"),
|
||||||
Property::HasAttachment => write!(f, "hasAttachment"),
|
Property::HasAttachment => write!(f, "hasAttachment"),
|
||||||
Property::Preview => write!(f, "preview"),
|
Property::Preview => write!(f, "preview"),
|
||||||
|
Property::Header(header) => header.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for Property {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PropertyVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for PropertyVisitor {
|
||||||
|
type Value = Property;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a valid JMAP e-mail property")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Property::parse(v).ok_or_else(|| {
|
||||||
|
serde::de::Error::custom(format!("Failed to parse JMAP property '{}'", v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Property {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(PropertyVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Header {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HeaderVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for HeaderVisitor {
|
||||||
|
type Value = Header;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a valid JMAP header")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Header::parse(v)
|
||||||
|
.ok_or_else(|| serde::de::Error::custom(format!("Failed to parse JMAP header '{}'", v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Header {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(HeaderVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeaderForm {
|
||||||
|
pub fn parse(value: &str) -> Option<HeaderForm> {
|
||||||
|
match value {
|
||||||
|
"asText" => Some(HeaderForm::Text),
|
||||||
|
"asAddresses" => Some(HeaderForm::Addresses),
|
||||||
|
"asGroupedAddresses" => Some(HeaderForm::GroupedAddresses),
|
||||||
|
"asMessageIds" => Some(HeaderForm::MessageIds),
|
||||||
|
"asDate" => Some(HeaderForm::Date),
|
||||||
|
"asURLs" => Some(HeaderForm::URLs),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
pub fn as_raw(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::Raw,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_text(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::Text,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_addresses(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::Addresses,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_grouped_addresses(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::GroupedAddresses,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_message_ids(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::MessageIds,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_date(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::Date,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_urls(name: impl Into<String>, all: bool) -> Header {
|
||||||
|
Header {
|
||||||
|
name: name.into(),
|
||||||
|
form: HeaderForm::URLs,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(value: &str) -> Option<Header> {
|
||||||
|
let mut all = false;
|
||||||
|
let mut form = HeaderForm::Raw;
|
||||||
|
let mut header = None;
|
||||||
|
for (pos, part) in value.split(':').enumerate() {
|
||||||
|
match pos {
|
||||||
|
0 if part == "header" => (),
|
||||||
|
1 => {
|
||||||
|
header = part.into();
|
||||||
|
}
|
||||||
|
2 | 3 if part == "all" => all = true,
|
||||||
|
2 => {
|
||||||
|
form = HeaderForm::parse(part)?;
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Header {
|
||||||
|
name: header?.to_string(),
|
||||||
|
form,
|
||||||
|
all,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Header {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "header:")?;
|
||||||
|
self.name.fmt(f)?;
|
||||||
|
self.form.fmt(f)?;
|
||||||
|
if self.all {
|
||||||
|
write!(f, ":all")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HeaderForm {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
HeaderForm::Raw => Ok(()),
|
||||||
|
HeaderForm::Text => write!(f, ":asText"),
|
||||||
|
HeaderForm::Addresses => write!(f, ":asAddresses"),
|
||||||
|
HeaderForm::GroupedAddresses => write!(f, ":asGroupedAddresses"),
|
||||||
|
HeaderForm::MessageIds => write!(f, ":asMessageIds"),
|
||||||
|
HeaderForm::Date => write!(f, ":asDate"),
|
||||||
|
HeaderForm::URLs => write!(f, ":asURLs"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum BodyProperty {
|
||||||
|
PartId,
|
||||||
|
BlobId,
|
||||||
|
Size,
|
||||||
|
Headers,
|
||||||
|
Name,
|
||||||
|
Type,
|
||||||
|
Charset,
|
||||||
|
Disposition,
|
||||||
|
Cid,
|
||||||
|
Language,
|
||||||
|
Location,
|
||||||
|
SubParts,
|
||||||
|
Header(Header),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyProperty {
|
||||||
|
fn parse(value: &str) -> Option<BodyProperty> {
|
||||||
|
match value {
|
||||||
|
"partId" => Some(BodyProperty::PartId),
|
||||||
|
"blobId" => Some(BodyProperty::BlobId),
|
||||||
|
"size" => Some(BodyProperty::Size),
|
||||||
|
"name" => Some(BodyProperty::Name),
|
||||||
|
"type" => Some(BodyProperty::Type),
|
||||||
|
"charset" => Some(BodyProperty::Charset),
|
||||||
|
"headers" => Some(BodyProperty::Headers),
|
||||||
|
"disposition" => Some(BodyProperty::Disposition),
|
||||||
|
"cid" => Some(BodyProperty::Cid),
|
||||||
|
"language" => Some(BodyProperty::Language),
|
||||||
|
"location" => Some(BodyProperty::Location),
|
||||||
|
"subParts" => Some(BodyProperty::SubParts),
|
||||||
|
_ if value.starts_with("header:") => Some(BodyProperty::Header(Header::parse(value)?)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BodyProperty {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
BodyProperty::PartId => write!(f, "partId"),
|
||||||
|
BodyProperty::BlobId => write!(f, "blobId"),
|
||||||
|
BodyProperty::Size => write!(f, "size"),
|
||||||
|
BodyProperty::Name => write!(f, "name"),
|
||||||
|
BodyProperty::Type => write!(f, "type"),
|
||||||
|
BodyProperty::Charset => write!(f, "charset"),
|
||||||
|
BodyProperty::Header(header) => header.fmt(f),
|
||||||
|
BodyProperty::Headers => write!(f, "headers"),
|
||||||
|
BodyProperty::Disposition => write!(f, "disposition"),
|
||||||
|
BodyProperty::Cid => write!(f, "cid"),
|
||||||
|
BodyProperty::Language => write!(f, "language"),
|
||||||
|
BodyProperty::Location => write!(f, "location"),
|
||||||
|
BodyProperty::SubParts => write!(f, "subParts"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for BodyProperty {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BodyPropertyVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for BodyPropertyVisitor {
|
||||||
|
type Value = BodyProperty;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a valid JMAP body property")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
BodyProperty::parse(v).ok_or_else(|| {
|
||||||
|
serde::de::Error::custom(format!("Failed to parse JMAP body property '{}'", v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for BodyProperty {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(BodyPropertyVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct MailCapabilities {
|
pub struct MailCapabilities {
|
||||||
#[serde(rename = "maxMailboxesPerEmail")]
|
#[serde(rename = "maxMailboxesPerEmail")]
|
||||||
|
@ -387,7 +712,8 @@ pub struct SubmissionCapabilities {
|
||||||
#[derive(Debug, Clone, Serialize, Default)]
|
#[derive(Debug, Clone, Serialize, Default)]
|
||||||
pub struct QueryArguments {
|
pub struct QueryArguments {
|
||||||
#[serde(rename = "collapseThreads")]
|
#[serde(rename = "collapseThreads")]
|
||||||
collapse_threads: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
collapse_threads: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Default)]
|
#[derive(Debug, Clone, Serialize, Default)]
|
||||||
|
@ -395,19 +721,27 @@ pub struct GetArguments {
|
||||||
#[serde(rename = "bodyProperties")]
|
#[serde(rename = "bodyProperties")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
body_properties: Option<Vec<BodyProperty>>,
|
body_properties: Option<Vec<BodyProperty>>,
|
||||||
|
|
||||||
#[serde(rename = "fetchTextBodyValues")]
|
#[serde(rename = "fetchTextBodyValues")]
|
||||||
fetch_text_body_values: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
fetch_text_body_values: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "fetchHTMLBodyValues")]
|
#[serde(rename = "fetchHTMLBodyValues")]
|
||||||
fetch_html_body_values: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
fetch_html_body_values: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "fetchAllBodyValues")]
|
#[serde(rename = "fetchAllBodyValues")]
|
||||||
fetch_all_body_values: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
fetch_all_body_values: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "maxBodyValueBytes")]
|
#[serde(rename = "maxBodyValueBytes")]
|
||||||
max_body_value_bytes: usize,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
max_body_value_bytes: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryArguments {
|
impl QueryArguments {
|
||||||
pub fn collapse_threads(&mut self, collapse_threads: bool) {
|
pub fn collapse_threads(&mut self, collapse_threads: bool) {
|
||||||
self.collapse_threads = collapse_threads;
|
self.collapse_threads = collapse_threads.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,22 +755,22 @@ impl GetArguments {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_text_body_values(&mut self, fetch_text_body_values: bool) -> &mut Self {
|
pub fn fetch_text_body_values(&mut self, fetch_text_body_values: bool) -> &mut Self {
|
||||||
self.fetch_text_body_values = fetch_text_body_values;
|
self.fetch_text_body_values = fetch_text_body_values.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_html_body_values(&mut self, fetch_html_body_values: bool) -> &mut Self {
|
pub fn fetch_html_body_values(&mut self, fetch_html_body_values: bool) -> &mut Self {
|
||||||
self.fetch_html_body_values = fetch_html_body_values;
|
self.fetch_html_body_values = fetch_html_body_values.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_all_body_values(&mut self, fetch_all_body_values: bool) -> &mut Self {
|
pub fn fetch_all_body_values(&mut self, fetch_all_body_values: bool) -> &mut Self {
|
||||||
self.fetch_all_body_values = fetch_all_body_values;
|
self.fetch_all_body_values = fetch_all_body_values.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_body_value_bytes(&mut self, max_body_value_bytes: usize) -> &mut Self {
|
pub fn max_body_value_bytes(&mut self, max_body_value_bytes: usize) -> &mut Self {
|
||||||
self.max_body_value_bytes = max_body_value_bytes;
|
self.max_body_value_bytes = max_body_value_bytes.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,3 +810,150 @@ impl SubmissionCapabilities {
|
||||||
&self.submission_extensions
|
&self.submission_extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TestEmail {
|
||||||
|
#[serde(rename = "mailboxIds")]
|
||||||
|
pub mailbox_ids: Option<BTreeMap<String, bool>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub keywords: Option<BTreeMap<String, bool>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub size: Option<usize>,
|
||||||
|
|
||||||
|
#[serde(rename = "receivedAt")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub received_at: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
|
#[serde(rename = "messageId")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub message_id: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[serde(rename = "inReplyTo")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub in_reply_to: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub references: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub sender: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub from: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub to: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub cc: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub bcc: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
|
#[serde(rename = "replyTo")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub reply_to: Option<Vec<EmailAddress>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub subject: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "sentAt")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub sent_at: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
|
#[serde(rename = "bodyStructure")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub body_structure: Option<Box<EmailBodyPart>>,
|
||||||
|
|
||||||
|
#[serde(rename = "bodyValues")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub body_values: Option<BTreeMap<String, EmailBodyValue>>,
|
||||||
|
|
||||||
|
#[serde(rename = "textBody")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub text_body: Option<Vec<EmailBodyPart>>,
|
||||||
|
|
||||||
|
#[serde(rename = "htmlBody")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub html_body: Option<Vec<EmailBodyPart>>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub attachments: Option<Vec<EmailBodyPart>>,
|
||||||
|
|
||||||
|
#[serde(rename = "hasAttachment")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub has_attachment: Option<bool>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub preview: Option<String>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
pub headers: BTreeMap<Header, Option<HeaderValue>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
impl From<Email> for TestEmail {
|
||||||
|
fn from(email: Email) -> Self {
|
||||||
|
TestEmail {
|
||||||
|
mailbox_ids: email.mailbox_ids.map(|ids| ids.into_iter().collect()),
|
||||||
|
keywords: email
|
||||||
|
.keywords
|
||||||
|
.map(|keywords| keywords.into_iter().collect()),
|
||||||
|
size: email.size,
|
||||||
|
received_at: email.received_at,
|
||||||
|
message_id: email.message_id,
|
||||||
|
in_reply_to: email.in_reply_to,
|
||||||
|
references: email.references,
|
||||||
|
sender: email.sender,
|
||||||
|
from: email.from,
|
||||||
|
to: email.to,
|
||||||
|
cc: email.cc,
|
||||||
|
bcc: email.bcc,
|
||||||
|
reply_to: email.reply_to,
|
||||||
|
subject: email.subject,
|
||||||
|
sent_at: email.sent_at,
|
||||||
|
body_structure: email.body_structure.map(|b| b.into_sorted_part().into()),
|
||||||
|
body_values: email
|
||||||
|
.body_values
|
||||||
|
.map(|body_values| body_values.into_iter().collect()),
|
||||||
|
text_body: email
|
||||||
|
.text_body
|
||||||
|
.map(|parts| parts.into_iter().map(|b| b.into_sorted_part()).collect()),
|
||||||
|
html_body: email
|
||||||
|
.html_body
|
||||||
|
.map(|parts| parts.into_iter().map(|b| b.into_sorted_part()).collect()),
|
||||||
|
attachments: email
|
||||||
|
.attachments
|
||||||
|
.map(|parts| parts.into_iter().map(|b| b.into_sorted_part()).collect()),
|
||||||
|
has_attachment: email.has_attachment,
|
||||||
|
preview: email.preview,
|
||||||
|
headers: email.headers.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
impl EmailBodyPart {
|
||||||
|
pub fn sort_headers(&mut self) {
|
||||||
|
if let Some(headers) = self.headers.as_mut() {
|
||||||
|
headers.sort_unstable_by_key(|h| (h.name.clone(), h.value.clone()));
|
||||||
|
}
|
||||||
|
if let Some(subparts) = self.sub_parts.as_mut() {
|
||||||
|
for sub_part in subparts {
|
||||||
|
sub_part.sort_headers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_sorted_part(mut self) -> Self {
|
||||||
|
self.sort_headers();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
use super::{BodyProperty, Email, Property};
|
use super::{BodyProperty, Email, Property};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
@ -13,22 +15,28 @@ pub struct EmailParseRequest {
|
||||||
blob_ids: Vec<String>,
|
blob_ids: Vec<String>,
|
||||||
|
|
||||||
#[serde(rename = "properties")]
|
#[serde(rename = "properties")]
|
||||||
properties: Vec<Property>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
properties: Option<Vec<Property>>,
|
||||||
|
|
||||||
#[serde(rename = "bodyProperties")]
|
#[serde(rename = "bodyProperties")]
|
||||||
body_properties: Vec<BodyProperty>,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
body_properties: Option<Vec<BodyProperty>>,
|
||||||
|
|
||||||
#[serde(rename = "fetchTextBodyValues")]
|
#[serde(rename = "fetchTextBodyValues")]
|
||||||
fetch_text_body_values: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
fetch_text_body_values: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "fetchHTMLBodyValues")]
|
#[serde(rename = "fetchHTMLBodyValues")]
|
||||||
fetch_html_body_values: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
fetch_html_body_values: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "fetchAllBodyValues")]
|
#[serde(rename = "fetchAllBodyValues")]
|
||||||
fetch_all_body_values: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
fetch_all_body_values: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename = "maxBodyValueBytes")]
|
#[serde(rename = "maxBodyValueBytes")]
|
||||||
max_body_value_bytes: usize,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
max_body_value_bytes: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -51,12 +59,12 @@ impl EmailParseRequest {
|
||||||
EmailParseRequest {
|
EmailParseRequest {
|
||||||
account_id,
|
account_id,
|
||||||
blob_ids: Vec::new(),
|
blob_ids: Vec::new(),
|
||||||
properties: Vec::new(),
|
properties: None,
|
||||||
body_properties: Vec::new(),
|
body_properties: None,
|
||||||
fetch_text_body_values: false,
|
fetch_text_body_values: None,
|
||||||
fetch_html_body_values: false,
|
fetch_html_body_values: None,
|
||||||
fetch_all_body_values: false,
|
fetch_all_body_values: None,
|
||||||
max_body_value_bytes: 0,
|
max_body_value_bytes: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +78,7 @@ impl EmailParseRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn properties(&mut self, properties: impl IntoIterator<Item = Property>) -> &mut Self {
|
pub fn properties(&mut self, properties: impl IntoIterator<Item = Property>) -> &mut Self {
|
||||||
self.properties = properties.into_iter().collect();
|
self.properties = Some(properties.into_iter().collect());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,27 +86,27 @@ impl EmailParseRequest {
|
||||||
&mut self,
|
&mut self,
|
||||||
body_properties: impl IntoIterator<Item = BodyProperty>,
|
body_properties: impl IntoIterator<Item = BodyProperty>,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.body_properties = body_properties.into_iter().collect();
|
self.body_properties = Some(body_properties.into_iter().collect());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_text_body_values(&mut self, fetch_text_body_values: bool) -> &mut Self {
|
pub fn fetch_text_body_values(&mut self, fetch_text_body_values: bool) -> &mut Self {
|
||||||
self.fetch_text_body_values = fetch_text_body_values;
|
self.fetch_text_body_values = fetch_text_body_values.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_html_body_values(&mut self, fetch_html_body_values: bool) -> &mut Self {
|
pub fn fetch_html_body_values(&mut self, fetch_html_body_values: bool) -> &mut Self {
|
||||||
self.fetch_html_body_values = fetch_html_body_values;
|
self.fetch_html_body_values = fetch_html_body_values.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_all_body_values(&mut self, fetch_all_body_values: bool) -> &mut Self {
|
pub fn fetch_all_body_values(&mut self, fetch_all_body_values: bool) -> &mut Self {
|
||||||
self.fetch_all_body_values = fetch_all_body_values;
|
self.fetch_all_body_values = fetch_all_body_values.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_body_value_bytes(&mut self, max_body_value_bytes: usize) -> &mut Self {
|
pub fn max_body_value_bytes(&mut self, max_body_value_bytes: usize) -> &mut Self {
|
||||||
self.max_body_value_bytes = max_body_value_bytes;
|
self.max_body_value_bytes = max_body_value_bytes.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,12 +116,26 @@ impl EmailParseResponse {
|
||||||
&self.account_id
|
&self.account_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parsed(&self) -> Option<impl Iterator<Item = &String>> {
|
pub fn parsed(&mut self, blob_id: &str) -> crate::Result<Email> {
|
||||||
self.parsed.as_ref().map(|map| map.keys())
|
if let Some(result) = self.parsed.as_mut().and_then(|r| r.remove(blob_id)) {
|
||||||
|
Ok(result)
|
||||||
|
} else if self
|
||||||
|
.not_parsable
|
||||||
|
.as_ref()
|
||||||
|
.map(|np| np.iter().any(|id| id == blob_id))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
Err(Error::Internal(format!(
|
||||||
|
"blobId {} is not parsable.",
|
||||||
|
blob_id
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(Error::Internal(format!("blobId {} not found.", blob_id)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parsed_details(&self, id: &str) -> Option<&Email> {
|
pub fn parsed_list(&self) -> Option<impl Iterator<Item = (&String, &Email)>> {
|
||||||
self.parsed.as_ref().and_then(|map| map.get(id))
|
self.parsed.as_ref().map(|map| map.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_parsable(&self) -> Option<&[String]> {
|
pub fn not_parsable(&self) -> Option<&[String]> {
|
||||||
|
|
|
@ -9,7 +9,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field,
|
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Header,
|
||||||
|
HeaderValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Email<Set> {
|
impl Email<Set> {
|
||||||
|
@ -31,10 +32,7 @@ impl Email<Set> {
|
||||||
|
|
||||||
pub fn mailbox_id(&mut self, mailbox_id: &str, set: bool) -> &mut Self {
|
pub fn mailbox_id(&mut self, mailbox_id: &str, set: bool) -> &mut Self {
|
||||||
self.mailbox_ids = None;
|
self.mailbox_ids = None;
|
||||||
self.others.insert(
|
self.patch.insert(format!("mailboxIds/{}", mailbox_id), set);
|
||||||
format!("mailboxIds/{}", mailbox_id),
|
|
||||||
Field::Bool(set).into(),
|
|
||||||
);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +47,7 @@ impl Email<Set> {
|
||||||
|
|
||||||
pub fn keyword(&mut self, keyword: &str, set: bool) -> &mut Self {
|
pub fn keyword(&mut self, keyword: &str, set: bool) -> &mut Self {
|
||||||
self.keywords = None;
|
self.keywords = None;
|
||||||
self.others
|
self.patch.insert(format!("keywords/{}", keyword), set);
|
||||||
.insert(format!("keywords/{}", keyword), Field::Bool(set).into());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,8 +171,8 @@ impl Email<Set> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header(&mut self, header: String, value: impl Into<Field>) -> &mut Self {
|
pub fn header(&mut self, header: Header, value: impl Into<HeaderValue>) -> &mut Self {
|
||||||
self.others.insert(header, Some(value.into()));
|
self.headers.insert(header, Some(value.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +208,8 @@ impl Create for Email<Set> {
|
||||||
attachments: Default::default(),
|
attachments: Default::default(),
|
||||||
has_attachment: Default::default(),
|
has_attachment: Default::default(),
|
||||||
preview: Default::default(),
|
preview: Default::default(),
|
||||||
others: Default::default(),
|
headers: Default::default(),
|
||||||
|
patch: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +233,7 @@ impl EmailBodyPart {
|
||||||
language: None,
|
language: None,
|
||||||
location: None,
|
location: None,
|
||||||
sub_parts: None,
|
sub_parts: None,
|
||||||
|
header: None,
|
||||||
_state: Default::default(),
|
_state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@ impl Mailbox<Get> {
|
||||||
self.id.as_ref().unwrap()
|
self.id.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unwrap_id(self) -> String {
|
||||||
|
self.id.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
self.name.as_ref().unwrap()
|
self.name.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::{client::Client, core::response::ThreadGetResponse};
|
||||||
|
|
||||||
|
use super::Thread;
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub async fn thread_get(&mut self, id: &str) -> crate::Result<Option<Thread>> {
|
||||||
|
let mut request = self.build();
|
||||||
|
request.get_thread().ids([id]);
|
||||||
|
request
|
||||||
|
.send_single::<ThreadGetResponse>()
|
||||||
|
.await
|
||||||
|
.map(|mut r| r.unwrap_list().pop())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod get;
|
pub mod get;
|
||||||
|
pub mod helpers;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue