diff --git a/src/types/soap.rs b/src/types/soap.rs index 9f9833c..930a757 100644 --- a/src/types/soap.rs +++ b/src/types/soap.rs @@ -15,22 +15,98 @@ use crate::{ mod de; use self::de::DeserializeEnvelope; +/// A SOAP header of an EWS operation or response. Headers are optional and may +/// contain additional information about the operation or response. +/// +/// In some operations that are only supported in later versions of Exchange, +/// the `RequestServerVersion` header is required to indicate the version of the +/// server to which the request is directed. +/// +/// See +#[derive(Clone, Debug)] +pub enum SoapHeaderTypes { + RequestServerVersion(RequestServerVersion), +} + +/// A SOAP header indicating the version of the Exchange server to which a +/// request is directed. +/// +/// See +#[derive(Clone, Debug)] +pub struct RequestServerVersion { + pub version: RequestServerVersionVersion, +} + +/// The version of the Exchange server to which a request is directed. +/// +/// See +#[derive(Clone, Debug)] +pub enum RequestServerVersionVersion { + Exchange2007, + Exchange2007Sp1, + Exchange2010, + Exchange2010Sp1, + Exchange2010Sp2, + Exchange2013, + Exchange2013Sp1, +} + +impl RequestServerVersionVersion { + fn as_str(&self) -> &'static str { + match self { + RequestServerVersionVersion::Exchange2007 => "Exchange2007", + RequestServerVersionVersion::Exchange2007Sp1 => "Exchange2007_SP1", + RequestServerVersionVersion::Exchange2010 => "Exchange2010", + RequestServerVersionVersion::Exchange2010Sp1 => "Exchange2010_SP1", + RequestServerVersionVersion::Exchange2010Sp2 => "Exchange2010_SP2", + RequestServerVersionVersion::Exchange2013 => "Exchange2013", + RequestServerVersionVersion::Exchange2013Sp1 => "Exchange2013_SP1", + } + } +} + +pub trait SoapHeader { + fn serialize_header(&self, writer: &mut Writer>) -> Result<(), Error>; +} + +impl SoapHeader for RequestServerVersion { + fn serialize_header(&self, writer: &mut Writer>) -> Result<(), Error> { + writer.write_event(Event::Start( + BytesStart::new("t:RequestServerVersion") + .with_attributes([("Version", self.version.as_str())]), + ))?; + writer.write_event(Event::End(BytesEnd::new("t:RequestServerVersion")))?; + Ok(()) + } +} + +// Implement `SoapHeader` for `()` to represent an empty or no-op header +impl SoapHeader for () { + fn serialize_header(&self, _writer: &mut Writer>) -> Result<(), Error> { + // No-op for empty header + Ok(()) + } +} + /// A SOAP envelope containing the body of an EWS operation or response. /// /// See #[derive(Clone, Debug)] -pub struct Envelope { +pub struct Envelope { pub body: B, + pub header: Option, } -impl Envelope +impl Envelope where B: Operation, + H: SoapHeader, { /// Serializes the SOAP envelope as a complete XML document. pub fn as_xml_document(&self) -> Result, Error> { const SOAP_ENVELOPE: &str = "soap:Envelope"; const SOAP_BODY: &str = "soap:Body"; + const SOAP_HEADER: &str = "soap:Header"; let mut writer = { let inner: Vec = Default::default(); @@ -46,12 +122,22 @@ where BytesStart::new(SOAP_ENVELOPE) .with_attributes([("xmlns:soap", SOAP_NS_URI), ("xmlns:t", TYPES_NS_URI)]), ))?; + + // Conditionally add Header if present + if let Some(header) = &self.header { + writer.write_event(Event::Start(BytesStart::new(SOAP_HEADER)))?; + header.serialize_header(&mut writer)?; + writer.write_event(Event::End(BytesEnd::new(SOAP_HEADER)))?; + } + + // Start Body writer.write_event(Event::Start(BytesStart::new(SOAP_BODY)))?; // Write the operation itself. self.body .serialize_as_element(&mut writer, ::name())?; + // End Body and Envelope writer.write_event(Event::End(BytesEnd::new(SOAP_BODY)))?; writer.write_event(Event::End(BytesEnd::new(SOAP_ENVELOPE)))?; @@ -59,7 +145,7 @@ where } } -impl Envelope +impl Envelope where B: OperationResponse, { @@ -89,6 +175,7 @@ where Ok(Envelope { body: envelope.body, + header: None, }) } } @@ -422,7 +509,7 @@ mod tests { // test the generic behavior of the interface. let xml = r#"testing content"#; - let actual: Envelope = + let actual: Envelope> = Envelope::from_xml_document(xml.as_bytes()).expect("deserialization should succeed"); assert_eq!( @@ -448,7 +535,7 @@ mod tests { // This XML is drawn from testing data for `evolution-ews`. let xml = r#"a:ErrorSchemaValidationThe request failed schema validation: The 'Id' attribute is invalid - The value 'invalidparentid' is invalid according to its datatype 'http://schemas.microsoft.com/exchange/services/2006/types:DistinguishedFolderIdNameType' - The Enumeration constraint failed.ErrorSchemaValidationThe request failed schema validation.2630The 'Id' attribute is invalid - The value 'invalidparentid' is invalid according to its datatype 'http://schemas.microsoft.com/exchange/services/2006/types:DistinguishedFolderIdNameType' - The Enumeration constraint failed."#; - let err = >::from_xml_document(xml.as_bytes()) + let err = >>::from_xml_document(xml.as_bytes()) .expect_err("should return error when body contains fault"); if let Error::RequestFault(fault) = err { @@ -503,7 +590,7 @@ mod tests { // real-life examples. let xml = r#"a:ErrorServerBusyI made this up because I don't have real testing data. 🙃ErrorServerBusyWho really knows?25"#; - let err = >::from_xml_document(xml.as_bytes()) + let err = >>::from_xml_document(xml.as_bytes()) .expect_err("should return error when body contains fault"); // The testing here isn't as thorough as the invalid schema test due to