Skip to content

Commit 6d85d50

Browse files
authored
Add types for CreateItem (#11)
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.
1 parent 1d6b729 commit 6d85d50

File tree

4 files changed

+321
-32
lines changed

4 files changed

+321
-32
lines changed

src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub use common::*;
99
pub use operations::*;
1010
pub mod soap;
1111

12+
pub mod create_item;
1213
pub mod get_folder;
1314
pub mod get_item;
1415
pub mod sync_folder_hierarchy;

src/types/common.rs

Lines changed: 178 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
use serde::Deserialize;
5+
use std::ops::{Deref, DerefMut};
6+
7+
use serde::{Deserialize, Deserializer};
68
use time::format_description::well_known::Iso8601;
79
use xml_struct::XmlSerialize;
810

@@ -278,7 +280,7 @@ pub enum BaseItemId {
278280
/// The unique identifier of an item.
279281
///
280282
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/itemid>
281-
#[derive(Debug, Deserialize, XmlSerialize)]
283+
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq)]
282284
pub struct ItemId {
283285
#[xml_struct(attribute)]
284286
#[serde(rename = "@Id")]
@@ -359,11 +361,18 @@ pub enum Folder {
359361
},
360362
}
361363

364+
/// An array of items.
365+
#[derive(Debug, Deserialize)]
366+
pub struct Items {
367+
#[serde(rename = "$value", default)]
368+
pub inner: Vec<RealItem>,
369+
}
370+
362371
/// An item which may appear as the result of a request to read or modify an
363372
/// Exchange item.
364373
///
365374
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/items>
366-
#[derive(Debug, Deserialize, XmlSerialize)]
375+
#[derive(Debug, Deserialize)]
367376
pub enum RealItem {
368377
Message(Message),
369378
}
@@ -372,7 +381,8 @@ pub enum RealItem {
372381
///
373382
/// See [`Attachment::ItemAttachment`] for details.
374383
// N.B.: Commented-out variants are not yet implemented.
375-
#[derive(Debug, Deserialize, XmlSerialize)]
384+
#[non_exhaustive]
385+
#[derive(Debug, Deserialize)]
376386
pub enum AttachmentItem {
377387
// Item(Item),
378388
Message(Message),
@@ -413,14 +423,13 @@ impl XmlSerialize for DateTime {
413423
/// An email message.
414424
///
415425
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/message-ex15websvcsotherref>
416-
#[derive(Debug, Deserialize, XmlSerialize)]
426+
#[derive(Debug, Deserialize)]
417427
#[serde(rename_all = "PascalCase")]
418428
pub struct Message {
419429
/// The MIME content of the item.
420430
pub mime_content: Option<MimeContent>,
421-
422431
/// The item's Exchange identifier.
423-
pub item_id: Option<ItemId>,
432+
pub item_id: ItemId,
424433

425434
/// The identifier for the containing folder.
426435
///
@@ -436,7 +445,6 @@ pub struct Message {
436445
///
437446
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/subject>
438447
pub subject: Option<String>,
439-
440448
pub sensitivity: Option<Sensitivity>,
441449
pub body: Option<Body>,
442450
pub attachments: Option<Attachments>,
@@ -447,7 +455,6 @@ pub struct Message {
447455
///
448456
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/categories-ex15websvcsotherref>
449457
pub categories: Option<Vec<StringElement>>,
450-
451458
pub importance: Option<Importance>,
452459
pub in_reply_to: Option<String>,
453460
pub is_submitted: Option<bool>,
@@ -465,21 +472,21 @@ pub struct Message {
465472
pub display_to: Option<String>,
466473
pub has_attachments: Option<bool>,
467474
pub culture: Option<String>,
468-
pub sender: Option<SingleRecipient>,
475+
pub sender: Option<Recipient>,
469476
pub to_recipients: Option<ArrayOfRecipients>,
470477
pub cc_recipients: Option<ArrayOfRecipients>,
471478
pub bcc_recipients: Option<ArrayOfRecipients>,
472479
pub is_read_receipt_requested: Option<bool>,
473480
pub is_delivery_receipt_requested: Option<bool>,
474481
pub conversation_index: Option<String>,
475482
pub conversation_topic: Option<String>,
476-
pub from: Option<SingleRecipient>,
483+
pub from: Option<Recipient>,
477484
pub internet_message_id: Option<String>,
478485
pub is_read: Option<bool>,
479486
pub is_response_requested: Option<bool>,
480-
pub reply_to: Option<SingleRecipient>,
481-
pub received_by: Option<SingleRecipient>,
482-
pub received_representing: Option<SingleRecipient>,
487+
pub reply_to: Option<Recipient>,
488+
pub received_by: Option<Recipient>,
489+
pub received_representing: Option<Recipient>,
483490
pub last_modified_name: Option<String>,
484491
pub last_modified_time: Option<DateTime>,
485492
pub is_associated: Option<bool>,
@@ -496,18 +503,61 @@ pub struct Attachments {
496503
pub inner: Vec<Attachment>,
497504
}
498505

506+
/// A newtype around a vector of `Recipient`s, that is deserialized using
507+
/// `deserialize_recipients`.
508+
#[derive(Debug, Default, Deserialize, XmlSerialize)]
509+
pub struct ArrayOfRecipients(
510+
#[serde(deserialize_with = "deserialize_recipients")] pub Vec<Recipient>,
511+
);
512+
513+
impl Deref for ArrayOfRecipients {
514+
type Target = Vec<Recipient>;
515+
516+
fn deref(&self) -> &Self::Target {
517+
&self.0
518+
}
519+
}
520+
521+
impl DerefMut for ArrayOfRecipients {
522+
fn deref_mut(&mut self) -> &mut Self::Target {
523+
&mut self.0
524+
}
525+
}
526+
499527
/// A single mailbox.
500-
#[derive(Debug, Deserialize, XmlSerialize)]
528+
#[derive(Debug, Deserialize, XmlSerialize, PartialEq)]
501529
#[serde(rename_all = "PascalCase")]
502-
pub struct SingleRecipient {
530+
pub struct Recipient {
531+
#[xml_struct(ns_prefix = "t")]
503532
pub mailbox: Mailbox,
504533
}
505534

506-
/// A list of mailboxes.
507-
#[derive(Debug, Deserialize, XmlSerialize)]
508-
#[serde(rename_all = "PascalCase")]
509-
pub struct ArrayOfRecipients {
510-
pub mailbox: Vec<Mailbox>,
535+
/// Deserializes a list of recipients.
536+
///
537+
/// `quick-xml`'s `serde` implementation requires the presence of an
538+
/// intermediate type when dealing with lists, and this is not compatible with
539+
/// our model for serialization.
540+
///
541+
/// We could directly deserialize into a `Vec<Mailbox>`, which would also
542+
/// simplify this function a bit, but this would mean using different models
543+
/// to represent single vs. multiple recipient(s).
544+
fn deserialize_recipients<'de, D>(deserializer: D) -> Result<Vec<Recipient>, D::Error>
545+
where
546+
D: Deserializer<'de>,
547+
{
548+
#[derive(Debug, Deserialize)]
549+
#[serde(rename_all = "PascalCase")]
550+
struct MailboxSequence {
551+
mailbox: Vec<Mailbox>,
552+
}
553+
554+
let seq = MailboxSequence::deserialize(deserializer)?;
555+
556+
Ok(seq
557+
.mailbox
558+
.into_iter()
559+
.map(|mailbox| Recipient { mailbox })
560+
.collect())
511561
}
512562

513563
/// A list of Internet Message Format headers.
@@ -520,13 +570,15 @@ pub struct InternetMessageHeaders {
520570
/// A reference to a user or address which can send or receive mail.
521571
///
522572
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailbox>
523-
#[derive(Debug, Deserialize, XmlSerialize)]
573+
#[derive(Clone, Debug, Deserialize, XmlSerialize, PartialEq)]
524574
#[serde(rename_all = "PascalCase")]
525575
pub struct Mailbox {
526576
/// The name of this mailbox's user.
577+
#[xml_struct(ns_prefix = "t")]
527578
pub name: Option<String>,
528579

529580
/// The email address for this mailbox.
581+
#[xml_struct(ns_prefix = "t")]
530582
pub email_address: String,
531583

532584
/// The protocol used in routing to this mailbox.
@@ -547,7 +599,7 @@ pub struct Mailbox {
547599
/// A protocol used in routing mail.
548600
///
549601
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/routingtype-emailaddress>
550-
#[derive(Clone, Copy, Debug, Default, Deserialize, XmlSerialize)]
602+
#[derive(Clone, Copy, Debug, Default, Deserialize, XmlSerialize, PartialEq)]
551603
#[xml_struct(text)]
552604
pub enum RoutingType {
553605
#[default]
@@ -558,7 +610,7 @@ pub enum RoutingType {
558610
/// The type of sender or recipient a mailbox represents.
559611
///
560612
/// See <https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailboxtype>
561-
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize)]
613+
#[derive(Clone, Copy, Debug, Deserialize, XmlSerialize, PartialEq)]
562614
#[xml_struct(text)]
563615
pub enum MailboxType {
564616
Mailbox,
@@ -829,3 +881,105 @@ pub struct MessageXml {
829881
/// if the server is throttling operations.
830882
pub back_off_milliseconds: Option<usize>,
831883
}
884+
885+
#[cfg(test)]
886+
mod tests {
887+
use quick_xml::Writer;
888+
889+
use super::*;
890+
use crate::Error;
891+
892+
/// Tests that an [`ArrayOfRecipients`] correctly serializes into XML. It
893+
/// should serialize as multiple `<t:Mailbox>` elements, one per [`Recipient`].
894+
#[test]
895+
fn serialize_array_of_recipients() -> Result<(), Error> {
896+
// Define the recipients to serialize.
897+
let alice = Recipient {
898+
mailbox: Mailbox {
899+
name: Some("Alice Test".into()),
900+
email_address: "alice@test.com".into(),
901+
routing_type: None,
902+
mailbox_type: None,
903+
item_id: None,
904+
},
905+
};
906+
907+
let bob = Recipient {
908+
mailbox: Mailbox {
909+
name: Some("Bob Test".into()),
910+
email_address: "bob@test.com".into(),
911+
routing_type: None,
912+
mailbox_type: None,
913+
item_id: None,
914+
},
915+
};
916+
917+
let recipients = ArrayOfRecipients(vec![alice, bob]);
918+
919+
// Serialize into XML.
920+
let mut writer = {
921+
let inner: Vec<u8> = Default::default();
922+
Writer::new(inner)
923+
};
924+
recipients.serialize_as_element(&mut writer, "Recipients")?;
925+
926+
// Read the contents of the `Writer`'s buffer.
927+
let buf = writer.into_inner();
928+
let actual = std::str::from_utf8(buf.as_slice())
929+
.map_err(|e| Error::UnexpectedResponse(e.to_string().into_bytes()))?;
930+
931+
// Ensure the structure of the XML document is correct.
932+
let expected = "<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></Recipients>";
933+
assert_eq!(expected, actual);
934+
935+
Ok(())
936+
}
937+
938+
/// Tests that deserializing a sequence of `<t:Mailbox>` XML elements
939+
/// results in an [`ArrayOfRecipients`] with one [`Recipient`] per
940+
/// `<t:Mailbox>` element.
941+
#[test]
942+
fn deserialize_array_of_recipients() -> Result<(), Error> {
943+
// The raw XML to deserialize.
944+
let xml = "<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></Recipients>";
945+
946+
// Deserialize the raw XML, with `serde_path_to_error` to help
947+
// troubleshoot any issue.
948+
let mut de = quick_xml::de::Deserializer::from_reader(xml.as_bytes());
949+
let recipients: ArrayOfRecipients = serde_path_to_error::deserialize(&mut de)?;
950+
951+
// Ensure we have the right number of recipients in the resulting
952+
// `ArrayOfRecipients`.
953+
assert_eq!(recipients.0.len(), 2);
954+
955+
// Ensure the first recipient correctly has a name and address.
956+
assert_eq!(
957+
recipients.get(0).expect("no recipient at index 0"),
958+
&Recipient {
959+
mailbox: Mailbox {
960+
name: Some("Alice Test".into()),
961+
email_address: "alice@test.com".into(),
962+
routing_type: None,
963+
mailbox_type: None,
964+
item_id: None,
965+
},
966+
}
967+
);
968+
969+
// Ensure the second recipient correctly has a name and address.
970+
assert_eq!(
971+
recipients.get(1).expect("no recipient at index 1"),
972+
&Recipient {
973+
mailbox: Mailbox {
974+
name: Some("Bob Test".into()),
975+
email_address: "bob@test.com".into(),
976+
routing_type: None,
977+
mailbox_type: None,
978+
item_id: None,
979+
},
980+
}
981+
);
982+
983+
Ok(())
984+
}
985+
}

0 commit comments

Comments
 (0)