From 4ef6083071996b455b349468b17b873c8ddc1e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20de=20B=C3=BArca?= Date: Tue, 16 Apr 2024 11:10:34 -0700 Subject: [PATCH] Improve organization, naming, and documentation --- src/types/get_folder.rs | 6 +-- src/types/operations.rs | 31 ++++++++++-- src/types/soap.rs | 79 +++-------------------------- src/types/soap/de.rs | 80 ++++++++++++++++++++++++++++++ src/types/sync_folder_hierarchy.rs | 6 +-- 5 files changed, 121 insertions(+), 81 deletions(-) create mode 100644 src/types/soap/de.rs diff --git a/src/types/get_folder.rs b/src/types/get_folder.rs index b739b93..300291c 100644 --- a/src/types/get_folder.rs +++ b/src/types/get_folder.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use xml_struct::XmlSerialize; use crate::{ - types::sealed::NamedStructure, BaseFolderId, Folder, FolderShape, Operation, OperationResponse, + types::sealed::EnvelopeBodyContents, BaseFolderId, Folder, FolderShape, Operation, OperationResponse, ResponseClass, MESSAGES_NS_URI, }; @@ -24,7 +24,7 @@ impl Operation for GetFolder { type Response = GetFolderResponse; } -impl NamedStructure for GetFolder { +impl EnvelopeBodyContents for GetFolder { fn name() -> &'static str { "GetFolder" } @@ -41,7 +41,7 @@ pub struct GetFolderResponse { impl OperationResponse for GetFolderResponse {} -impl NamedStructure for GetFolderResponse { +impl EnvelopeBodyContents for GetFolderResponse { fn name() -> &'static str { "GetFolderResponse" } diff --git a/src/types/operations.rs b/src/types/operations.rs index b967f58..857a647 100644 --- a/src/types/operations.rs +++ b/src/types/operations.rs @@ -5,14 +5,39 @@ use serde::Deserialize; use xml_struct::XmlSerialize; -pub trait Operation: XmlSerialize + sealed::NamedStructure { +/// A marker trait for EWS operations. +/// +/// Types implementing this trait may appear in requests to EWS as the operation +/// to be performed. +/// +/// # Usage +/// +/// See [`Envelope`] for details. +/// +/// [`Envelope`]: crate::soap::Envelope +pub trait Operation: XmlSerialize + sealed::EnvelopeBodyContents { + /// The structure returned by EWS in response to requests containing this + /// operation. type Response: OperationResponse; } -pub trait OperationResponse: for<'de> Deserialize<'de> + sealed::NamedStructure {} +/// A marker trait for EWS operation responses. +/// +/// Types implementing this trait may appear in responses from EWS after +/// requesting an operation be performed. +/// +/// # Usage +/// +/// See [`Envelope`] for details. +/// +/// [`Envelope`]: crate::soap::Envelope +pub trait OperationResponse: for<'de> Deserialize<'de> + sealed::EnvelopeBodyContents {} pub(super) mod sealed { - pub trait NamedStructure { + /// A trait for structures which may appear in the body of a SOAP envelope. + pub trait EnvelopeBodyContents { + /// Gets the name of the element enclosing the contents of this + /// structure when represented in XML. fn name() -> &'static str; } } diff --git a/src/types/soap.rs b/src/types/soap.rs index 362a5f5..e30c73d 100644 --- a/src/types/soap.rs +++ b/src/types/soap.rs @@ -2,18 +2,18 @@ * 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 std::marker::PhantomData; - use quick_xml::{ events::{BytesDecl, BytesEnd, BytesStart, Event}, Reader, Writer, }; -use serde::{de::Visitor, Deserialize, Deserializer}; use crate::{ Error, MessageXml, Operation, OperationResponse, ResponseCode, SOAP_NS_URI, TYPES_NS_URI, }; +mod de; +use self::de::DummyEnvelope; + /// A SOAP envelope wrapping an EWS operation. /// /// See @@ -63,71 +63,6 @@ where { /// Populates an [`Envelope`] from raw XML. pub fn from_xml_document(document: &[u8]) -> Result { - struct BodyVisitor(PhantomData); - - impl<'de, T> Visitor<'de> for BodyVisitor - where - T: OperationResponse, - { - type Value = T; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("EWS operation response body") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let name: Option = map.next_key()?; - if let Some(name) = name { - let expected = T::name(); - if name.as_str() != expected { - return Err(serde::de::Error::custom(format_args!( - "unknown field `{}`, expected {}", - name, expected - ))); - } - - let value = map.next_value()?; - - // To satisfy quick-xml's serde impl, we need to consume the - // final `None` key value in order to successfully complete. - if let Some(name) = map.next_key::()? { - return Err(serde::de::Error::custom(format_args!( - "unexpected field `{}`", - name - ))); - } - - return Ok(value); - } - - Err(serde::de::Error::invalid_type( - serde::de::Unexpected::Map, - &self, - )) - } - } - - fn deserialize_body<'de, D, T>(body: D) -> Result - where - D: Deserializer<'de>, - T: OperationResponse, - { - body.deserialize_map(BodyVisitor::(PhantomData)) - } - - #[derive(Deserialize)] - #[serde(rename_all = "PascalCase")] - struct DummyEnvelope - where - T: OperationResponse, - { - #[serde(deserialize_with = "deserialize_body")] - body: T, - } - // The body of an envelope can contain a fault, indicating an error with // a request. We want to parse that and return it as the `Err` portion // of a result. However, Microsoft includes a field in their fault @@ -453,7 +388,7 @@ pub struct FaultDetail { mod tests { use serde::Deserialize; - use crate::{types::sealed::NamedStructure, Error, OperationResponse}; + use crate::{types::sealed::EnvelopeBodyContents, Error, OperationResponse}; use super::Envelope; @@ -469,7 +404,7 @@ mod tests { impl OperationResponse for SomeStruct {} - impl NamedStructure for SomeStruct { + impl EnvelopeBodyContents for SomeStruct { fn name() -> &'static str { "Foo" } @@ -496,7 +431,7 @@ mod tests { impl OperationResponse for Foo {} - impl NamedStructure for Foo { + impl EnvelopeBodyContents for Foo { fn name() -> &'static str { "Foo" } @@ -549,7 +484,7 @@ mod tests { impl OperationResponse for Foo {} - impl NamedStructure for Foo { + impl EnvelopeBodyContents for Foo { fn name() -> &'static str { "Foo" } diff --git a/src/types/soap/de.rs b/src/types/soap/de.rs new file mode 100644 index 0000000..00fe5a0 --- /dev/null +++ b/src/types/soap/de.rs @@ -0,0 +1,80 @@ +/* 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 std::marker::PhantomData; + +use serde::{de::Visitor, Deserialize, Deserializer}; + +use crate::OperationResponse; + +#[derive(Deserialize)] +#[serde(rename_all = "PascalCase")] +pub(super) struct DummyEnvelope +where + T: OperationResponse, +{ + #[serde(deserialize_with = "deserialize_body")] + pub body: T, +} + +fn deserialize_body<'de, D, T>(body: D) -> Result +where + D: Deserializer<'de>, + T: OperationResponse, +{ + body.deserialize_map(BodyVisitor::(PhantomData)) +} + +/// A visitor for custom name-based deserialization of operation responses. +struct BodyVisitor(PhantomData); + +impl<'de, T> Visitor<'de> for BodyVisitor +where + T: OperationResponse, +{ + type Value = T; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("EWS operation response body") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + match map.next_key::()? { + Some(name) => { + // We expect the body of the response to contain a single + // element with the name of the expected operation response. + let expected = T::name(); + if name.as_str() != expected { + return Err(serde::de::Error::custom(format_args!( + "unknown field `{}`, expected {}", + name, expected + ))); + } + + let value = map.next_value()?; + + // To satisfy quick-xml's serde impl, we need to consume the + // final `None` key value in order to successfully complete. + match map.next_key::()? { + Some(name) => { + // The response body contained more than one element, + // which violates our expectations. + Err(serde::de::Error::custom(format_args!( + "unexpected field `{}`", + name + ))) + } + None => Ok(value), + } + } + None => Err(serde::de::Error::invalid_type( + serde::de::Unexpected::Map, + &self, + )), + } + } +} diff --git a/src/types/sync_folder_hierarchy.rs b/src/types/sync_folder_hierarchy.rs index 39ec3b0..d8a9d4d 100644 --- a/src/types/sync_folder_hierarchy.rs +++ b/src/types/sync_folder_hierarchy.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use xml_struct::XmlSerialize; use crate::{ - types::sealed::NamedStructure, BaseFolderId, Folder, FolderId, FolderShape, Operation, + types::sealed::EnvelopeBodyContents, BaseFolderId, Folder, FolderId, FolderShape, Operation, OperationResponse, ResponseClass, MESSAGES_NS_URI, }; @@ -25,7 +25,7 @@ impl Operation for SyncFolderHierarchy { type Response = SyncFolderHierarchyResponse; } -impl NamedStructure for SyncFolderHierarchy { +impl EnvelopeBodyContents for SyncFolderHierarchy { fn name() -> &'static str { "SyncFolderHierarchy" } @@ -42,7 +42,7 @@ pub struct SyncFolderHierarchyResponse { impl OperationResponse for SyncFolderHierarchyResponse {} -impl NamedStructure for SyncFolderHierarchyResponse { +impl EnvelopeBodyContents for SyncFolderHierarchyResponse { fn name() -> &'static str { "SyncFolderHierarchyResponse" }