Skip to content

Commit

Permalink
Improve organization, naming, and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
leftmostcat committed Apr 16, 2024
1 parent 4635133 commit 4ef6083
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 81 deletions.
6 changes: 3 additions & 3 deletions src/types/get_folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -24,7 +24,7 @@ impl Operation for GetFolder {
type Response = GetFolderResponse;
}

impl NamedStructure for GetFolder {
impl EnvelopeBodyContents for GetFolder {
fn name() -> &'static str {
"GetFolder"
}
Expand All @@ -41,7 +41,7 @@ pub struct GetFolderResponse {

impl OperationResponse for GetFolderResponse {}

impl NamedStructure for GetFolderResponse {
impl EnvelopeBodyContents for GetFolderResponse {
fn name() -> &'static str {
"GetFolderResponse"
}
Expand Down
31 changes: 28 additions & 3 deletions src/types/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
79 changes: 7 additions & 72 deletions src/types/soap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383494>
Expand Down Expand Up @@ -63,71 +63,6 @@ where
{
/// Populates an [`Envelope`] from raw XML.
pub fn from_xml_document(document: &[u8]) -> Result<Self, Error> {
struct BodyVisitor<T>(PhantomData<T>);

impl<'de, T> Visitor<'de> for BodyVisitor<T>
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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let name: Option<String> = 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::<String>()? {
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<T, D::Error>
where
D: Deserializer<'de>,
T: OperationResponse,
{
body.deserialize_map(BodyVisitor::<T>(PhantomData))
}

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
struct DummyEnvelope<T>
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
Expand Down Expand Up @@ -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;

Expand All @@ -469,7 +404,7 @@ mod tests {

impl OperationResponse for SomeStruct {}

impl NamedStructure for SomeStruct {
impl EnvelopeBodyContents for SomeStruct {
fn name() -> &'static str {
"Foo"
}
Expand All @@ -496,7 +431,7 @@ mod tests {

impl OperationResponse for Foo {}

impl NamedStructure for Foo {
impl EnvelopeBodyContents for Foo {
fn name() -> &'static str {
"Foo"
}
Expand Down Expand Up @@ -549,7 +484,7 @@ mod tests {

impl OperationResponse for Foo {}

impl NamedStructure for Foo {
impl EnvelopeBodyContents for Foo {
fn name() -> &'static str {
"Foo"
}
Expand Down
80 changes: 80 additions & 0 deletions src/types/soap/de.rs
Original file line number Diff line number Diff line change
@@ -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<T>
where
T: OperationResponse,
{
#[serde(deserialize_with = "deserialize_body")]
pub body: T,
}

fn deserialize_body<'de, D, T>(body: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: OperationResponse,
{
body.deserialize_map(BodyVisitor::<T>(PhantomData))
}

/// A visitor for custom name-based deserialization of operation responses.
struct BodyVisitor<T>(PhantomData<T>);

impl<'de, T> Visitor<'de> for BodyVisitor<T>
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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
match map.next_key::<String>()? {
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::<String>()? {
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,
)),
}
}
}
6 changes: 3 additions & 3 deletions src/types/sync_folder_hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -25,7 +25,7 @@ impl Operation for SyncFolderHierarchy {
type Response = SyncFolderHierarchyResponse;
}

impl NamedStructure for SyncFolderHierarchy {
impl EnvelopeBodyContents for SyncFolderHierarchy {
fn name() -> &'static str {
"SyncFolderHierarchy"
}
Expand All @@ -42,7 +42,7 @@ pub struct SyncFolderHierarchyResponse {

impl OperationResponse for SyncFolderHierarchyResponse {}

impl NamedStructure for SyncFolderHierarchyResponse {
impl EnvelopeBodyContents for SyncFolderHierarchyResponse {
fn name() -> &'static str {
"SyncFolderHierarchyResponse"
}
Expand Down

0 comments on commit 4ef6083

Please sign in to comment.