diff --git a/src/exchange_ews/types.rs b/src/exchange_ews/types.rs index 3c1b682..736668d 100644 --- a/src/exchange_ews/types.rs +++ b/src/exchange_ews/types.rs @@ -94,7 +94,7 @@ impl DistinguishedFolderId { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum ServerVersion { Exchange2007, Exchange2010, diff --git a/src/exchange_ews/xml.rs b/src/exchange_ews/xml.rs index 7c15a4c..fca2d2c 100644 --- a/src/exchange_ews/xml.rs +++ b/src/exchange_ews/xml.rs @@ -5,7 +5,7 @@ */ use crate::exchange_ews::soap::write_escaped; -use crate::exchange_ews::types::{DistinguishedFolderId, FolderId, ItemId}; +use crate::exchange_ews::types::{DistinguishedFolderId, FolderId, ItemId, ServerVersion}; #[derive(Debug, Clone, Copy)] pub enum FolderRef<'a> { @@ -124,7 +124,7 @@ pub enum ItemShape { Contact, } -pub fn get_item_body(shape: ItemShape, ids: &[ItemId]) -> String { +pub fn get_item_body(shape: ItemShape, ids: &[ItemId], version: ServerVersion) -> String { let mut out = String::with_capacity(256 + ids.len() * 128); out.push_str(""); match shape { @@ -134,11 +134,13 @@ pub fn get_item_body(shape: ItemShape, ids: &[ItemId]) -> String { out.push_str("Best"); out.push_str(""); out.push_str(""); - out.push_str(""); + out.push_str(""); out.push_str(""); out.push_str(""); out.push_str(""); - out.push_str(""); + if version >= ServerVersion::Exchange2013 { + out.push_str(""); + } out.push_str(""); out.push_str(""); } @@ -302,15 +304,34 @@ mod tests { #[test] fn get_item_message_shape_requests_mime() { let ids = vec![ItemId::new("I1", "CK1"), ItemId::new("I2", "")]; - let body = get_item_body(ItemShape::Message, &ids); + let body = get_item_body(ItemShape::Message, &ids, ServerVersion::Exchange2013Sp1); assert!(body.contains("true")); assert!(body.contains("")); assert!(body.contains("")); } + #[test] + fn item_flag_requested_only_on_exchange_2013_and_later() { + let ids = [ItemId::new("I1", "")]; + let modern = get_item_body(ItemShape::Message, &ids, ServerVersion::Exchange2013Sp1); + assert!(modern.contains("item:Flag")); + let legacy = get_item_body(ItemShape::Message, &ids, ServerVersion::Exchange2010Sp2); + assert!( + !legacy.contains("item:Flag"), + "item:Flag is an Exchange 2013 schema addition; must be omitted on 2010" + ); + assert!(legacy.contains("item:DateTimeReceived")); + assert!(legacy.contains("message:IsReadReceiptRequested")); + assert!( + modern.contains("\"message:IsRead\"") && !modern.contains("\"item:IsRead\""), + "IsRead is a MessageType property; its FieldURI is message:IsRead" + ); + } + #[test] fn get_item_calendar_shape_lists_calendar_fields() { - let body = get_item_body(ItemShape::CalendarItem, &[ItemId::new("X", "")]); + let body = + get_item_body(ItemShape::CalendarItem, &[ItemId::new("X", "")], ServerVersion::Exchange2010Sp2); assert!(body.contains("calendar:Recurrence")); assert!(body.contains("calendar:ModifiedOccurrences")); assert!(body.contains("calendar:DeletedOccurrences")); @@ -320,7 +341,8 @@ mod tests { #[test] fn get_item_contact_shape_requests_all_properties() { - let body = get_item_body(ItemShape::Contact, &[ItemId::new("X", "")]); + let body = + get_item_body(ItemShape::Contact, &[ItemId::new("X", "")], ServerVersion::Exchange2013Sp1); assert!(body.contains("AllProperties")); } diff --git a/src/sync/import_exchange_ews/items.rs b/src/sync/import_exchange_ews/items.rs index 626fd3a..adb177e 100644 --- a/src/sync/import_exchange_ews/items.rs +++ b/src/sync/import_exchange_ews/items.rs @@ -286,11 +286,12 @@ pub fn get_items( ) -> Result { let batch = ctx.batch_size.max(1); let workers = ctx.connections.clamp(1, 8); + let version = ctx.client.server_version(); let mut failed_items: u64 = 0; if workers <= 1 || ids.len() <= batch { let mut all = Vec::new(); for chunk in ids.chunks(batch) { - let body = get_item_body(shape, chunk); + let body = get_item_body(shape, chunk, version); match ctx.client.call(ctx.url, "GetItem", &body) { Ok(resp) => match parse_response_messages(&resp.body, b"GetItemResponseMessage") { Ok(mut msgs) => all.append(&mut msgs), @@ -326,7 +327,7 @@ pub fn get_items( ); let pool: crate::sync::import_jmap::pool::Pool, BatchResult> = crate::sync::import_jmap::pool::Pool::new(workers, move |chunk: Vec| { - let body = get_item_body(shape, &chunk); + let body = get_item_body(shape, &chunk, version); let n = chunk.len(); let result = match client.call(&url, "GetItem", &body) { Ok(resp) => parse_response_messages(&resp.body, b"GetItemResponseMessage"), diff --git a/tests/mock_exchange_ews.rs b/tests/mock_exchange_ews.rs index 65d451e..d8fc104 100644 --- a/tests/mock_exchange_ews.rs +++ b/tests/mock_exchange_ews.rs @@ -458,7 +458,7 @@ fn get_item_batches_chunk_the_id_list() { let chunks: Vec<&[ItemId]> = ids.chunks(3).collect(); assert_eq!(chunks.len(), 3); for chunk in &chunks { - let body = get_item_body(ItemShape::Message, chunk); + let body = get_item_body(ItemShape::Message, chunk, ServerVersion::Exchange2013Sp1); assert!(body.contains("")); for id in *chunk { assert!(body.contains(&format!("Id=\"{}\"", id.id)));