From d50f10a2b0444288c5323042aa4c2c50980d8f1e Mon Sep 17 00:00:00 2001 From: Rudi Floren Date: Mon, 16 Dec 2024 20:04:31 +0100 Subject: [PATCH 1/4] rename some generics to make them more clear --- src/client.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5b9480a..ac52fc3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -116,14 +116,14 @@ impl EppClient { xml::deserialize::(&response) } - pub async fn transact<'c, 'e, Cmd, Ext>( + pub async fn transact<'c, 'e, Cmd, CmdExt>( &mut self, - data: impl Into>, + data: impl Into>, id: &str, - ) -> Result, Error> + ) -> Result, Error> where - Cmd: Transaction + Command + 'c, - Ext: Extension + 'e, + Cmd: Transaction + Command + 'c, + CmdExt: Extension + 'e, { let data = data.into(); let document = CommandWrapper::new(data.command, data.extension, id); @@ -133,7 +133,7 @@ impl EppClient { let response = self.connection.transact(&xml)?.await?; debug!("{}: response: {}", self.connection.registry, &response); - let rsp = match xml::deserialize::>(&response) { + let rsp = match xml::deserialize::>(&response) { Ok(rsp) => rsp, Err(e) => { error!(%response, "failed to deserialize response for transaction: {e}"); @@ -179,9 +179,9 @@ impl EppClient { } #[derive(Debug)] -pub struct RequestData<'c, 'e, C, E> { - pub(crate) command: &'c C, - pub(crate) extension: Option<&'e E>, +pub struct RequestData<'c, 'e, Command, CommandExt> { + pub(crate) command: &'c Command, + pub(crate) extension: Option<&'e CommandExt>, } impl<'c, C: Command> From<&'c C> for RequestData<'c, 'static, C, NoExtension> { @@ -193,8 +193,10 @@ impl<'c, C: Command> From<&'c C> for RequestData<'c, 'static, C, NoExtension> { } } -impl<'c, 'e, C: Command, E: Extension> From<(&'c C, &'e E)> for RequestData<'c, 'e, C, E> { - fn from((command, extension): (&'c C, &'e E)) -> Self { +impl<'c, 'e, C: Command, CommandExt: Extension> From<(&'c C, &'e CommandExt)> + for RequestData<'c, 'e, C, CommandExt> +{ + fn from((command, extension): (&'c C, &'e CommandExt)) -> Self { Self { command, extension: Some(extension), @@ -203,14 +205,14 @@ impl<'c, 'e, C: Command, E: Extension> From<(&'c C, &'e E)> for RequestData<'c, } // Manual impl because this does not depend on whether `C` and `E` are `Clone` -impl Clone for RequestData<'_, '_, C, E> { +impl Clone for RequestData<'_, '_, Command, CommandExt> { fn clone(&self) -> Self { *self } } // Manual impl because this does not depend on whether `C` and `E` are `Copy` -impl Copy for RequestData<'_, '_, C, E> {} +impl Copy for RequestData<'_, '_, Command, CommandExt> {} #[cfg(feature = "__rustls")] pub use rustls_connector::RustlsConnector; From 785d3f6771ec110334a2ba2a9863ca8c47d14ecd Mon Sep 17 00:00:00 2001 From: Rudi Floren Date: Mon, 16 Dec 2024 20:07:11 +0100 Subject: [PATCH 2/4] introduce connection level extensions Some EPP extensions such as change poll didn't map to the present command oriented extensions. The element will be present in the poll response without any elements in the element of the request. There is no allowed value for the element for the poll command (if you only support RGP, NameStore, and ChangePoll). This now stores enabled general extensions in the EppClient. This allows you to retrieve command level extensions responses using Response::command_extension and connection level extensions using Response::connection_extension --- src/client.rs | 29 +++++--- src/extensions/change_poll.rs | 72 +++++++----------- src/extensions/namestore.rs | 5 +- src/extensions/rgp/request.rs | 23 +++--- src/response.rs | 133 +++++++++++++++++++++++++++++++--- src/tests/mod.rs | 15 ++-- tests/basic.rs | 76 +++++++++++++++++-- 7 files changed, 262 insertions(+), 91 deletions(-) diff --git a/src/client.rs b/src/client.rs index ac52fc3..dfc8967 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::time::Duration; #[cfg(feature = "__rustls")] @@ -10,7 +11,7 @@ use crate::connection::EppConnection; use crate::error::Error; use crate::hello::{Greeting, Hello}; use crate::request::{Command, CommandWrapper, Extension, Transaction}; -use crate::response::{Response, ResponseStatus}; +use crate::response::{ConnectionExtensionResponse, Response, ResponseStatus}; use crate::xml; /// An `EppClient` provides an interface to sending EPP requests to a registry @@ -64,12 +65,13 @@ use crate::xml; /// Domain: eppdev.com, Available: 1 /// Domain: eppdev.net, Available: 1 /// ``` -pub struct EppClient { +pub struct EppClient { connection: EppConnection, + phantom_type: PhantomData, } #[cfg(feature = "__rustls")] -impl EppClient { +impl EppClient { /// Connect to the specified `addr` and `hostname` over TLS /// /// The `registry` is used as a name in internal logging; `host` provides the host name @@ -97,11 +99,12 @@ impl EppClient { } } -impl EppClient { +impl EppClient { /// Create an `EppClient` from an already established connection pub async fn new(connector: C, registry: String, timeout: Duration) -> Result { Ok(Self { connection: EppConnection::new(connector, registry, timeout).await?, + phantom_type: PhantomData, }) } @@ -120,7 +123,7 @@ impl EppClient { &mut self, data: impl Into>, id: &str, - ) -> Result, Error> + ) -> Result, Error> where Cmd: Transaction + Command + 'c, CmdExt: Extension + 'e, @@ -133,13 +136,15 @@ impl EppClient { let response = self.connection.transact(&xml)?.await?; debug!("{}: response: {}", self.connection.registry, &response); - let rsp = match xml::deserialize::>(&response) { - Ok(rsp) => rsp, - Err(e) => { - error!(%response, "failed to deserialize response for transaction: {e}"); - return Err(e); - } - }; + let rsp = + match xml::deserialize::>(&response) + { + Ok(rsp) => rsp, + Err(e) => { + error!(%response, "failed to deserialize response for transaction: {e}"); + return Err(e); + } + }; if rsp.result.code.is_success() { return Ok(rsp); diff --git a/src/extensions/change_poll.rs b/src/extensions/change_poll.rs index a266a2e..efa0077 100644 --- a/src/extensions/change_poll.rs +++ b/src/extensions/change_poll.rs @@ -7,18 +7,11 @@ use std::borrow::Cow; use instant_xml::{Error, FromXml, ToXml}; -use crate::{ - poll::Poll, - request::{Extension, Transaction}, -}; +use crate::response::ConnectionExtensionResponse; pub const XMLNS: &str = "urn:ietf:params:xml:ns:changePoll-1.0"; -impl Transaction> for Poll {} - -impl Extension for ChangePoll<'_> { - type Response = ChangePoll<'static>; -} +impl ConnectionExtensionResponse for ChangePoll<'_> {} /// Type for EPP XML `` extension /// @@ -204,13 +197,14 @@ pub enum State { #[cfg(test)] mod tests { use super::*; + use crate::common::NoExtension; use crate::poll::Poll; use crate::response::ResultCode; use crate::tests::{response_from_file_with_ext, CLTRID, SVTRID}; #[test] fn urs_lock_before() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/change_poll/urs_lock_before.xml", ); @@ -223,29 +217,18 @@ mod tests { "Command completed successfully; ack to dequeue" ); - assert_eq!(object.extension().unwrap().state.unwrap(), State::Before); - assert_eq!( - object.extension().unwrap().operation.kind().unwrap(), - OperationKind::Update - ); - assert_eq!(object.extension().unwrap().date, "2013-10-22T14:25:57.0Z"); - assert_eq!(object.extension().unwrap().server_tr_id, "12345-XYZ"); - assert_eq!(object.extension().unwrap().who, "URS Admin"); + let ext = &object.connection_extension().unwrap(); + + assert_eq!(ext.state.unwrap(), State::Before); + assert_eq!(ext.operation.kind().unwrap(), OperationKind::Update); + assert_eq!(ext.date, "2013-10-22T14:25:57.0Z"); + assert_eq!(ext.server_tr_id, "12345-XYZ"); + assert_eq!(ext.who, "URS Admin"); assert_eq!( - object - .extension() - .unwrap() - .case_id - .as_ref() - .unwrap() - .kind() - .unwrap(), + ext.case_id.as_ref().unwrap().kind().unwrap(), CaseIdentifierKind::Urs ); - assert_eq!( - object.extension().unwrap().reason.as_ref().unwrap().inner, - "URS Lock" - ); + assert_eq!(ext.reason.as_ref().unwrap().inner, "URS Lock"); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID); assert_eq!(object.tr_ids.server_tr_id, SVTRID); @@ -253,7 +236,7 @@ mod tests { #[test] fn urs_lock_after() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/change_poll/urs_lock_after.xml", ); @@ -265,7 +248,10 @@ mod tests { object.result.message, "Command completed successfully; ack to dequeue" ); - assert_eq!(object.extension().unwrap().state.unwrap(), State::After); + + let ext = &object.connection_extension().unwrap(); + + assert_eq!(ext.state.unwrap(), State::After); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID); assert_eq!(object.tr_ids.server_tr_id, SVTRID); @@ -273,7 +259,7 @@ mod tests { #[test] fn custom_sync_after() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/change_poll/custom_sync_after.xml", ); @@ -286,15 +272,11 @@ mod tests { "Command completed successfully; ack to dequeue" ); - assert_eq!( - object.extension().unwrap().operation.kind().unwrap(), - OperationKind::Custom("sync") - ); - assert_eq!(object.extension().unwrap().who, "CSR"); - assert_eq!( - object.extension().unwrap().reason.as_ref().unwrap().inner, - "Customer sync request" - ); + let ext = &object.connection_extension().unwrap(); + + assert_eq!(ext.operation.kind().unwrap(), OperationKind::Custom("sync")); + assert_eq!(ext.who, "CSR"); + assert_eq!(ext.reason.as_ref().unwrap().inner, "Customer sync request"); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID); assert_eq!(object.tr_ids.server_tr_id, SVTRID); @@ -302,7 +284,7 @@ mod tests { #[test] fn delete_before() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/change_poll/delete_before.xml", ); @@ -321,7 +303,7 @@ mod tests { #[test] fn autopurge_before() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/change_poll/autopurge_before.xml", ); @@ -340,7 +322,7 @@ mod tests { #[test] fn update_after() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/change_poll/update_after.xml", ); diff --git a/src/extensions/namestore.rs b/src/extensions/namestore.rs index ac7825d..021c0bb 100644 --- a/src/extensions/namestore.rs +++ b/src/extensions/namestore.rs @@ -75,6 +75,7 @@ pub struct NameStore<'a> { #[cfg(test)] mod tests { use super::NameStore; + use crate::common::NoExtension; use crate::domain::check::DomainCheck; use crate::tests::{assert_serialized, response_from_file_with_ext}; @@ -94,10 +95,10 @@ mod tests { #[test] fn response() { - let object = response_from_file_with_ext::( + let object = response_from_file_with_ext::( "response/extensions/namestore.xml", ); - let ext = object.extension().unwrap(); + let ext = &object.command_extension().unwrap(); assert_eq!(ext.subproduct, "com"); } } diff --git a/src/extensions/rgp/request.rs b/src/extensions/rgp/request.rs index c87bb09..d93900d 100644 --- a/src/extensions/rgp/request.rs +++ b/src/extensions/rgp/request.rs @@ -69,6 +69,7 @@ pub enum RgpRequestResponse { #[cfg(test)] mod tests { use super::{RgpRestoreRequest, Update}; + use crate::common::NoExtension; use crate::domain::info::DomainInfo; use crate::domain::update::{DomainChangeInfo, DomainUpdate}; use crate::extensions::rgp::request::RgpRequestResponse; @@ -99,15 +100,16 @@ mod tests { #[test] fn request_response() { - let object = response_from_file_with_ext::>( - "response/extensions/rgp_restore.xml", - ); - let ext = object.extension.unwrap(); + let object = + response_from_file_with_ext::, NoExtension>( + "response/extensions/rgp_restore.xml", + ); + let ext = object.command_extension().unwrap(); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.message, SUCCESS_MSG); - let data = match ext.data { + let data = match ext { RgpRequestResponse::Update(data) => data, _ => panic!("Unexpected response type"), }; @@ -118,12 +120,13 @@ mod tests { #[test] fn domain_info_request_response() { - let object = response_from_file_with_ext::>( - "response/extensions/domain_info_rgp.xml", - ); - let ext = object.extension.unwrap(); + let object = + response_from_file_with_ext::, NoExtension>( + "response/extensions/domain_info_rgp.xml", + ); + let ext = object.command_extension().unwrap(); - let data = match ext.data { + let data = match ext { RgpRequestResponse::Info(data) => data, _ => panic!("Unexpected response type"), }; diff --git a/src/response.rs b/src/response.rs index e1e5d8b..d7b19be 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,9 +3,9 @@ use std::fmt::Debug; use chrono::{DateTime, Utc}; -use instant_xml::{FromXml, Kind}; +use instant_xml::{Deserializer, FromXml, FromXmlOwned, Id, Kind}; -use crate::common::EPP_XMLNS; +use crate::common::{NoExtension, EPP_XMLNS}; /// Type corresponding to the `` tag an EPP response XML #[derive(Debug, Eq, FromXml, PartialEq)] @@ -238,7 +238,7 @@ pub struct Message { /// Type corresponding to the `` tag in an EPP response XML /// containing an `` tag #[xml(rename = "response", ns(EPP_XMLNS))] -pub struct Response { +pub struct Response { /// Data under the `` tag pub result: EppResult, /// Data under the `` tag @@ -247,7 +247,7 @@ pub struct Response { /// Data under the `` tag pub res_data: Option>, /// Data under the `` tag - pub extension: Option>, + pub extension: Option>, /// Data under the `` tag pub tr_ids: ResponseTRID, } @@ -276,7 +276,7 @@ pub struct ResponseStatus { pub tr_ids: ResponseTRID, } -impl Response { +impl Response { /// Returns the data under the corresponding `` from the EPP XML pub fn res_data(&self) -> Option<&T> { match &self.res_data { @@ -285,9 +285,16 @@ impl Response { } } - pub fn extension(&self) -> Option<&E> { + pub fn command_extension(&self) -> Option<&CmdExt> { match &self.extension { - Some(extension) => Some(&extension.data), + Some(extension) => extension.data.command.as_ref(), + None => None, + } + } + + pub fn connection_extension(&self) -> Option<&ConnExt> { + match &self.extension { + Some(extension) => extension.data.connection.as_ref(), None => None, } } @@ -303,8 +310,116 @@ impl Response { #[derive(Debug, Eq, FromXml, PartialEq)] #[xml(rename = "extension", ns(EPP_XMLNS))] -pub struct Extension { - pub data: E, +pub struct Extension { + pub data: CombinedExtensions, +} + +/// Types implementing this can be used as ConnectionExtensions. +/// +/// Their type will be assumed to be in the response's extension element. +pub trait ConnectionExtensionResponse: FromXmlOwned + Debug {} + +impl ConnectionExtensionResponse for NoExtension {} + +/// Combines connection extensions and command extensions +/// +/// Some extensions are sent by the server no matter if an extension was defined for the command. +#[derive(Debug, Eq, PartialEq)] +pub struct CombinedExtensions { + pub command: Option, + pub connection: Option, +} + +// // This manual impl is needed because instant-xml is not able to create the correct derived impl. +// // for this transparent type. It fails to set the correct bounds for struct __CombinedExtensionsAccumulator. +impl<'xml, CmdExt: FromXml<'xml>, ConnExt: FromXml<'xml>> FromXml<'xml> + for CombinedExtensions +{ + #[inline] + fn matches(id: Id<'_>, _: Option>) -> bool { + >::matches(id, None) + || >::matches(id, None) + } + fn deserialize<'cx>( + into: &mut Self::Accumulator, + _: &'static str, + deserializer: &mut Deserializer<'cx, 'xml>, + ) -> Result<(), instant_xml::Error> { + use instant_xml::Kind; + let current = deserializer.parent(); + if >::matches(current, None) { + match ::KIND { + Kind::Element => { + >::deserialize( + &mut into.command, + "CombinedExtensions::command", + deserializer, + )?; + } + Kind::Scalar => { + >::deserialize( + &mut into.command, + "CombinedExtensions::command", + deserializer, + )?; + deserializer.ignore()?; + } + } + } else if >::matches(current, None) { + match ::KIND { + Kind::Element => { + >::deserialize( + &mut into.connection, + "CombinedExtensions::connection", + deserializer, + )?; + } + Kind::Scalar => { + >::deserialize( + &mut into.connection, + "CombinedExtensions::connection", + deserializer, + )?; + deserializer.ignore()?; + } + } + } + Ok(()) + } + type Accumulator = __CombinedExtensionsAccumulator<'xml, CmdExt, ConnExt>; + const KIND: instant_xml::Kind = instant_xml::Kind::Element; +} + +pub struct __CombinedExtensionsAccumulator<'xml, CommandExt: FromXml<'xml>, ConnExt: FromXml<'xml>> +{ + command: as FromXml<'xml>>::Accumulator, + connection: as FromXml<'xml>>::Accumulator, +} + +impl<'xml, CommandExt: FromXml<'xml>, ConnExt: FromXml<'xml>> + instant_xml::Accumulate> + for __CombinedExtensionsAccumulator<'xml, CommandExt, ConnExt> +{ + fn try_done( + self, + _: &'static str, + ) -> Result, instant_xml::Error> { + Ok(CombinedExtensions { + command: self.command.try_done("CombinedExtensions::command")?, + connection: self.connection.try_done("CombinedExtensions::connection")?, + }) + } +} + +impl<'xml, CommandExt: FromXml<'xml>, ConnExt: instant_xml::FromXml<'xml>> Default + for __CombinedExtensionsAccumulator<'xml, CommandExt, ConnExt> +{ + fn default() -> Self { + Self { + command: Default::default(), + connection: Default::default(), + } + } } #[cfg(test)] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 79e1806..b7b9caa 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -9,7 +9,7 @@ use crate::{ client::RequestData, common::NoExtension, request::{Command, CommandWrapper, Extension, Transaction}, - response::Response, + response::{ConnectionExtensionResponse, Response}, xml, }; @@ -53,23 +53,24 @@ pub(crate) fn assert_serialized<'c, 'e, Cmd, Ext>( pub(crate) fn response_from_file<'c, Cmd>( path: &str, -) -> Response::Response> +) -> Response::Response, NoExtension> where Cmd: Transaction + Command + 'c, { - response_from_file_with_ext::(path) + response_from_file_with_ext::(path) } -pub(crate) fn response_from_file_with_ext( +pub(crate) fn response_from_file_with_ext( path: &str, -) -> Response +) -> Response where Cmd: Transaction + Command, - Ext: Extension, + CmdExt: Extension, + ConnExt: ConnectionExtensionResponse, { let xml = get_xml(path).unwrap(); dbg!(&xml); - let rsp = xml::deserialize::>(&xml).unwrap(); + let rsp = xml::deserialize::>(&xml).unwrap(); assert!(rsp.result.code.is_success()); rsp } diff --git a/tests/basic.rs b/tests/basic.rs index 1dd64cd..689d39d 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -4,11 +4,14 @@ use std::str; use std::time::Duration; use async_trait::async_trait; +use instant_epp::extensions::change_poll::ChangePoll; +use instant_epp::poll::{Poll, PollData}; use regex::Regex; use tokio::time::timeout; use tokio_test::io::Builder; use instant_epp::client::{Connector, EppClient}; +use instant_epp::common::NoExtension; use instant_epp::domain::{DomainCheck, DomainContact, DomainCreate, Period, PeriodLength}; use instant_epp::login::Login; use instant_epp::response::ResultCode; @@ -100,9 +103,10 @@ async fn client() { } } - let mut client = EppClient::new(FakeConnector, "test".into(), Duration::from_secs(5)) - .await - .unwrap(); + let mut client = + EppClient::<_, NoExtension>::new(FakeConnector, "test".into(), Duration::from_secs(5)) + .await + .unwrap(); assert_eq!(client.xml_greeting(), xml("response/greeting.xml")); let rsp = client @@ -176,9 +180,10 @@ async fn dropped() { } } - let mut client = EppClient::new(FakeConnector, "test".into(), Duration::from_secs(5)) - .await - .unwrap(); + let mut client = + EppClient::<_, NoExtension>::new(FakeConnector, "test".into(), Duration::from_secs(5)) + .await + .unwrap(); assert_eq!(client.xml_greeting(), xml("response/greeting.xml")); let rsp = client @@ -242,3 +247,62 @@ async fn dropped() { let rsp = client.transact(&create, CLTRID).await.unwrap(); assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully); } + +#[tokio::test] +async fn poll_with_extensions() { + let _guard = log_to_stdout(); + + struct FakeConnector; + + #[async_trait] + impl Connector for FakeConnector { + type Connection = tokio_test::io::Mock; + + async fn connect(&self, _: Duration) -> Result { + Ok(build_stream(&[ + "response/greeting.xml", + "request/login.xml", + "response/login.xml", + "request/poll/poll.xml", + "response/extensions/change_poll/urs_lock_after.xml", + ]) + .build()) + } + } + + let mut client = + EppClient::<_, ChangePoll>::new(FakeConnector, "test".into(), Duration::from_secs(5)) + .await + .unwrap(); + + assert_eq!(client.xml_greeting(), xml("response/greeting.xml")); + let rsp = client + .transact( + &Login::new( + "username", + "password", + Some("new-password"), + Some(&["http://schema.ispapi.net/epp/xml/keyvalue-1.0"]), + ), + CLTRID, + ) + .await + .unwrap(); + + assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully); + + let rsp = client.transact(&Poll, CLTRID).await.unwrap(); + assert_eq!( + rsp.result.code, + ResultCode::CommandCompletedSuccessfullyAckToDequeue + ); + + let data = rsp.res_data().unwrap(); + let data = match data { + PollData::DomainInfo(info_data) => info_data, + _ => panic!("expected domain:infData"), + }; + assert_eq!(data.name, "domain.example"); + let ext = rsp.connection_extension().unwrap(); + assert_eq!(ext.case_id.as_ref().unwrap().id, "urs123"); +} From 83a51f2b6995c9124d675078cd9f7486daf5331b Mon Sep 17 00:00:00 2001 From: Rudi Floren Date: Mon, 16 Dec 2024 20:21:31 +0100 Subject: [PATCH 3/4] implement ConnectionExtensionResponse for Option --- src/response.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/response.rs b/src/response.rs index d7b19be..7424295 100644 --- a/src/response.rs +++ b/src/response.rs @@ -321,6 +321,8 @@ pub trait ConnectionExtensionResponse: FromXmlOwned + Debug {} impl ConnectionExtensionResponse for NoExtension {} +impl ConnectionExtensionResponse for Option {} + /// Combines connection extensions and command extensions /// /// Some extensions are sent by the server no matter if an extension was defined for the command. From 30ecf944dd422d2a9b34223213c0c0f805e2a6f5 Mon Sep 17 00:00:00 2001 From: Rudi Floren Date: Mon, 16 Dec 2024 20:24:26 +0100 Subject: [PATCH 4/4] make ChangePoll fully owned There is not need for Cow as this is only deserialized and xml::deserialize uses FromXmlOwned. --- src/extensions/change_poll.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/extensions/change_poll.rs b/src/extensions/change_poll.rs index efa0077..649c452 100644 --- a/src/extensions/change_poll.rs +++ b/src/extensions/change_poll.rs @@ -3,33 +3,31 @@ //! As described in RFC8590: [Change Poll Extension for the Extensible Provisioning Protocol (EPP)](https://www.rfc-editor.org/rfc/rfc8590.html). //! Tests cases in `tests/resources/response/extensions/changepoll`` are taken from the RFC. -use std::borrow::Cow; - use instant_xml::{Error, FromXml, ToXml}; use crate::response::ConnectionExtensionResponse; pub const XMLNS: &str = "urn:ietf:params:xml:ns:changePoll-1.0"; -impl ConnectionExtensionResponse for ChangePoll<'_> {} +impl ConnectionExtensionResponse for ChangePoll {} /// Type for EPP XML `` extension /// /// Attributes associated with the change #[derive(Debug, FromXml, ToXml)] #[xml(rename = "changeData", ns(XMLNS))] -pub struct ChangePoll<'a> { +pub struct ChangePoll { /// Transform operation executed on the object - pub operation: Operation<'a>, + pub operation: Operation, /// Date and time when the operation was executed - pub date: Cow<'a, str>, + pub date: String, /// Server transaction identifier of the operation #[xml(rename = "svTRID")] - pub server_tr_id: Cow<'a, str>, + pub server_tr_id: String, /// Who executed the operation - pub who: Cow<'a, str>, + pub who: String, /// Case identifier associated with the operation - pub case_id: Option>, + pub case_id: Option, /// Reason for executing the operation pub reason: Option, /// Enumerated state of the object in the poll message @@ -39,7 +37,7 @@ pub struct ChangePoll<'a> { state: Option, } -impl ChangePoll<'_> { +impl ChangePoll { /// State reflects if the `infData` describes the object before or after the operation pub fn state(&self) -> State { self.state.unwrap_or_default() @@ -51,16 +49,16 @@ impl ChangePoll<'_> { // to make this struct more ergonomic. #[derive(Debug, FromXml, ToXml)] #[xml(rename = "operation", ns(XMLNS))] -pub struct Operation<'a> { +pub struct Operation { /// Custom value for`OperationKind::Custom` #[xml(attribute, rename = "op")] - op: Option>, + op: Option, /// The operation #[xml(direct)] kind: OperationType, } -impl Operation<'_> { +impl Operation { pub fn kind(&self) -> Result { Ok(match self.kind { OperationType::Create => OperationKind::Create, @@ -121,16 +119,16 @@ enum OperationType { // to make this struct more ergonomic. #[derive(Debug, FromXml, ToXml)] #[xml(rename = "caseId", ns(XMLNS))] -pub struct CaseIdentifier<'a> { +pub struct CaseIdentifier { #[xml(attribute, rename = "type")] id_type: CaseIdentifierType, #[xml(attribute)] - name: Option>, + name: Option, #[xml(direct)] - pub id: Cow<'a, str>, + pub id: String, } -impl CaseIdentifier<'_> { +impl CaseIdentifier { pub fn kind(&self) -> Result { Ok(match self.id_type { CaseIdentifierType::Udrp => CaseIdentifierKind::Udrp,