Skip to content

Commit

Permalink
Add types for CreateItem (#11)
Browse files Browse the repository at this point in the history
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
babolivier authored Jun 20, 2024
1 parent 1d6b729 commit 6d85d50
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub use common::*;
pub use operations::*;
pub mod soap;

pub mod create_item;
pub mod get_folder;
pub mod get_item;
pub mod sync_folder_hierarchy;
Expand Down
202 changes: 178 additions & 24 deletions src/types/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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),
}
Expand All @@ -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),
Expand Down Expand Up @@ -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.
///
Expand All @@ -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>,
Expand All @@ -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>,
Expand All @@ -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>,
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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]
Expand All @@ -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,
Expand Down Expand Up @@ -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(())
}
}
Loading

0 comments on commit 6d85d50

Please sign in to comment.