-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change undoes part of #10 following complications from making the item ID optional. See https://phabricator.services.mozilla.com/D211258 for more context. Instead of using `common::Message` for both serialization and deserialization, this change introduces a new `Message` type that is specific to the `CreateItem` operation. For now we agreed on only adding the fields that Thunderbird uses, but ultimately `create_item::Message` should include every field that `common::Message` does.
- Loading branch information
1 parent
1d6b729
commit 6d85d50
Showing
4 changed files
with
321 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,9 @@ | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
use serde::Deserialize; | ||
use std::ops::{Deref, DerefMut}; | ||
|
||
use serde::{Deserialize, Deserializer}; | ||
use time::format_description::well_known::Iso8601; | ||
use xml_struct::XmlSerialize; | ||
|
||
|
@@ -278,7 +280,7 @@ pub enum BaseItemId { | |
/// The unique identifier of an item. | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/itemid> | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq)] | ||
pub struct ItemId { | ||
#[xml_struct(attribute)] | ||
#[serde(rename = "@Id")] | ||
|
@@ -359,11 +361,18 @@ pub enum Folder { | |
}, | ||
} | ||
|
||
/// An array of items. | ||
#[derive(Debug, Deserialize)] | ||
pub struct Items { | ||
#[serde(rename = "$value", default)] | ||
pub inner: Vec<RealItem>, | ||
} | ||
|
||
/// An item which may appear as the result of a request to read or modify an | ||
/// Exchange item. | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/items> | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[derive(Debug, Deserialize)] | ||
pub enum RealItem { | ||
Message(Message), | ||
} | ||
|
@@ -372,7 +381,8 @@ pub enum RealItem { | |
/// | ||
/// See [`Attachment::ItemAttachment`] for details. | ||
// N.B.: Commented-out variants are not yet implemented. | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[non_exhaustive] | ||
#[derive(Debug, Deserialize)] | ||
pub enum AttachmentItem { | ||
// Item(Item), | ||
Message(Message), | ||
|
@@ -413,14 +423,13 @@ impl XmlSerialize for DateTime { | |
/// An email message. | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/message-ex15websvcsotherref> | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "PascalCase")] | ||
pub struct Message { | ||
/// The MIME content of the item. | ||
pub mime_content: Option<MimeContent>, | ||
|
||
/// The item's Exchange identifier. | ||
pub item_id: Option<ItemId>, | ||
pub item_id: ItemId, | ||
|
||
/// The identifier for the containing folder. | ||
/// | ||
|
@@ -436,7 +445,6 @@ pub struct Message { | |
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/subject> | ||
pub subject: Option<String>, | ||
|
||
pub sensitivity: Option<Sensitivity>, | ||
pub body: Option<Body>, | ||
pub attachments: Option<Attachments>, | ||
|
@@ -447,7 +455,6 @@ pub struct Message { | |
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/categories-ex15websvcsotherref> | ||
pub categories: Option<Vec<StringElement>>, | ||
|
||
pub importance: Option<Importance>, | ||
pub in_reply_to: Option<String>, | ||
pub is_submitted: Option<bool>, | ||
|
@@ -465,21 +472,21 @@ pub struct Message { | |
pub display_to: Option<String>, | ||
pub has_attachments: Option<bool>, | ||
pub culture: Option<String>, | ||
pub sender: Option<SingleRecipient>, | ||
pub sender: Option<Recipient>, | ||
pub to_recipients: Option<ArrayOfRecipients>, | ||
pub cc_recipients: Option<ArrayOfRecipients>, | ||
pub bcc_recipients: Option<ArrayOfRecipients>, | ||
pub is_read_receipt_requested: Option<bool>, | ||
pub is_delivery_receipt_requested: Option<bool>, | ||
pub conversation_index: Option<String>, | ||
pub conversation_topic: Option<String>, | ||
pub from: Option<SingleRecipient>, | ||
pub from: Option<Recipient>, | ||
pub internet_message_id: Option<String>, | ||
pub is_read: Option<bool>, | ||
pub is_response_requested: Option<bool>, | ||
pub reply_to: Option<SingleRecipient>, | ||
pub received_by: Option<SingleRecipient>, | ||
pub received_representing: Option<SingleRecipient>, | ||
pub reply_to: Option<Recipient>, | ||
pub received_by: Option<Recipient>, | ||
pub received_representing: Option<Recipient>, | ||
pub last_modified_name: Option<String>, | ||
pub last_modified_time: Option<DateTime>, | ||
pub is_associated: Option<bool>, | ||
|
@@ -496,18 +503,61 @@ pub struct Attachments { | |
pub inner: Vec<Attachment>, | ||
} | ||
|
||
/// A newtype around a vector of `Recipient`s, that is deserialized using | ||
/// `deserialize_recipients`. | ||
#[derive(Debug, Default, Deserialize, XmlSerialize)] | ||
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 | ||
} | ||
} | ||
|
||
/// A single mailbox. | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[derive(Debug, Deserialize, XmlSerialize, PartialEq)] | ||
#[serde(rename_all = "PascalCase")] | ||
pub struct SingleRecipient { | ||
pub struct Recipient { | ||
#[xml_struct(ns_prefix = "t")] | ||
pub mailbox: Mailbox, | ||
} | ||
|
||
/// A list of mailboxes. | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[serde(rename_all = "PascalCase")] | ||
pub struct ArrayOfRecipients { | ||
pub mailbox: Vec<Mailbox>, | ||
/// Deserializes a list of recipients. | ||
/// | ||
/// `quick-xml`'s `serde` implementation requires the presence of an | ||
/// intermediate type when dealing with lists, and this is not compatible with | ||
/// our model for serialization. | ||
/// | ||
/// We could directly deserialize into a `Vec<Mailbox>`, which would also | ||
/// simplify this function a bit, but this would mean using different models | ||
/// to represent single vs. multiple recipient(s). | ||
fn deserialize_recipients<'de, D>(deserializer: D) -> Result<Vec<Recipient>, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
#[derive(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()) | ||
} | ||
|
||
/// A list of Internet Message Format headers. | ||
|
@@ -520,13 +570,15 @@ pub struct InternetMessageHeaders { | |
/// A reference to a user or address which can send or receive mail. | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailbox> | ||
#[derive(Debug, Deserialize, XmlSerialize)] | ||
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq)] | ||
#[serde(rename_all = "PascalCase")] | ||
pub struct Mailbox { | ||
/// The name of this mailbox's user. | ||
#[xml_struct(ns_prefix = "t")] | ||
pub name: Option<String>, | ||
|
||
/// The email address for this mailbox. | ||
#[xml_struct(ns_prefix = "t")] | ||
pub email_address: String, | ||
|
||
/// The protocol used in routing to this mailbox. | ||
|
@@ -547,7 +599,7 @@ pub struct Mailbox { | |
/// A protocol used in routing mail. | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/routingtype-emailaddress> | ||
#[derive(Clone, Copy, Debug, Default, Deserialize, XmlSerialize)] | ||
#[derive(Clone, Copy, Debug, Default, Deserialize, XmlSerialize, PartialEq)] | ||
#[xml_struct(text)] | ||
pub enum RoutingType { | ||
#[default] | ||
|
@@ -558,7 +610,7 @@ pub enum RoutingType { | |
/// The type of sender or recipient a mailbox represents. | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailboxtype> | ||
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize)] | ||
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, PartialEq)] | ||
#[xml_struct(text)] | ||
pub enum MailboxType { | ||
Mailbox, | ||
|
@@ -829,3 +881,105 @@ pub struct MessageXml { | |
/// if the server is throttling operations. | ||
pub back_off_milliseconds: Option<usize>, | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use quick_xml::Writer; | ||
|
||
use super::*; | ||
use crate::Error; | ||
|
||
/// Tests that an [`ArrayOfRecipients`] correctly serializes into XML. It | ||
/// should serialize as multiple `<t:Mailbox>` elements, one per [`Recipient`]. | ||
#[test] | ||
fn serialize_array_of_recipients() -> Result<(), Error> { | ||
// Define the recipients to serialize. | ||
let alice = Recipient { | ||
mailbox: Mailbox { | ||
name: Some("Alice Test".into()), | ||
email_address: "[email protected]".into(), | ||
routing_type: None, | ||
mailbox_type: None, | ||
item_id: None, | ||
}, | ||
}; | ||
|
||
let bob = Recipient { | ||
mailbox: Mailbox { | ||
name: Some("Bob Test".into()), | ||
email_address: "[email protected]".into(), | ||
routing_type: None, | ||
mailbox_type: None, | ||
item_id: None, | ||
}, | ||
}; | ||
|
||
let recipients = ArrayOfRecipients(vec![alice, bob]); | ||
|
||
// Serialize into XML. | ||
let mut writer = { | ||
let inner: Vec<u8> = Default::default(); | ||
Writer::new(inner) | ||
}; | ||
recipients.serialize_as_element(&mut writer, "Recipients")?; | ||
|
||
// Read the contents of the `Writer`'s buffer. | ||
let buf = writer.into_inner(); | ||
let actual = std::str::from_utf8(buf.as_slice()) | ||
.map_err(|e| Error::UnexpectedResponse(e.to_string().into_bytes()))?; | ||
|
||
// Ensure the structure of the XML document is correct. | ||
let expected = "<Recipients><t:Mailbox><t:Name>Alice Test</t:Name><t:EmailAddress>[email protected]</t:EmailAddress></t:Mailbox><t:Mailbox><t:Name>Bob Test</t:Name><t:EmailAddress>[email protected]</t:EmailAddress></t:Mailbox></Recipients>"; | ||
assert_eq!(expected, actual); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Tests that deserializing a sequence of `<t:Mailbox>` XML elements | ||
/// results in an [`ArrayOfRecipients`] with one [`Recipient`] per | ||
/// `<t:Mailbox>` element. | ||
#[test] | ||
fn deserialize_array_of_recipients() -> Result<(), Error> { | ||
// The raw XML to deserialize. | ||
let xml = "<Recipients><t:Mailbox><t:Name>Alice Test</t:Name><t:EmailAddress>[email protected]</t:EmailAddress></t:Mailbox><t:Mailbox><t:Name>Bob Test</t:Name><t:EmailAddress>[email protected]</t:EmailAddress></t:Mailbox></Recipients>"; | ||
|
||
// Deserialize the raw XML, with `serde_path_to_error` to help | ||
// troubleshoot any issue. | ||
let mut de = quick_xml::de::Deserializer::from_reader(xml.as_bytes()); | ||
let recipients: ArrayOfRecipients = serde_path_to_error::deserialize(&mut de)?; | ||
|
||
// Ensure we have the right number of recipients in the resulting | ||
// `ArrayOfRecipients`. | ||
assert_eq!(recipients.0.len(), 2); | ||
|
||
// Ensure the first recipient correctly has a name and address. | ||
assert_eq!( | ||
recipients.get(0).expect("no recipient at index 0"), | ||
&Recipient { | ||
mailbox: Mailbox { | ||
name: Some("Alice Test".into()), | ||
email_address: "[email protected]".into(), | ||
routing_type: None, | ||
mailbox_type: None, | ||
item_id: None, | ||
}, | ||
} | ||
); | ||
|
||
// Ensure the second recipient correctly has a name and address. | ||
assert_eq!( | ||
recipients.get(1).expect("no recipient at index 1"), | ||
&Recipient { | ||
mailbox: Mailbox { | ||
name: Some("Bob Test".into()), | ||
email_address: "[email protected]".into(), | ||
routing_type: None, | ||
mailbox_type: None, | ||
item_id: None, | ||
}, | ||
} | ||
); | ||
|
||
Ok(()) | ||
} | ||
} |
Oops, something went wrong.