use std::ops::{Deref, DerefMut};
use serde::{de, Deserialize, Deserializer};
use time::format_description::well_known::Iso8601;
use time::{OffsetDateTime, PrimitiveDateTime};
use xml_struct::XmlSerialize;
pub mod response;
pub use self::response::{ResponseClass, ResponseMessages};
pub mod message_xml;
pub use self::message_xml::MessageXml;
pub(crate) const MESSAGES_NS_URI: &str =
"http://schemas.microsoft.com/exchange/services/2006/messages";
pub(crate) const SOAP_NS_URI: &str = "http://schemas.xmlsoap.org/soap/envelope/";
pub(crate) const TYPES_NS_URI: &str = "http://schemas.microsoft.com/exchange/services/2006/types";
#[derive(Clone, Debug, Default, XmlSerialize)]
pub struct FolderShape {
#[xml_struct(ns_prefix = "t")]
pub base_shape: BaseShape,
}
#[derive(Clone, Debug, Default, XmlSerialize)]
pub struct ItemShape {
#[xml_struct(ns_prefix = "t")]
pub base_shape: BaseShape,
#[xml_struct(ns_prefix = "t")]
pub include_mime_content: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub additional_properties: Option<Vec<PathToElement>>,
}
#[derive(Clone, Debug, XmlSerialize)]
#[xml_struct(variant_ns_prefix = "t")]
pub enum PathToElement {
ExtendedFieldURI {
#[xml_struct(attribute)]
distinguished_property_set_id: Option<DistinguishedPropertySet>,
#[xml_struct(attribute)]
property_set_id: Option<String>,
#[xml_struct(attribute)]
property_tag: Option<String>,
#[xml_struct(attribute)]
property_name: Option<String>,
#[xml_struct(attribute)]
property_id: Option<String>,
#[xml_struct(attribute)]
property_type: PropertyType,
},
#[allow(non_snake_case)]
FieldURI {
#[xml_struct(attribute)]
field_URI: String,
},
#[allow(non_snake_case)]
IndexedFieldURI {
#[xml_struct(attribute)]
field_URI: String,
#[xml_struct(attribute)]
field_index: String,
},
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, Eq, PartialEq)]
pub struct ExtendedFieldURI {
#[xml_struct(attribute)]
#[serde(rename = "@DistinguishedPropertySetId")]
pub distinguished_property_set_id: Option<DistinguishedPropertySet>,
#[xml_struct(attribute)]
#[serde(rename = "@PropertySetId")]
pub property_set_id: Option<String>,
#[xml_struct(attribute)]
#[serde(rename = "@PropertyTag")]
pub property_tag: Option<String>,
#[xml_struct(attribute)]
#[serde(rename = "@PropertyName")]
pub property_name: Option<String>,
#[xml_struct(attribute)]
#[serde(rename = "@PropertyId")]
pub property_id: Option<String>,
#[xml_struct(attribute)]
#[serde(rename = "@PropertyType")]
pub property_type: Option<PropertyType>,
}
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, Eq, PartialEq)]
#[xml_struct(text)]
pub enum DistinguishedPropertySet {
Address,
Appointment,
CalendarAssistant,
Common,
InternetHeaders,
Meeting,
PublicStrings,
Sharing,
Task,
UnifiedMessaging,
}
#[derive(Clone, Copy, Debug, XmlSerialize)]
#[xml_struct(text)]
pub enum MessageDisposition {
SaveOnly,
SendOnly,
SendAndSaveCopy,
}
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, Eq, PartialEq)]
#[xml_struct(text)]
pub enum PropertyType {
ApplicationTime,
ApplicationTimeArray,
Binary,
BinaryArray,
Boolean,
CLSID,
CLSIDArray,
Currency,
CurrencyArray,
Double,
DoubleArray,
Float,
FloatArray,
Integer,
IntegerArray,
Long,
LongArray,
Short,
ShortArray,
SystemTime,
SystemTimeArray,
String,
StringArray,
}
#[derive(Clone, Copy, Debug, Default, XmlSerialize)]
#[xml_struct(text)]
pub enum BaseShape {
IdOnly,
#[default]
Default,
AllProperties,
}
#[derive(Clone, Debug, XmlSerialize)]
pub struct CopyMoveItemData {
pub to_folder_id: BaseFolderId,
pub item_ids: Vec<BaseItemId>,
pub return_new_item_ids: Option<bool>,
}
#[derive(Clone, Debug, XmlSerialize)]
pub struct CopyMoveFolderData {
pub to_folder_id: BaseFolderId,
pub folder_ids: Vec<BaseFolderId>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct FolderResponseMessage {
pub folders: Folders,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct ItemResponseMessage {
pub items: Items,
}
#[derive(Clone, Debug, XmlSerialize)]
#[xml_struct(variant_ns_prefix = "t")]
pub enum BaseFolderId {
FolderId {
#[xml_struct(attribute)]
id: String,
#[xml_struct(attribute)]
change_key: Option<String>,
},
DistinguishedFolderId {
#[xml_struct(attribute)]
id: String,
#[xml_struct(attribute)]
change_key: Option<String>,
#[xml_struct(ns_prefix = "t")]
mailbox: Option<Mailbox>,
},
}
impl BaseFolderId {
pub fn new_folder(id: impl Into<String>) -> Self {
BaseFolderId::FolderId {
id: id.into(),
change_key: None,
}
}
pub fn new_distinguished(id: impl Into<String>) -> Self {
BaseFolderId::DistinguishedFolderId {
id: id.into(),
change_key: None,
mailbox: None,
}
}
pub fn new_distinguished_in_mailbox(id: impl Into<String>, mailbox: Mailbox) -> Self {
BaseFolderId::DistinguishedFolderId {
id: id.into(),
change_key: None,
mailbox: Some(mailbox),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, XmlSerialize, Eq)]
pub struct FolderId {
#[serde(rename = "@Id")]
pub id: String,
#[serde(rename = "@ChangeKey")]
pub change_key: Option<String>,
}
#[derive(Clone, Debug, XmlSerialize)]
#[xml_struct(text)]
pub enum DeleteType {
HardDelete,
MoveToDeletedItems,
SoftDelete,
}
#[derive(Clone, Debug, XmlSerialize)]
#[xml_struct(variant_ns_prefix = "t")]
pub enum BaseItemId {
ItemId {
#[xml_struct(attribute)]
id: String,
#[xml_struct(attribute)]
change_key: Option<String>,
},
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
pub struct ItemId {
#[xml_struct(attribute)]
#[serde(rename = "@Id")]
pub id: String,
#[serde(rename = "@ChangeKey")]
#[xml_struct(attribute)]
pub change_key: Option<String>,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, Eq, PartialEq)]
#[xml_struct(variant_ns_prefix = "t")]
pub enum Folder {
#[serde(rename_all = "PascalCase")]
CalendarFolder {
#[xml_struct(ns_prefix = "t")]
folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
parent_folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
folder_class: Option<String>,
#[xml_struct(ns_prefix = "t")]
display_name: Option<String>,
#[xml_struct(ns_prefix = "t")]
total_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
child_folder_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
extended_property: Option<Vec<ExtendedProperty>>,
},
#[serde(rename_all = "PascalCase")]
ContactsFolder {
#[xml_struct(ns_prefix = "t")]
folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
parent_folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
folder_class: Option<String>,
#[xml_struct(ns_prefix = "t")]
display_name: Option<String>,
#[xml_struct(ns_prefix = "t")]
total_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
child_folder_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
extended_property: Option<Vec<ExtendedProperty>>,
},
#[serde(rename_all = "PascalCase")]
Folder {
#[xml_struct(ns_prefix = "t")]
folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
parent_folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
folder_class: Option<String>,
#[xml_struct(ns_prefix = "t")]
display_name: Option<String>,
#[xml_struct(ns_prefix = "t")]
total_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
child_folder_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
extended_property: Option<Vec<ExtendedProperty>>,
#[xml_struct(ns_prefix = "t")]
unread_count: Option<u32>,
},
#[serde(rename_all = "PascalCase")]
SearchFolder {
#[xml_struct(ns_prefix = "t")]
folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
parent_folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
folder_class: Option<String>,
#[xml_struct(ns_prefix = "t")]
display_name: Option<String>,
#[xml_struct(ns_prefix = "t")]
total_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
child_folder_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
extended_property: Option<Vec<ExtendedProperty>>,
},
#[serde(rename_all = "PascalCase")]
TasksFolder {
#[xml_struct(ns_prefix = "t")]
folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
parent_folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
folder_class: Option<String>,
#[xml_struct(ns_prefix = "t")]
display_name: Option<String>,
#[xml_struct(ns_prefix = "t")]
total_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
child_folder_count: Option<u32>,
#[xml_struct(ns_prefix = "t")]
extended_property: Option<Vec<ExtendedProperty>>,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Items {
#[serde(rename = "$value", default)]
pub inner: Vec<RealItem>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct Folders {
#[serde(rename = "$value", default)]
pub inner: Vec<Folder>,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[xml_struct(variant_ns_prefix = "t")]
#[non_exhaustive]
pub enum RealItem {
Item(Message),
Message(Message),
CalendarItem(Message),
Contact(Message),
DistributionList(Message),
MeetingMessage(Message),
MeetingRequest(Message),
MeetingResponse(Message),
MeetingCancellation(Message),
Task(Message),
PostItem(Message),
}
impl RealItem {
pub fn inner_message(&self) -> &Message {
use RealItem::*;
match self {
Item(message)
| Message(message)
| CalendarItem(message)
| Contact(message)
| DistributionList(message)
| MeetingMessage(message)
| MeetingRequest(message)
| MeetingResponse(message)
| MeetingCancellation(message)
| Task(message)
| PostItem(message) => message,
}
}
pub fn into_inner_message(self) -> Message {
use RealItem::*;
match self {
Item(message)
| Message(message)
| CalendarItem(message)
| Contact(message)
| DistributionList(message)
| MeetingMessage(message)
| MeetingRequest(message)
| MeetingResponse(message)
| MeetingCancellation(message)
| Task(message)
| PostItem(message) => message,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DateTime(pub OffsetDateTime);
impl<'de> Deserialize<'de> for DateTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
if let Ok(date_time) = OffsetDateTime::parse(&value, &Iso8601::DEFAULT) {
Ok(Self(date_time))
} else {
PrimitiveDateTime::parse(&value, &Iso8601::DEFAULT)
.map(|date_time| Self(date_time.assume_utc()))
.map_err(de::Error::custom)
}
}
}
impl XmlSerialize for DateTime {
fn serialize_child_nodes<W>(
&self,
writer: &mut quick_xml::Writer<W>,
) -> Result<(), xml_struct::Error>
where
W: std::io::Write,
{
let time = self
.0
.format(&Iso8601::DEFAULT)
.map_err(|err| xml_struct::Error::Value(err.into()))?;
time.serialize_child_nodes(writer)
}
}
#[derive(Clone, Debug, Default, Deserialize, XmlSerialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct Message {
#[xml_struct(ns_prefix = "t")]
pub mime_content: Option<MimeContent>,
#[xml_struct(ns_prefix = "t")]
pub item_id: Option<ItemId>,
#[xml_struct(ns_prefix = "t")]
pub parent_folder_id: Option<FolderId>,
#[xml_struct(ns_prefix = "t")]
pub item_class: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub subject: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub sensitivity: Option<Sensitivity>,
#[xml_struct(ns_prefix = "t")]
pub body: Option<Body>,
#[xml_struct(ns_prefix = "t")]
pub attachments: Option<Attachments>,
#[xml_struct(ns_prefix = "t")]
pub date_time_received: Option<DateTime>,
#[xml_struct(ns_prefix = "t")]
pub size: Option<usize>,
#[xml_struct(ns_prefix = "t")]
pub categories: Option<Vec<StringElement>>,
#[xml_struct(ns_prefix = "t")]
pub extended_property: Option<Vec<ExtendedProperty>>,
#[xml_struct(ns_prefix = "t")]
pub importance: Option<Importance>,
#[xml_struct(ns_prefix = "t")]
pub in_reply_to: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub is_submitted: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub is_draft: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub is_from_me: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub is_resend: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub is_unmodified: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub internet_message_headers: Option<InternetMessageHeaders>,
#[xml_struct(ns_prefix = "t")]
pub date_time_sent: Option<DateTime>,
#[xml_struct(ns_prefix = "t")]
pub date_time_created: Option<DateTime>,
#[xml_struct(ns_prefix = "t")]
pub reminder_due_by: Option<DateTime>,
#[xml_struct(ns_prefix = "t")]
pub reminder_is_set: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub reminder_minutes_before_start: Option<usize>,
#[xml_struct(ns_prefix = "t")]
pub display_cc: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub display_to: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub has_attachments: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub culture: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub sender: Option<Recipient>,
#[xml_struct(ns_prefix = "t")]
pub to_recipients: Option<ArrayOfRecipients>,
#[xml_struct(ns_prefix = "t")]
pub cc_recipients: Option<ArrayOfRecipients>,
#[xml_struct(ns_prefix = "t")]
pub bcc_recipients: Option<ArrayOfRecipients>,
#[xml_struct(ns_prefix = "t")]
pub is_read_receipt_requested: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub is_delivery_receipt_requested: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub conversation_index: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub conversation_topic: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub from: Option<Recipient>,
#[xml_struct(ns_prefix = "t")]
pub internet_message_id: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub is_read: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub is_response_requested: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub reply_to: Option<ArrayOfRecipients>,
#[xml_struct(ns_prefix = "t")]
pub received_by: Option<Recipient>,
#[xml_struct(ns_prefix = "t")]
pub received_representing: Option<Recipient>,
#[xml_struct(ns_prefix = "t")]
pub last_modified_name: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub last_modified_time: Option<DateTime>,
#[xml_struct(ns_prefix = "t")]
pub is_associated: Option<bool>,
#[xml_struct(ns_prefix = "t")]
pub conversation_id: Option<ItemId>,
#[xml_struct(ns_prefix = "t")]
pub references: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub preview: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub flag: Option<Flag>,
}
#[allow(non_snake_case)]
#[derive(Clone, Debug, Deserialize, XmlSerialize, Eq, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct ExtendedProperty {
#[xml_struct(ns_prefix = "t")]
#[serde(rename = "ExtendedFieldURI")]
pub extended_field_URI: ExtendedFieldURI,
#[xml_struct(ns_prefix = "t")]
pub value: String,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
pub struct Attachments {
#[serde(rename = "$value")]
#[xml_struct(flatten)]
pub inner: Vec<Attachment>,
}
#[derive(Clone, Debug, Default, Deserialize, XmlSerialize, PartialEq, Eq)]
pub struct ArrayOfRecipients(
#[serde(deserialize_with = "deserialize_recipients")] pub Vec<Recipient>,
);
impl Deref for ArrayOfRecipients {
type Target = Vec<Recipient>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ArrayOfRecipients {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct Recipient {
#[xml_struct(ns_prefix = "t")]
pub mailbox: Mailbox,
}
fn deserialize_recipients<'de, D>(deserializer: D) -> Result<Vec<Recipient>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct MailboxSequence {
mailbox: Vec<Mailbox>,
}
let seq = MailboxSequence::deserialize(deserializer)?;
Ok(seq
.mailbox
.into_iter()
.map(|mailbox| Recipient { mailbox })
.collect())
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct InternetMessageHeaders {
pub internet_message_header: Vec<InternetMessageHeader>,
}
#[derive(Clone, Debug, Default, Deserialize, XmlSerialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct Mailbox {
#[xml_struct(ns_prefix = "t")]
pub name: Option<String>,
#[xml_struct(ns_prefix = "t")]
pub email_address: Option<String>,
pub routing_type: Option<String>,
pub mailbox_type: Option<MailboxType>,
pub item_id: Option<ItemId>,
}
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[xml_struct(text)]
pub enum MailboxType {
Mailbox,
PublicDL,
PrivateDL,
Contact,
PublicFolder,
Unknown,
OneOff,
GroupMailbox,
}
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[xml_struct(text)]
pub enum Importance {
Low,
Normal,
High,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct StringElement {
pub string: String,
}
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[xml_struct(text)]
pub enum Sensitivity {
Normal,
Personal,
Private,
Confidential,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
pub struct Body {
#[serde(rename = "@BodyType")]
#[xml_struct(attribute)]
pub body_type: BodyType,
#[serde(rename = "@IsTruncated")]
#[xml_struct(attribute)]
pub is_truncated: Option<bool>,
#[serde(rename = "$text")]
#[xml_struct(flatten)]
pub content: Option<String>,
}
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[xml_struct(text)]
pub enum BodyType {
HTML,
Text,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
pub enum Attachment {
#[serde(rename_all = "PascalCase")]
ItemAttachment {
attachment_id: AttachmentId,
name: String,
content_type: String,
content_id: Option<String>,
content_location: Option<String>,
size: Option<usize>,
last_modified_time: Option<DateTime>,
is_inline: Option<bool>,
#[serde(flatten)]
content: Option<Box<RealItem>>,
},
#[serde(rename_all = "PascalCase")]
FileAttachment {
attachment_id: AttachmentId,
name: String,
content_type: String,
content_id: Option<String>,
content_location: Option<String>,
size: Option<usize>,
last_modified_time: Option<DateTime>,
is_inline: Option<bool>,
is_contact_photo: Option<bool>,
content: Option<String>,
},
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
pub struct AttachmentId {
#[serde(rename = "@Id")]
#[xml_struct(attribute)]
pub id: String,
#[serde(rename = "@RootItemId")]
#[xml_struct(attribute)]
pub root_item_id: Option<String>,
#[serde(rename = "@RootItemChangeKey")]
#[xml_struct(attribute)]
pub root_item_change_key: Option<String>,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
pub struct MimeContent {
#[serde(rename = "@CharacterSet")]
#[xml_struct(attribute)]
pub character_set: Option<String>,
#[serde(rename = "$text")]
#[xml_struct(flatten)]
pub content: String,
}
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct InternetMessageHeader {
#[serde(rename = "@HeaderName")]
#[xml_struct(attribute)]
pub header_name: String,
#[serde(rename = "$text")]
#[xml_struct(flatten)]
pub value: Option<String>,
}
#[derive(Clone, Debug, XmlSerialize)]
pub enum View {
IndexedPageItemView {
#[xml_struct(attribute)]
max_entries_returned: Option<usize>,
#[xml_struct(attribute)]
base_point: BasePoint,
#[xml_struct(attribute)]
offset: usize,
},
FractionalPageItemView {
#[xml_struct(attribute)]
max_entries_returned: Option<usize>,
#[xml_struct(attribute)]
numerator: usize,
#[xml_struct(attribute)]
denominator: usize,
},
CalendarView {
#[xml_struct(attribute)]
max_entries_returned: Option<usize>,
#[xml_struct(attribute)]
start_date: String,
#[xml_struct(attribute)]
end_date: String,
},
ContactsView {
#[xml_struct(attribute)]
max_entries_returned: Option<usize>,
#[xml_struct(attribute)]
initial_name: Option<String>,
#[xml_struct(attribute)]
final_name: Option<String>,
},
}
#[derive(Clone, Debug, XmlSerialize)]
#[xml_struct(text)]
pub enum BasePoint {
Beginning,
End,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct Groups {
#[serde(rename = "$value", default)]
pub inner: Vec<GroupedItems>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct GroupedItems {
pub group_index: Option<usize>,
pub items: Items,
}
#[derive(Clone, Debug, XmlSerialize, Deserialize, Eq, PartialEq)]
#[xml_struct(text)]
pub enum FlagStatus {
NotFlagged,
Flagged,
Complete,
}
#[derive(Clone, Debug, XmlSerialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Flag {
pub flag_status: Option<FlagStatus>,
pub start_date: Option<String>,
pub due_date: Option<String>,
pub complete_date: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
test_utils::{assert_deserialized_content, assert_serialized_content, minify_xml},
Error,
};
#[test]
fn test_array_of_recipients() -> Result<(), Error> {
let xml = minify_xml(
r#"
<Recipients>
<t:Mailbox>
<t:Name>Alice Test</t:Name>
<t:EmailAddress>alice@test.com</t:EmailAddress>
</t:Mailbox>
<t:Mailbox>
<t:Name>Bob Test</t:Name>
<t:EmailAddress>bob@test.com</t:EmailAddress>
</t:Mailbox>
<t:Mailbox>
<t:Name>Charlie Test</t:Name>
</t:Mailbox>
</Recipients>"#,
);
let data = ArrayOfRecipients(vec![
Recipient {
mailbox: Mailbox {
name: Some("Alice Test".into()),
email_address: Some("alice@test.com".into()),
..Default::default()
},
},
Recipient {
mailbox: Mailbox {
name: Some("Bob Test".into()),
email_address: Some("bob@test.com".into()),
..Default::default()
},
},
Recipient {
mailbox: Mailbox {
name: Some("Charlie Test".into()),
..Default::default()
},
},
]);
assert_serialized_content(&data, "Recipients", &xml);
assert_deserialized_content(&xml, data);
Ok(())
}
#[test]
fn test_extended_property() -> Result<(), Error> {
let data = ExtendedProperty {
extended_field_URI: ExtendedFieldURI {
distinguished_property_set_id: None,
property_set_id: None,
property_tag: Some("0x007D".into()),
property_name: None,
property_id: None,
property_type: Some(PropertyType::String),
},
value: "data goes here".into(),
};
let xml = minify_xml(
r#"
<ExtendedProperty>
<t:ExtendedFieldURI PropertyTag="0x007D" PropertyType="String"/>
<t:Value>data goes here</t:Value>
</ExtendedProperty>"#,
);
assert_serialized_content(&data, "ExtendedProperty", &xml);
assert_deserialized_content(&xml, data);
Ok(())
}
#[test]
fn test_attachment_parsing() {
let item_attachment_xml = r#"
<m:Attachments>
<t:ItemAttachment>
<t:AttachmentId Id="Ktum21o=" />
<t:Name>Attached Message Item</t:Name>
<t:ContentType>message/rfc822</t:ContentType>
<t:Message>
<t:ItemId Id="AAMkAd" ChangeKey="FwAAABY" />
</t:Message>
</t:ItemAttachment>
</m:Attachments>"#;
let data = Attachments {
inner: vec![Attachment::ItemAttachment {
attachment_id: AttachmentId {
id: "Ktum21o=".to_string(),
root_item_id: None,
root_item_change_key: None,
},
name: "Attached Message Item".to_string(),
content_type: "message/rfc822".to_string(),
content_id: None,
content_location: None,
size: None,
last_modified_time: None,
is_inline: None,
content: Some(Box::new(RealItem::Message(Message {
item_id: Some(ItemId {
id: "AAMkAd".to_string(),
change_key: Some("FwAAABY".to_string()),
}),
..Default::default()
}))),
}],
};
assert_deserialized_content(item_attachment_xml, data);
let file_attachment_xml = r#"
<m:Attachments>
<t:FileAttachment>
<t:AttachmentId Id="AAAtAEFkbWluaX..."/>
<t:Name>SomeFile</t:Name>
<t:ContentType>message/rfc822</t:ContentType>
<t:Content>AQIDBAU=</t:Content>
<t:LastModifiedTime>2026-06-26T17:54:39Z</t:LastModifiedTime>
</t:FileAttachment>
</m:Attachments>"#;
let data = Attachments {
inner: vec![Attachment::FileAttachment {
attachment_id: AttachmentId {
id: "AAAtAEFkbWluaX...".to_string(),
root_item_id: None,
root_item_change_key: None,
},
name: "SomeFile".to_string(),
content_type: "message/rfc822".to_string(),
content_id: None,
content_location: None,
size: None,
last_modified_time: Some(DateTime(
OffsetDateTime::parse("2026-06-26T17:54:39Z", &Iso8601::DEFAULT).unwrap(),
)),
is_inline: None,
is_contact_photo: None,
content: Some("AQIDBAU=".to_string()),
}],
};
assert_deserialized_content(file_attachment_xml, data);
}
#[test]
fn test_deserialize_date_time_without_time_zone() {
let content = r#"
<t:Message>
<t:DateTimeReceived>2026-06-26T17:54:39</t:DateTimeReceived>
</t:Message>"#;
let expected = Message {
date_time_received: Some(DateTime(
OffsetDateTime::parse("2026-06-26T17:54:39Z", &Iso8601::DEFAULT).unwrap(),
)),
..Default::default()
};
assert_deserialized_content(content, expected);
}
#[test]
fn test_serialize_flag_status() {
let message = Message {
flag: Some(Flag {
flag_status: Some(FlagStatus::Flagged),
start_date: None,
due_date: None,
complete_date: None,
}),
..Default::default()
};
let expected = r#"<Message><t:Flag><FlagStatus>Flagged</FlagStatus></t:Flag></Message>"#;
assert_serialized_content(&message, "Message", expected);
}
#[test]
fn test_deserialize_flag_status() {
let content = r#"
<t:Message>
<t:Flag>
<t:FlagStatus>Flagged</t:FlagStatus>
<t:StartDate>2026-01-26T23:00:00Z</t:StartDate>
<t:DueDate>2026-01-26T23:00:00Z</t:DueDate>
</t:Flag>
</t:Message>"#;
let expected = Message {
flag: Some(Flag {
flag_status: Some(FlagStatus::Flagged),
start_date: Some("2026-01-26T23:00:00Z".to_string()),
due_date: Some("2026-01-26T23:00:00Z".to_string()),
complete_date: None,
}),
..Default::default()
};
assert_deserialized_content(content, expected);
}
#[test]
fn test_deserialize_empty_header() {
let content = r#"
<InternetMessageHeader HeaderName="X-Is-Empty"/>"#;
let expected = InternetMessageHeader {
header_name: "X-Is-Empty".to_string(),
value: None,
};
assert_deserialized_content(content, expected);
}
}