diff --git a/Cargo.lock b/Cargo.lock index b515ebf..c0c8df8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,9 @@ dependencies = [ "log", "quick-xml", "serde", + "serde_path_to_error", "thiserror", + "time", "xml_struct", ] @@ -21,15 +23,15 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -46,38 +48,47 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "serde" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -86,24 +97,50 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "time" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +dependencies = [ + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +dependencies = [ + "time-core", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -113,7 +150,7 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "xml_struct" version = "0.1.0" -source = "git+https://github.com/thunderbird/xml-struct-rs.git?rev=ace285b5848750064d4f5ad30ddc0c0bff53d8a9#ace285b5848750064d4f5ad30ddc0c0bff53d8a9" +source = "git+https://github.com/thunderbird/xml-struct-rs.git?rev=0374b50878596a08ef9c9ac2dfcc7325b9c39b83#0374b50878596a08ef9c9ac2dfcc7325b9c39b83" dependencies = [ "quick-xml", "thiserror", @@ -123,7 +160,7 @@ dependencies = [ [[package]] name = "xml_struct_derive" version = "0.1.0" -source = "git+https://github.com/thunderbird/xml-struct-rs.git?rev=ace285b5848750064d4f5ad30ddc0c0bff53d8a9#ace285b5848750064d4f5ad30ddc0c0bff53d8a9" +source = "git+https://github.com/thunderbird/xml-struct-rs.git?rev=0374b50878596a08ef9c9ac2dfcc7325b9c39b83#0374b50878596a08ef9c9ac2dfcc7325b9c39b83" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index de851b6..3d233ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,7 @@ edition = "2021" log = { version = "0.4.21", features = ["std"] } quick-xml = { version = "0.31.0", features = ["serde", "serialize"] } serde = { version = "1.0.196", features = ["derive"] } +serde_path_to_error = "0.1.11" thiserror = "1.0.57" -xml_struct = { git = "https://github.com/thunderbird/xml-struct-rs.git", rev = "ace285b5848750064d4f5ad30ddc0c0bff53d8a9", version = "0.1.0" } +time = { version = "0.3.23", features = ["parsing", "serde"] } +xml_struct = { git = "https://github.com/thunderbird/xml-struct-rs.git", rev = "0374b50878596a08ef9c9ac2dfcc7325b9c39b83", version = "0.1.0" } diff --git a/src/lib.rs b/src/lib.rs index 2794ca5..5c13ce2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ pub enum Error { Serialize(#[from] xml_struct::Error), #[error("failed to deserialize structure from XML")] - Deserialize(#[from] quick_xml::DeError), + Deserialize(#[from] serde_path_to_error::Error), #[error("invalid XML document")] InvalidXml(#[from] quick_xml::Error), diff --git a/src/types.rs b/src/types.rs index fb74a52..ca1a73c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -10,4 +10,6 @@ pub use operations::*; pub mod soap; pub mod get_folder; +pub mod get_item; pub mod sync_folder_hierarchy; +pub mod sync_folder_items; diff --git a/src/types/common.rs b/src/types/common.rs index 4702ed4..c42d1e3 100644 --- a/src/types/common.rs +++ b/src/types/common.rs @@ -13,7 +13,7 @@ pub(crate) const TYPES_NS_URI: &str = "http://schemas.microsoft.com/exchange/ser /// The folder properties which should be included in the response. /// /// See . -#[derive(Debug, XmlSerialize)] +#[derive(Debug, Default, XmlSerialize)] pub struct FolderShape { #[xml_struct(ns_prefix = "t")] pub base_shape: BaseShape, @@ -22,17 +22,159 @@ pub struct FolderShape { /// The item properties which should be included in the response. /// /// See . -#[derive(Debug, XmlSerialize)] +#[derive(Debug, Default, XmlSerialize)] pub struct ItemShape { + /// The base set of properties to include, which may be extended by other + /// fields. + /// + /// See #[xml_struct(ns_prefix = "t")] pub base_shape: BaseShape, + + /// Whether the MIME content of an item should be included. + /// + /// See + #[xml_struct(ns_prefix = "t")] + pub include_mime_content: Option, + + /// A list of properties which should be included in addition to those + /// implied by other fields. + /// + /// See + #[xml_struct(ns_prefix = "t")] + pub additional_properties: Option>, +} + +/// An identifier for a property on an Exchange entity. +#[derive(Debug, XmlSerialize)] +#[xml_struct(variant_ns_prefix = "t")] +pub enum PathToElement { + /// An identifier for an extended MAPI property. + /// + /// The full set of constraints on which properties may or must be set + /// together are not expressed in the structure of this variant. Please see + /// Microsoft's documentation for further details. + /// + /// See + // TODO: We can represent in a friendlier way with an enum, probably. A + // property is fully specified by a type and either: + // - A property set ID plus property name/ID, or + // - A property tag. + // https://github.com/thunderbird/ews-rs/issues/9 + ExtendedFieldURI { + /// A well-known identifier for a property set. + #[xml_struct(attribute)] + distinguished_property_set_id: Option, + + /// A GUID representing a property set. + // TODO: This could use a strong type for representing a GUID. + #[xml_struct(attribute)] + property_set_id: Option, + + /// Specifies a property by integer tag. + // TODO: This should use an integer type, but it seems a hex + // representation is preferred, and we should restrict the possible + // values per the docs. + #[xml_struct(attribute)] + property_tag: Option, + + /// The name of a property within a specified property set. + #[xml_struct(attribute)] + property_name: Option, + + /// The dispatch ID of a property within a specified property set. + #[xml_struct(attribute)] + property_id: Option, + + /// The value type of the desired property. + #[xml_struct(attribute)] + property_type: PropertyType, + }, + + /// An identifier for a property given by a well-known string. + /// + /// See + #[allow(non_snake_case)] + FieldURI { + /// The well-known string. + // TODO: Adjust xml_struct to support field renaming to avoid non-snake + // case identifiers. + // https://github.com/thunderbird/xml-struct-rs/issues/6 + // TODO: We could use an enum for this field. It's just large and not + // worth typing out by hand. + #[xml_struct(attribute)] + field_URI: String, + }, + + /// An identifier for a specific element of a dictionary-based property. + /// + /// See + #[allow(non_snake_case)] + IndexedFieldURI { + /// The well-known string identifier of the property. + #[xml_struct(attribute)] + field_URI: String, + + /// The member within the dictionary to access. + #[xml_struct(attribute)] + field_index: String, + }, +} + +/// A well-known MAPI property set identifier. +/// +/// See +#[derive(Clone, Copy, Debug, XmlSerialize)] +#[xml_struct(text)] +pub enum DistinguishedPropertySet { + Address, + Appointment, + CalendarAssistant, + Common, + InternetHeaders, + Meeting, + PublicStrings, + Sharing, + Task, + UnifiedMessaging, +} + +/// The type of the value of a MAPI property. +/// +/// See +#[derive(Clone, Copy, Debug, XmlSerialize)] +#[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, } /// The base set of properties to be returned in response to our request. /// Additional properties may be specified by the parent element. /// /// See . -#[derive(Debug, Default, XmlSerialize)] +#[derive(Clone, Copy, Debug, Default, XmlSerialize)] #[xml_struct(text)] pub enum BaseShape { /// Only the IDs of any items or folders returned. @@ -50,7 +192,7 @@ pub enum BaseShape { } /// The success/failure status of an operation. -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum ResponseClass { Success, Warning, @@ -73,7 +215,7 @@ where } } -/// An identifier for a remote folder. +/// An identifier for an Exchange folder. #[derive(Debug, XmlSerialize)] #[xml_struct(variant_ns_prefix = "t")] pub enum BaseFolderId { @@ -113,8 +255,41 @@ pub struct FolderId { pub change_key: Option, } +/// An identifier for an Exchange item. +/// +/// See +// N.B.: Commented-out variants are not yet implemented. +#[derive(Debug, XmlSerialize)] +#[xml_struct(variant_ns_prefix = "t")] +pub enum BaseItemId { + /// An identifier for a standard Exchange item. + ItemId { + #[xml_struct(attribute)] + id: String, + + #[xml_struct(attribute)] + change_key: Option, + }, + // OccurrenceItemId { .. } + // RecurringMasterItemId { .. } +} + +/// The unique identifier of an item. +/// +/// See +#[derive(Debug, Deserialize, XmlSerialize)] +pub struct ItemId { + #[xml_struct(attribute)] + #[serde(rename = "@Id")] + pub id: String, + + #[serde(rename = "@ChangeKey")] + #[xml_struct(attribute)] + pub change_key: Option, +} + /// The representation of a folder in an EWS operation. -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, Deserialize)] pub enum Folder { /// A calendar folder in a mailbox. /// @@ -183,6 +358,423 @@ pub enum Folder { }, } +/// An item which may appear as the result of a request to read or modify an +/// Exchange item. +/// +/// See +#[derive(Debug, Deserialize)] +pub enum RealItem { + Message(Message), +} + +/// An item which may appear in an item-based attachment. +/// +/// See [`Attachment::ItemAttachment`] for details. +// N.B.: Commented-out variants are not yet implemented. +#[derive(Debug, Deserialize)] +pub enum AttachmentItem { + // Item(Item), + Message(Message), + // CalendarItem(CalendarItem), + // Contact(Contact), + // Task(Task), + // MeetingMessage(MeetingMessage), + // MeetingRequest(MeetingRequest), + // MeetingResponse(MeetingResponse), + // MeetingCancellation(MeetingCancellation), +} + +/// A date and time with second precision. +// `time` provides an `Option` deserializer, but it does not +// work with map fields which may be omitted, as in our case. +#[derive(Debug, Deserialize)] +pub struct DateTime(#[serde(with = "time::serde::iso8601")] pub time::OffsetDateTime); + +/// An email message. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct Message { + /// The MIME content of the item. + pub mime_content: Option, + + /// The item's Exchange identifier. + pub item_id: ItemId, + + /// The identifier for the containing folder. + /// + /// See + pub parent_folder_id: Option, + + /// The Exchange class value of the item. + /// + /// See + pub item_class: Option, + + /// The subject of the item. + /// + /// See + pub subject: Option, + + pub sensitivity: Option, + pub body: Option, + pub attachments: Option, + pub date_time_received: Option, + pub size: Option, + + /// A list of categories describing an item. + /// + /// See + pub categories: Option>, + + pub importance: Option, + pub in_reply_to: Option, + pub is_submitted: Option, + pub is_draft: Option, + pub is_from_me: Option, + pub is_resend: Option, + pub is_unmodified: Option, + pub internet_message_headers: Option, + pub date_time_sent: Option, + pub date_time_created: Option, + pub reminder_due_by: Option, + pub reminder_is_set: Option, + pub reminder_minutes_before_start: Option, + pub display_cc: Option, + pub display_to: Option, + pub has_attachments: Option, + pub culture: Option, + pub sender: Option, + pub to_recipients: Option, + pub cc_recipients: Option, + pub bcc_recipients: Option, + pub is_read_receipt_requested: Option, + pub is_delivery_receipt_requested: Option, + pub conversation_index: Option, + pub conversation_topic: Option, + pub from: Option, + pub internet_message_id: Option, + pub is_read: Option, + pub is_response_requested: Option, + pub reply_to: Option, + pub received_by: Option, + pub received_representing: Option, + pub last_modified_name: Option, + pub last_modified_time: Option, + pub is_associated: Option, + pub conversation_id: Option, +} + +/// A list of attachments. +/// +/// See +#[derive(Debug, Deserialize)] +pub struct Attachments { + #[serde(rename = "$value")] + pub inner: Vec, +} + +/// A single mailbox. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct SingleRecipient { + pub mailbox: Mailbox, +} + +/// A list of mailboxes. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ArrayOfRecipients { + pub mailbox: Vec, +} + +/// A list of Internet Message Format headers. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct InternetMessageHeaders { + pub internet_message_header: Vec, +} + +/// A reference to a user or address which can send or receive mail. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct Mailbox { + /// The name of this mailbox's user. + pub name: Option, + + /// The email address for this mailbox. + pub email_address: String, + + /// The protocol used in routing to this mailbox. + /// + /// See + pub routing_type: Option, + + /// The type of sender/recipient represented by this mailbox. + /// + /// See + pub mailbox_type: Option, + + /// An identifier for a contact or list of contacts corresponding to this + /// mailbox. + pub item_id: Option, +} + +/// A protocol used in routing mail. +/// +/// See +#[derive(Clone, Copy, Debug, Default, Deserialize)] +pub enum RoutingType { + #[default] + SMTP, + EX, +} + +/// The type of sender or recipient a mailbox represents. +/// +/// See +#[derive(Clone, Copy, Debug, Deserialize)] +pub enum MailboxType { + Mailbox, + PublicDL, + PrivateDL, + Contact, + PublicFolder, + Unknown, + OneOff, + GroupMailbox, +} + +/// The priority level of an item. +/// +/// See +#[derive(Clone, Copy, Debug, Deserialize)] +pub enum Importance { + Low, + Normal, + High, +} + +/// A string value. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct StringElement { + /// The string content. + pub string: String, +} + +/// The sensitivity of the contents of an item. +/// +/// See +#[derive(Clone, Copy, Debug, Deserialize)] +pub enum Sensitivity { + Normal, + Personal, + Private, + Confidential, +} + +/// The body of an item. +/// +/// See +#[derive(Debug, Deserialize)] +pub struct Body { + /// The content type of the body. + #[serde(rename = "@BodyType")] + pub body_type: BodyType, + + /// Whether the body has been truncated. + #[serde(rename = "@IsTruncated")] + pub is_truncated: Option, + + /// The content of the body. + // TODO: It's not immediately obvious why this tag may be empty, but it has + // been encountered in real world responses. Needs a closer look. + #[serde(rename = "$text")] + pub content: Option, +} + +/// The content type of an item's body. +/// +/// See +#[derive(Clone, Copy, Debug, Deserialize)] +pub enum BodyType { + HTML, + Text, +} + +/// An attachment to an Exchange item. +/// +/// See +#[derive(Debug, Deserialize)] +pub enum Attachment { + /// An attachment containing an Exchange item. + /// + /// See + #[serde(rename_all = "PascalCase")] + ItemAttachment { + /// An identifier for the attachment. + attachment_id: AttachmentId, + + /// The name of the attachment. + /// + /// See + name: String, + + /// The MIME type of the attachment's content. + /// + /// See + content_type: String, + + /// An arbitrary identifier for the attachment. + /// + /// This field is not set by Exchange and is intended for use by + /// external applications. + /// + /// See + content_id: Option, + + /// A URI representing the location of the attachment's content. + /// + /// See + content_location: Option, + + /// The size of the attachment's content in bytes. + /// + /// See + size: Option, + + /// The most recent modification time for the attachment. + /// + /// See + last_modified_time: Option, + + /// Whether the attachment appears inline in the item body. + /// + /// See + is_inline: Option, + // XXX: With this field in place, parsing will fail if there is no + // `AttachmentItem` in the response. + // See https://github.com/tafia/quick-xml/issues/683 + // /// The attached item. + // #[serde(rename = "$value")] + // content: Option, + }, + + /// An attachment containing a file. + /// + /// See + #[serde(rename_all = "PascalCase")] + FileAttachment { + /// An identifier for the attachment. + attachment_id: AttachmentId, + + /// The name of the attachment. + /// + /// See + name: String, + + /// The MIME type of the attachment's content. + /// + /// See + content_type: String, + + /// An arbitrary identifier for the attachment. + /// + /// This field is not set by Exchange and is intended for use by + /// external applications. + /// + /// See + content_id: Option, + + /// A URI representing the location of the attachment's content. + /// + /// See + content_location: Option, + + /// The size of the attachment's content in bytes. + /// + /// See + size: Option, + + /// The most recent modification time for the attachment. + /// + /// See + last_modified_time: Option, + + /// Whether the attachment appears inline in the item body. + /// + /// See + is_inline: Option, + + /// Whether the attachment represents a contact photo. + /// + /// See + is_contact_photo: Option, + + /// The base64-encoded content of the attachment. + /// + /// See + content: Option, + }, +} + +/// An identifier for an attachment. +/// +/// See +#[derive(Debug, Deserialize)] +pub struct AttachmentId { + /// A unique identifier for the attachment. + #[serde(rename = "@Id")] + pub id: String, + + /// The unique identifier of the item to which it is attached. + #[serde(rename = "@RootItemId")] + pub root_item_id: Option, + + /// The change key of the item to which it is attached. + #[serde(rename = "@RootItemChangeKey")] + pub root_item_change_key: Option, +} + +/// The content of an item, represented according to MIME (Multipurpose Internet +/// Mail Extensions). +/// +/// See +#[derive(Debug, Deserialize)] +pub struct MimeContent { + /// The character set of the MIME content if it contains [RFC 2045]-encoded + /// text. + /// + /// [RFC 2045]: https://datatracker.ietf.org/doc/html/rfc2045 + #[serde(rename = "@CharacterSet")] + pub character_set: Option, + + /// The item content. + #[serde(rename = "$text")] + pub content: String, +} + +/// The headers of an Exchange item's MIME content. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct InternetMessageHeader { + /// The name of the header. + #[serde(rename = "@HeaderName")] + pub header_name: String, + + /// The value of the header. + #[serde(rename = "$text")] + pub value: String, +} + /// Structured data for diagnosing or responding to an EWS error. /// /// Because the possible contents of this field are not documented, any XML diff --git a/src/types/get_folder.rs b/src/types/get_folder.rs index 82d3390..574b1e0 100644 --- a/src/types/get_folder.rs +++ b/src/types/get_folder.rs @@ -66,7 +66,8 @@ pub struct ResponseMessages { #[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct GetFolderResponseMessage { - /// The success value of the corresponding request. + /// The status of the corresponding request, i.e. whether it succeeded or + /// resulted in an error. #[serde(rename = "@ResponseClass")] pub response_class: ResponseClass, diff --git a/src/types/get_item.rs b/src/types/get_item.rs new file mode 100644 index 0000000..73092e0 --- /dev/null +++ b/src/types/get_item.rs @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 xml_struct::XmlSerialize; + +use crate::{ + types::sealed::EnvelopeBodyContents, BaseItemId, ItemShape, Operation, OperationResponse, + RealItem, ResponseClass, ResponseCode, MESSAGES_NS_URI, +}; + +/// A request for the properties of one or more Exchange items, e.g. messages, +/// calendar events, or contacts. +/// +/// See +#[derive(Debug, XmlSerialize)] +#[xml_struct(default_ns = MESSAGES_NS_URI)] +pub struct GetItem { + /// A description of the information to be included in the response for each + /// item. + /// + /// See + pub item_shape: ItemShape, + + /// The Exchange identifiers of the items which should be fetched. + /// + /// See + pub item_ids: Vec, +} + +impl Operation for GetItem { + type Response = GetItemResponse; +} + +impl EnvelopeBodyContents for GetItem { + fn name() -> &'static str { + "GetItem" + } +} + +/// A response to a [`GetItem`] request. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct GetItemResponse { + pub response_messages: ResponseMessages, +} + +impl OperationResponse for GetItemResponse {} + +impl EnvelopeBodyContents for GetItemResponse { + fn name() -> &'static str { + "GetItemResponse" + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ResponseMessages { + pub get_item_response_message: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct GetItemResponseMessage { + /// The status of the corresponding request, i.e. whether it succeeded or + /// resulted in an error. + #[serde(rename = "@ResponseClass")] + pub response_class: ResponseClass, + + pub response_code: Option, + + pub message_text: Option, + + pub items: Items, +} + +#[derive(Debug, Deserialize)] +pub struct Items { + #[serde(rename = "$value")] + pub inner: Vec, +} diff --git a/src/types/soap.rs b/src/types/soap.rs index b4dda98..2006f41 100644 --- a/src/types/soap.rs +++ b/src/types/soap.rs @@ -77,7 +77,13 @@ where return Err(Error::RequestFault(Box::new(fault))); } - let envelope: DeserializeEnvelope = quick_xml::de::from_reader(document)?; + let de = &mut quick_xml::de::Deserializer::from_reader(document); + + // `serde_path_to_error` ensures that we get sufficient information to + // debug errors in deserialization. serde's default errors only provide + // the immediate error with no context; this gives us a description of + // the context within the structure. + let envelope: DeserializeEnvelope = serde_path_to_error::deserialize(de)?; Ok(Envelope { body: envelope.body, diff --git a/src/types/sync_folder_hierarchy.rs b/src/types/sync_folder_hierarchy.rs index f986f24..8f9cef0 100644 --- a/src/types/sync_folder_hierarchy.rs +++ b/src/types/sync_folder_hierarchy.rs @@ -75,7 +75,8 @@ pub struct ResponseMessages { #[derive(Debug, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct SyncFolderHierarchyResponseMessage { - /// The success value of the corresponding request. + /// The status of the corresponding request, i.e. whether it succeeded or + /// resulted in an error. #[serde(rename = "@ResponseClass")] pub response_class: ResponseClass, diff --git a/src/types/sync_folder_items.rs b/src/types/sync_folder_items.rs new file mode 100644 index 0000000..ad0789d --- /dev/null +++ b/src/types/sync_folder_items.rs @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 xml_struct::XmlSerialize; + +use crate::{ + types::sealed::EnvelopeBodyContents, BaseFolderId, BaseItemId, ItemId, ItemShape, Operation, + OperationResponse, RealItem, ResponseClass, MESSAGES_NS_URI, +}; + +/// A request for a list of items which have been created, updated, or deleted +/// server-side. +/// +/// See +#[derive(Debug, XmlSerialize)] +#[xml_struct(default_ns = MESSAGES_NS_URI)] +pub struct SyncFolderItems { + /// A description of the information to be included in the response for each + /// changed item. + pub item_shape: ItemShape, + + /// The ID of the folder to sync. + pub sync_folder_id: BaseFolderId, + + /// The synchronization state after which to list changes. + /// + /// If `None`, the response will include `Create` changes for each item + /// which is contained in the requested folder. + /// + /// See + pub sync_state: Option, + + /// A list of item IDs for which changes should not be returned. + /// + /// See + pub ignore: Option, + + /// The maximum number of changes to return in the response. + /// + /// This value must be in the range `1..=512`. + /// + /// See + pub max_changes_returned: u16, + + pub sync_scope: Option, +} + +impl Operation for SyncFolderItems { + type Response = SyncFolderItemsResponse; +} + +impl EnvelopeBodyContents for SyncFolderItems { + fn name() -> &'static str { + "SyncFolderItems" + } +} + +/// A response to a [`SyncFolderItems`] request. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct SyncFolderItemsResponse { + pub response_messages: ResponseMessages, +} + +impl OperationResponse for SyncFolderItemsResponse {} + +impl EnvelopeBodyContents for SyncFolderItemsResponse { + fn name() -> &'static str { + "SyncFolderItemsResponse" + } +} + +/// A collection of responses for individual entities within a request. +/// +/// See +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ResponseMessages { + pub sync_folder_items_response_message: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct SyncFolderItemsResponseMessage { + /// The status of the corresponding request, i.e. whether it succeeded or + /// resulted in an error. + #[serde(rename = "@ResponseClass")] + pub response_class: ResponseClass, + + /// An identifier for the synchronization state following application of the + /// changes included in this response. + pub sync_state: String, + + /// Whether all relevant item changes have been synchronized following this + /// response. + pub includes_last_item_in_range: bool, + + /// The collection of changes between the prior synchronization state and + /// the one represented by this response. + pub changes: Changes, +} + +/// An ordered collection of identifiers for Exchange items. +#[derive(Debug, XmlSerialize)] +pub struct ArrayOfBaseItemIds { + item_id: Vec, +} + +#[derive(Clone, Copy, Debug, XmlSerialize)] +pub enum SyncScope { + NormalItems, + NormalAndAssociatedItems, +} + +#[derive(Debug, Deserialize)] +pub struct Changes { + #[serde(default, rename = "$value")] + pub inner: Vec, +} + +/// A server-side change to an item. +/// +/// See +#[derive(Debug, Deserialize)] +pub enum Change { + /// A creation of an item. + /// + /// See + Create { + /// The state of the item upon creation. + #[serde(rename = "$value")] + item: RealItem, + }, + + /// An update to an item. + /// + /// See + Update { + /// The updated state of the item. + #[serde(rename = "$value")] + item: RealItem, + }, + + /// A deletion of an item. + /// + /// See + #[serde(rename_all = "PascalCase")] + Delete { + /// The EWS ID for the deleted item. + item_id: ItemId, + }, + + #[serde(rename_all = "PascalCase")] + ReadFlagChange { item_id: ItemId, is_read: bool }, +}