Improvements and testing.
parent
a23d97e3a4
commit
e24039ab12
|
@ -18,7 +18,8 @@ chrono = { version = "0.4", features = ["serde"]}
|
|||
reqwest = "0.11"
|
||||
base64 = "0.13"
|
||||
|
||||
#[dev-dependencies]
|
||||
[features]
|
||||
debug = []
|
||||
|
||||
[profile.bench]
|
||||
debug = true
|
|
@ -5,7 +5,8 @@ use reqwest::header::CONTENT_TYPE;
|
|||
use crate::{client::Client, core::session::URLPart};
|
||||
|
||||
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(
|
||||
self.session().download_url().len() + account_id.len() + blob_id.len(),
|
||||
);
|
||||
|
|
|
@ -23,10 +23,10 @@ pub struct UploadResponse {
|
|||
impl Client {
|
||||
pub async fn upload(
|
||||
&self,
|
||||
account_id: &str,
|
||||
content_type: Option<&str>,
|
||||
blob: Vec<u8>,
|
||||
content_type: Option<&str>,
|
||||
) -> crate::Result<UploadResponse> {
|
||||
let account_id = self.default_account_id();
|
||||
let mut upload_url =
|
||||
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
|
||||
}
|
||||
|
||||
pub fn timeout(&self) -> u64 {
|
||||
|
@ -143,8 +144,9 @@ impl Client {
|
|||
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
|
||||
}
|
||||
|
||||
pub fn default_account_id(&self) -> &str {
|
||||
|
@ -186,8 +188,23 @@ impl Client {
|
|||
|
||||
#[cfg(test)]
|
||||
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
|
||||
.send()
|
||||
|
|
|
@ -14,21 +14,24 @@ pub struct QueryRequest<F, S, A: Default> {
|
|||
sort: Option<Vec<Comparator<S>>>,
|
||||
|
||||
#[serde(rename = "position")]
|
||||
position: i32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
position: Option<i32>,
|
||||
|
||||
#[serde(rename = "anchor")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
anchor: Option<String>,
|
||||
|
||||
#[serde(rename = "anchorOffset")]
|
||||
anchor_offset: i32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
anchor_offset: Option<i32>,
|
||||
|
||||
#[serde(rename = "limit")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
limit: Option<usize>,
|
||||
|
||||
#[serde(rename = "calculateTotal")]
|
||||
calculate_total: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
calculate_total: Option<bool>,
|
||||
|
||||
#[serde(flatten)]
|
||||
arguments: A,
|
||||
|
@ -78,7 +81,7 @@ pub struct QueryResponse {
|
|||
query_state: String,
|
||||
|
||||
#[serde(rename = "canCalculateChanges")]
|
||||
can_calculate_changes: bool,
|
||||
can_calculate_changes: Option<bool>,
|
||||
|
||||
#[serde(rename = "position")]
|
||||
position: i32,
|
||||
|
@ -99,11 +102,11 @@ impl<F, S, A: Default> QueryRequest<F, S, A> {
|
|||
account_id,
|
||||
filter: None,
|
||||
sort: None,
|
||||
position: 0,
|
||||
position: None,
|
||||
anchor: None,
|
||||
anchor_offset: 0,
|
||||
anchor_offset: None,
|
||||
limit: None,
|
||||
calculate_total: false,
|
||||
calculate_total: None,
|
||||
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 {
|
||||
self.position = position;
|
||||
self.position = position.into();
|
||||
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 {
|
||||
self.anchor_offset = anchor_offset;
|
||||
self.anchor_offset = anchor_offset.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -174,7 +177,7 @@ impl QueryResponse {
|
|||
}
|
||||
|
||||
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 super::{
|
||||
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field,
|
||||
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Header,
|
||||
HeaderValue,
|
||||
};
|
||||
|
||||
impl Email<Get> {
|
||||
|
@ -109,12 +110,17 @@ impl Email<Get> {
|
|||
*self.has_attachment.as_ref().unwrap_or(&false)
|
||||
}
|
||||
|
||||
pub fn header(&self, id: &str) -> Option<&Field> {
|
||||
self.others.get(id).and_then(|v| v.as_ref())
|
||||
pub fn header(&self, id: &Header) -> Option<&HeaderValue> {
|
||||
self.headers.get(id).and_then(|v| v.as_ref())
|
||||
}
|
||||
|
||||
pub fn has_header(&self, id: &str) -> bool {
|
||||
self.others.contains_key(id)
|
||||
pub fn has_header(&self, id: &Header) -> bool {
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn header(&self, id: &Header) -> Option<&HeaderValue> {
|
||||
self.header.as_ref().and_then(|v| v.get(id))
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
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 {
|
||||
pub async fn email_import<T, U>(
|
||||
|
@ -20,10 +22,7 @@ impl Client {
|
|||
T: IntoIterator<Item = U>,
|
||||
U: Into<String>,
|
||||
{
|
||||
let blob_id = self
|
||||
.upload(self.default_account_id(), None, raw_message)
|
||||
.await?
|
||||
.unwrap_blob_id();
|
||||
let blob_id = self.upload(raw_message, None).await?.unwrap_blob_id();
|
||||
let mut request = self.build();
|
||||
let import_request = request
|
||||
.import_email()
|
||||
|
@ -107,12 +106,12 @@ impl Client {
|
|||
pub async fn email_get(
|
||||
&mut self,
|
||||
id: &str,
|
||||
properties: Option<Vec<Property>>,
|
||||
properties: Option<impl IntoIterator<Item = Property>>,
|
||||
) -> crate::Result<Option<Email>> {
|
||||
let mut request = self.build();
|
||||
let get_request = request.get_email().ids([id]);
|
||||
if let Some(properties) = properties {
|
||||
get_request.properties(properties.into_iter());
|
||||
get_request.properties(properties);
|
||||
}
|
||||
request
|
||||
.send_single::<EmailGetResponse>()
|
||||
|
@ -135,4 +134,33 @@ impl Client {
|
|||
}
|
||||
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;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{de::Visitor, Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Display, Formatter},
|
||||
|
@ -56,47 +56,67 @@ pub struct Email<State = Get> {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
sent_at: Option<DateTime<Utc>>,
|
||||
|
||||
|
@ -130,7 +150,12 @@ pub struct Email<State = Get> {
|
|||
|
||||
#[serde(flatten)]
|
||||
#[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)]
|
||||
|
@ -185,6 +210,10 @@ pub struct EmailBodyPart<State = Get> {
|
|||
#[serde(rename = "subParts")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
sub_parts: Option<Vec<EmailBodyPart>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
header: Option<HashMap<Header, HeaderValue>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -202,17 +231,6 @@ pub struct EmailBodyValue<State = Get> {
|
|||
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)]
|
||||
pub struct EmailAddress<State = Get> {
|
||||
#[serde(skip)]
|
||||
|
@ -240,86 +258,100 @@ pub struct EmailHeader<State = Get> {
|
|||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Property {
|
||||
#[serde(rename = "id")]
|
||||
Id,
|
||||
#[serde(rename = "blobId")]
|
||||
BlobId,
|
||||
#[serde(rename = "threadId")]
|
||||
ThreadId,
|
||||
#[serde(rename = "mailboxIds")]
|
||||
MailboxIds,
|
||||
#[serde(rename = "keywords")]
|
||||
Keywords,
|
||||
#[serde(rename = "size")]
|
||||
Size,
|
||||
#[serde(rename = "receivedAt")]
|
||||
ReceivedAt,
|
||||
#[serde(rename = "messageId")]
|
||||
MessageId,
|
||||
#[serde(rename = "inReplyTo")]
|
||||
InReplyTo,
|
||||
#[serde(rename = "references")]
|
||||
References,
|
||||
#[serde(rename = "sender")]
|
||||
Sender,
|
||||
#[serde(rename = "from")]
|
||||
From,
|
||||
#[serde(rename = "to")]
|
||||
To,
|
||||
#[serde(rename = "cc")]
|
||||
Cc,
|
||||
#[serde(rename = "bcc")]
|
||||
Bcc,
|
||||
#[serde(rename = "replyTo")]
|
||||
ReplyTo,
|
||||
#[serde(rename = "subject")]
|
||||
Subject,
|
||||
#[serde(rename = "sentAt")]
|
||||
SentAt,
|
||||
#[serde(rename = "bodyStructure")]
|
||||
BodyStructure,
|
||||
#[serde(rename = "bodyValues")]
|
||||
BodyValues,
|
||||
#[serde(rename = "textBody")]
|
||||
TextBody,
|
||||
#[serde(rename = "htmlBody")]
|
||||
HtmlBody,
|
||||
#[serde(rename = "attachments")]
|
||||
Attachments,
|
||||
#[serde(rename = "hasAttachment")]
|
||||
HasAttachment,
|
||||
#[serde(rename = "preview")]
|
||||
Preview,
|
||||
Header(Header),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum BodyProperty {
|
||||
#[serde(rename = "partId")]
|
||||
PartId,
|
||||
#[serde(rename = "blobId")]
|
||||
BlobId,
|
||||
#[serde(rename = "size")]
|
||||
Size,
|
||||
#[serde(rename = "headers")]
|
||||
Headers,
|
||||
#[serde(rename = "name")]
|
||||
Name,
|
||||
#[serde(rename = "type")]
|
||||
Type,
|
||||
#[serde(rename = "charset")]
|
||||
Charset,
|
||||
#[serde(rename = "disposition")]
|
||||
Disposition,
|
||||
#[serde(rename = "cid")]
|
||||
Cid,
|
||||
#[serde(rename = "language")]
|
||||
Language,
|
||||
#[serde(rename = "location")]
|
||||
Location,
|
||||
#[serde(rename = "subParts")]
|
||||
SubParts,
|
||||
#[serde(untagged)]
|
||||
pub enum HeaderValue {
|
||||
AsDate(DateTime<Utc>),
|
||||
AsDateAll(Vec<DateTime<Utc>>),
|
||||
AsText(String),
|
||||
AsTextAll(Vec<String>),
|
||||
AsTextListAll(Vec<Vec<String>>),
|
||||
AsAddressesAll(Vec<Vec<EmailAddress>>),
|
||||
AsAddresses(Vec<EmailAddress>),
|
||||
AsGroupedAddressesAll(Vec<Vec<EmailAddressGroup>>),
|
||||
AsGroupedAddresses(Vec<EmailAddressGroup>),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||
pub struct Header {
|
||||
pub name: String,
|
||||
pub form: HeaderForm,
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)]
|
||||
pub enum HeaderForm {
|
||||
Raw,
|
||||
Text,
|
||||
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 {
|
||||
|
@ -350,10 +382,303 @@ impl Display for Property {
|
|||
Property::Attachments => write!(f, "attachments"),
|
||||
Property::HasAttachment => write!(f, "hasAttachment"),
|
||||
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)]
|
||||
pub struct MailCapabilities {
|
||||
#[serde(rename = "maxMailboxesPerEmail")]
|
||||
|
@ -387,7 +712,8 @@ pub struct SubmissionCapabilities {
|
|||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct QueryArguments {
|
||||
#[serde(rename = "collapseThreads")]
|
||||
collapse_threads: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
collapse_threads: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
|
@ -395,19 +721,27 @@ pub struct GetArguments {
|
|||
#[serde(rename = "bodyProperties")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
body_properties: Option<Vec<BodyProperty>>,
|
||||
|
||||
#[serde(rename = "fetchTextBodyValues")]
|
||||
fetch_text_body_values: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fetch_text_body_values: Option<bool>,
|
||||
|
||||
#[serde(rename = "fetchHTMLBodyValues")]
|
||||
fetch_html_body_values: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fetch_html_body_values: Option<bool>,
|
||||
|
||||
#[serde(rename = "fetchAllBodyValues")]
|
||||
fetch_all_body_values: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fetch_all_body_values: Option<bool>,
|
||||
|
||||
#[serde(rename = "maxBodyValueBytes")]
|
||||
max_body_value_bytes: usize,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
max_body_value_bytes: Option<usize>,
|
||||
}
|
||||
|
||||
impl QueryArguments {
|
||||
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 {
|
||||
self.fetch_text_body_values = fetch_text_body_values;
|
||||
self.fetch_text_body_values = fetch_text_body_values.into();
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -476,3 +810,150 @@ impl SubmissionCapabilities {
|
|||
&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 crate::Error;
|
||||
|
||||
use super::{BodyProperty, Email, Property};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
|
@ -13,22 +15,28 @@ pub struct EmailParseRequest {
|
|||
blob_ids: Vec<String>,
|
||||
|
||||
#[serde(rename = "properties")]
|
||||
properties: Vec<Property>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
properties: Option<Vec<Property>>,
|
||||
|
||||
#[serde(rename = "bodyProperties")]
|
||||
body_properties: Vec<BodyProperty>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
body_properties: Option<Vec<BodyProperty>>,
|
||||
|
||||
#[serde(rename = "fetchTextBodyValues")]
|
||||
fetch_text_body_values: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fetch_text_body_values: Option<bool>,
|
||||
|
||||
#[serde(rename = "fetchHTMLBodyValues")]
|
||||
fetch_html_body_values: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fetch_html_body_values: Option<bool>,
|
||||
|
||||
#[serde(rename = "fetchAllBodyValues")]
|
||||
fetch_all_body_values: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fetch_all_body_values: Option<bool>,
|
||||
|
||||
#[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)]
|
||||
|
@ -51,12 +59,12 @@ impl EmailParseRequest {
|
|||
EmailParseRequest {
|
||||
account_id,
|
||||
blob_ids: Vec::new(),
|
||||
properties: Vec::new(),
|
||||
body_properties: Vec::new(),
|
||||
fetch_text_body_values: false,
|
||||
fetch_html_body_values: false,
|
||||
fetch_all_body_values: false,
|
||||
max_body_value_bytes: 0,
|
||||
properties: None,
|
||||
body_properties: None,
|
||||
fetch_text_body_values: None,
|
||||
fetch_html_body_values: None,
|
||||
fetch_all_body_values: None,
|
||||
max_body_value_bytes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +78,7 @@ impl EmailParseRequest {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -78,27 +86,27 @@ impl EmailParseRequest {
|
|||
&mut self,
|
||||
body_properties: impl IntoIterator<Item = BodyProperty>,
|
||||
) -> &mut Self {
|
||||
self.body_properties = body_properties.into_iter().collect();
|
||||
self.body_properties = Some(body_properties.into_iter().collect());
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -108,12 +116,26 @@ impl EmailParseResponse {
|
|||
&self.account_id
|
||||
}
|
||||
|
||||
pub fn parsed(&self) -> Option<impl Iterator<Item = &String>> {
|
||||
self.parsed.as_ref().map(|map| map.keys())
|
||||
pub fn parsed(&mut self, blob_id: &str) -> crate::Result<Email> {
|
||||
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> {
|
||||
self.parsed.as_ref().and_then(|map| map.get(id))
|
||||
pub fn parsed_list(&self) -> Option<impl Iterator<Item = (&String, &Email)>> {
|
||||
self.parsed.as_ref().map(|map| map.iter())
|
||||
}
|
||||
|
||||
pub fn not_parsable(&self) -> Option<&[String]> {
|
||||
|
|
|
@ -9,7 +9,8 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Field,
|
||||
Email, EmailAddress, EmailAddressGroup, EmailBodyPart, EmailBodyValue, EmailHeader, Header,
|
||||
HeaderValue,
|
||||
};
|
||||
|
||||
impl Email<Set> {
|
||||
|
@ -31,10 +32,7 @@ impl Email<Set> {
|
|||
|
||||
pub fn mailbox_id(&mut self, mailbox_id: &str, set: bool) -> &mut Self {
|
||||
self.mailbox_ids = None;
|
||||
self.others.insert(
|
||||
format!("mailboxIds/{}", mailbox_id),
|
||||
Field::Bool(set).into(),
|
||||
);
|
||||
self.patch.insert(format!("mailboxIds/{}", mailbox_id), set);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -49,8 +47,7 @@ impl Email<Set> {
|
|||
|
||||
pub fn keyword(&mut self, keyword: &str, set: bool) -> &mut Self {
|
||||
self.keywords = None;
|
||||
self.others
|
||||
.insert(format!("keywords/{}", keyword), Field::Bool(set).into());
|
||||
self.patch.insert(format!("keywords/{}", keyword), set);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -174,8 +171,8 @@ impl Email<Set> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn header(&mut self, header: String, value: impl Into<Field>) -> &mut Self {
|
||||
self.others.insert(header, Some(value.into()));
|
||||
pub fn header(&mut self, header: Header, value: impl Into<HeaderValue>) -> &mut Self {
|
||||
self.headers.insert(header, Some(value.into()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +208,8 @@ impl Create for Email<Set> {
|
|||
attachments: Default::default(),
|
||||
has_attachment: Default::default(),
|
||||
preview: Default::default(),
|
||||
others: Default::default(),
|
||||
headers: Default::default(),
|
||||
patch: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,6 +233,7 @@ impl EmailBodyPart {
|
|||
language: None,
|
||||
location: None,
|
||||
sub_parts: None,
|
||||
header: None,
|
||||
_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ impl Mailbox<Get> {
|
|||
self.id.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn unwrap_id(self) -> String {
|
||||
self.id.unwrap()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
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 helpers;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
Loading…
Reference in New Issue