Switch from quick-xml to instant-xml

This commit is contained in:
Dirkjan Ochtman 2022-11-21 11:34:25 -08:00
parent 75f1dfe03e
commit 3829ed77fa
76 changed files with 1866 additions and 1850 deletions

View File

@ -14,7 +14,7 @@ default = ["tokio-rustls"]
async-trait = "0.1.52" async-trait = "0.1.52"
celes = "2.1" celes = "2.1"
chrono = { version = "0.4.23", features = ["serde"] } chrono = { version = "0.4.23", features = ["serde"] }
quick-xml = { version = "0.26", features = [ "serialize" ] } instant-xml = { version = "0.1", features = ["chrono"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["io-util", "net", "time"] } tokio = { version = "1.0", features = ["io-util", "net", "time"] }
tokio-rustls = { version = "0.23", optional = true } tokio-rustls = { version = "0.23", optional = true }
@ -26,3 +26,4 @@ regex = "1.5"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
tokio-test = "0.4" tokio-test = "0.4"
tracing-subscriber = "0.3.3" tracing-subscriber = "0.3.3"
similar-asserts = "1.4.2"

View File

@ -18,9 +18,9 @@ use crate::common::{Certificate, NoExtension, PrivateKey};
pub use crate::connection::Connector; pub use crate::connection::Connector;
use crate::connection::{self, EppConnection}; use crate::connection::{self, EppConnection};
use crate::error::Error; use crate::error::Error;
use crate::hello::{Greeting, GreetingDocument, HelloDocument}; use crate::hello::{Greeting, Hello};
use crate::request::{Command, CommandDocument, Extension, Transaction}; use crate::request::{Command, CommandWrapper, Extension, Transaction};
use crate::response::{Response, ResponseDocument, ResponseStatus}; use crate::response::{Response, ResponseStatus};
use crate::xml; use crate::xml;
/// An `EppClient` provides an interface to sending EPP requests to a registry /// An `EppClient` provides an interface to sending EPP requests to a registry
@ -35,9 +35,9 @@ use crate::xml;
/// # use std::net::ToSocketAddrs; /// # use std::net::ToSocketAddrs;
/// # use std::time::Duration; /// # use std::time::Duration;
/// # /// #
/// use epp_client::EppClient; /// use instant_epp::EppClient;
/// use epp_client::domain::DomainCheck; /// use instant_epp::domain::DomainCheck;
/// use epp_client::common::NoExtension; /// use instant_epp::common::NoExtension;
/// ///
/// # #[tokio::main] /// # #[tokio::main]
/// # async fn main() { /// # async fn main() {
@ -55,9 +55,12 @@ use crate::xml;
/// // Execute an EPP Command against the registry with distinct request and response objects /// // Execute an EPP Command against the registry with distinct request and response objects
/// let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] }; /// let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] };
/// let response = client.transact(&domain_check, "transaction-id").await.unwrap(); /// let response = client.transact(&domain_check, "transaction-id").await.unwrap();
/// response.res_data.unwrap().list /// response
/// .res_data()
/// .unwrap()
/// .list
/// .iter() /// .iter()
/// .for_each(|chk| println!("Domain: {}, Available: {}", chk.id, chk.available)); /// .for_each(|chk| println!("Domain: {}, Available: {}", chk.inner.id, chk.inner.available));
/// # } /// # }
/// ``` /// ```
/// ///
@ -103,13 +106,13 @@ impl<C: Connector> EppClient<C> {
/// Executes an EPP Hello call and returns the response as a `Greeting` /// Executes an EPP Hello call and returns the response as a `Greeting`
pub async fn hello(&mut self) -> Result<Greeting, Error> { pub async fn hello(&mut self) -> Result<Greeting, Error> {
let xml = xml::serialize(&HelloDocument::default())?; let xml = xml::serialize(Hello)?;
debug!("{}: hello: {}", self.connection.registry, &xml); debug!("{}: hello: {}", self.connection.registry, &xml);
let response = self.connection.transact(&xml)?.await?; let response = self.connection.transact(&xml)?.await?;
debug!("{}: greeting: {}", self.connection.registry, &response); debug!("{}: greeting: {}", self.connection.registry, &response);
Ok(xml::deserialize::<GreetingDocument>(&response)?.data) xml::deserialize::<Greeting>(&response)
} }
pub async fn transact<'c, 'e, Cmd, Ext>( pub async fn transact<'c, 'e, Cmd, Ext>(
@ -122,29 +125,28 @@ impl<C: Connector> EppClient<C> {
Ext: Extension + 'e, Ext: Extension + 'e,
{ {
let data = data.into(); let data = data.into();
let document = CommandDocument::new(data.command, data.extension, id); let document = CommandWrapper::new(data.command, data.extension, id);
let xml = xml::serialize(&document)?; let xml = xml::serialize(&document)?;
debug!("{}: request: {}", self.connection.registry, &xml); debug!("{}: request: {}", self.connection.registry, &xml);
let response = self.connection.transact(&xml)?.await?; let response = self.connection.transact(&xml)?.await?;
debug!("{}: response: {}", self.connection.registry, &response); debug!("{}: response: {}", self.connection.registry, &response);
let rsp = let rsp = match xml::deserialize::<Response<Cmd::Response, Ext::Response>>(&response) {
match xml::deserialize::<ResponseDocument<Cmd::Response, Ext::Response>>(&response) { Ok(rsp) => rsp,
Ok(rsp) => rsp, Err(e) => {
Err(e) => { error!(%response, "failed to deserialize response for transaction: {e}");
error!(%response, "failed to deserialize response for transaction: {e}"); return Err(e);
return Err(e); }
} };
};
if rsp.data.result.code.is_success() { if rsp.result.code.is_success() {
return Ok(rsp.data); return Ok(rsp);
} }
let err = crate::error::Error::Command(Box::new(ResponseStatus { let err = crate::error::Error::Command(Box::new(ResponseStatus {
result: rsp.data.result, result: rsp.result,
tr_ids: rsp.data.tr_ids, tr_ids: rsp.tr_ids,
})); }));
error!(%response, "Failed to deserialize response for transaction: {}", err); error!(%response, "Failed to deserialize response for transaction: {}", err);
@ -164,7 +166,7 @@ impl<C: Connector> EppClient<C> {
/// Returns the greeting received on establishment of the connection as an `Greeting` /// Returns the greeting received on establishment of the connection as an `Greeting`
pub fn greeting(&self) -> Result<Greeting, Error> { pub fn greeting(&self) -> Result<Greeting, Error> {
xml::deserialize::<GreetingDocument>(&self.connection.greeting).map(|obj| obj.data) xml::deserialize::<Greeting>(&self.connection.greeting)
} }
pub async fn reconnect(&mut self) -> Result<(), Error> { pub async fn reconnect(&mut self) -> Result<(), Error> {

View File

@ -1,135 +1,45 @@
//! Common data types included in EPP Requests and Responses //! Common data types included in EPP Requests and Responses
use std::ops::Deref; use std::borrow::Cow;
use std::{borrow::Cow, fmt::Display, net::IpAddr};
use serde::ser::SerializeSeq; use instant_xml::{FromXml, ToXml};
use serde::{Deserialize, Serialize};
use crate::request::Extension; use crate::request::Extension;
pub(crate) const EPP_XMLNS: &str = "urn:ietf:params:xml:ns:epp-1.0"; pub(crate) const EPP_XMLNS: &str = "urn:ietf:params:xml:ns:epp-1.0";
/// Wraps String for easier serialization to and from values that are inner text #[derive(Debug, Eq, PartialEq, ToXml)]
/// for tags rather than attributes
#[derive(Default, Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct StringValue<'a>(Cow<'a, str>);
impl Deref for StringValue<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl<'a> AsRef<str> for StringValue<'a> {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Display for StringValue<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> From<&'a str> for StringValue<'a> {
fn from(s: &'a str) -> Self {
Self(s.into())
}
}
impl From<String> for StringValue<'static> {
fn from(s: String) -> Self {
Self(s.into())
}
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
/// An empty placeholder tag. To be refactored to something more compliant later.
pub struct NoExtension; pub struct NoExtension;
impl<'xml> FromXml<'xml> for NoExtension {
fn matches(_: instant_xml::Id<'_>, _: Option<instant_xml::Id<'_>>) -> bool {
false
}
fn deserialize<'cx>(
_: &mut Self::Accumulator,
_: &'static str,
_: &mut instant_xml::Deserializer<'cx, 'xml>,
) -> Result<(), instant_xml::Error> {
unreachable!()
}
type Accumulator = Option<Self>;
const KIND: instant_xml::Kind = instant_xml::Kind::Element;
}
impl Extension for NoExtension { impl Extension for NoExtension {
type Response = NoExtension; type Response = NoExtension;
} }
/// Type that represents the &lt;name&gt; tag for host check response
#[derive(Deserialize, Debug)]
struct Available {
/// The resource name
#[serde(rename = "$value")]
pub id: StringValue<'static>,
/// The resource (un)availability
#[serde(rename = "avail")]
pub available: bool,
}
/// Type that represents the &lt;cd&gt; tag for domain check response
#[derive(Deserialize, Debug)]
struct CheckResponseDataItem {
/// Data under the &lt;name&gt; tag
#[serde(rename = "name", alias = "id")]
pub resource: Available,
/// The reason for (un)availability
pub reason: Option<StringValue<'static>>,
}
/// Type that represents the &lt;chkData&gt; tag for host check response
#[derive(Deserialize, Debug)]
struct CheckData {
/// Data under the &lt;cd&gt; tag
#[serde(rename = "cd")]
pub list: Vec<CheckResponseDataItem>,
}
/// Type that represents the &lt;resData&gt; tag for host check response
#[derive(Deserialize, Debug)]
struct DeserializedCheckResponse {
/// Data under the &lt;chkData&gt; tag
#[serde(rename = "chkData")]
pub check_data: CheckData,
}
#[derive(Debug)]
pub struct Checked {
pub id: String,
pub available: bool,
pub reason: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(from = "DeserializedCheckResponse")]
pub struct CheckResponse {
pub list: Vec<Checked>,
}
impl From<DeserializedCheckResponse> for CheckResponse {
fn from(rsp: DeserializedCheckResponse) -> Self {
Self {
list: rsp
.check_data
.list
.into_iter()
.map(|item| Checked {
id: item.resource.id.0.into_owned(),
available: item.resource.available,
reason: item.reason.map(|r| r.0.into_owned()),
})
.collect(),
}
}
}
/// The <option> type in EPP XML login requests /// The <option> type in EPP XML login requests
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
#[serde(rename = "options")] #[xml(rename = "options", ns(EPP_XMLNS))]
pub struct Options<'a> { pub struct Options<'a> {
/// The EPP version being used /// The EPP version being used
pub version: StringValue<'a>, pub version: Cow<'a, str>,
/// The language that will be used during EPP transactions /// The language that will be used during EPP transactions
pub lang: StringValue<'a>, pub lang: Cow<'a, str>,
} }
impl<'a> Options<'a> { impl<'a> Options<'a> {
@ -143,73 +53,26 @@ impl<'a> Options<'a> {
} }
/// The <svcExtension> type in EPP XML /// The <svcExtension> type in EPP XML
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
#[serde(rename = "svcExtension")] #[xml(rename = "svcExtension", ns(EPP_XMLNS))]
pub struct ServiceExtension<'a> { pub struct ServiceExtension<'a> {
/// The service extension URIs being represented by <extURI> in EPP XML /// The service extension URIs being represented by <extURI> in EPP XML
#[serde(rename = "extURI")] #[xml(rename = "extURI")]
pub ext_uris: Option<Vec<StringValue<'a>>>, pub ext_uris: Option<Vec<Cow<'a, str>>>,
} }
/// The <svcs> type in EPP XML /// The <svcs> type in EPP XML
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
#[xml(rename = "svcs", ns(EPP_XMLNS))]
pub struct Services<'a> { pub struct Services<'a> {
/// The service URIs being used by this EPP session represented by <objURI> in EPP XML /// The service URIs being used by this EPP session represented by <objURI> in EPP XML
#[serde(rename = "objURI")] #[xml(rename = "objURI")]
pub obj_uris: Vec<StringValue<'a>>, pub obj_uris: Vec<Cow<'a, str>>,
/// The <svcExtention> being used in this EPP session // The <svcExtension> being used in this EPP session
#[serde(rename = "svcExtension")] #[xml(rename = "svcExtension")]
pub svc_ext: Option<ServiceExtension<'a>>, pub svc_ext: Option<ServiceExtension<'a>>,
} }
/// The &lt;hostAddr&gt; types domain or host transactions
#[derive(Serialize, Deserialize, Debug)]
pub(crate) struct HostAddr<'a> {
#[serde(rename = "ip")]
pub ip_version: Option<Cow<'a, str>>,
#[serde(rename = "$value")]
pub address: Cow<'a, str>,
}
impl From<&IpAddr> for HostAddr<'static> {
fn from(addr: &IpAddr) -> Self {
Self {
ip_version: Some(match addr {
IpAddr::V4(_) => "v4".into(),
IpAddr::V6(_) => "v6".into(),
}),
address: addr.to_string().into(),
}
}
}
pub(crate) fn serialize_host_addrs_option<T: AsRef<[IpAddr]>, S>(
addrs: &Option<T>,
ser: S,
) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let addrs = match addrs {
Some(addrs) => addrs.as_ref(),
None => return ser.serialize_none(),
};
let mut seq = ser.serialize_seq(Some(addrs.len()))?;
for addr in addrs {
seq.serialize_element(&HostAddr::from(addr))?;
}
seq.end()
}
/// The &lt;status&gt; type on contact transactions
#[derive(Serialize, Deserialize, Debug)]
pub struct ObjectStatus<'a> {
/// The status name, represented by the 's' attr on &lt;status&gt; tags
#[serde(rename = "s")]
pub status: Cow<'a, str>,
}
/// This type contains a single DER-encoded X.509 certificate. /// This type contains a single DER-encoded X.509 certificate.
/// ///
/// The rustls-pemfile crate can be used to parse a PEM file. /// The rustls-pemfile crate can be used to parse a PEM file.

View File

@ -1,9 +1,8 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use serde::{Deserialize, Serialize}; use instant_xml::{display_to_xml, from_xml_str, FromXml, ToXml};
use crate::common::StringValue;
pub mod check; pub mod check;
pub use check::ContactCheck; pub use check::ContactCheck;
@ -22,9 +21,39 @@ pub use update::ContactUpdate;
pub const XMLNS: &str = "urn:ietf:params:xml:ns:contact-1.0"; pub const XMLNS: &str = "urn:ietf:params:xml:ns:contact-1.0";
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Debug, Clone)]
pub struct Country(celes::Country); pub struct Country(celes::Country);
impl<'xml> FromXml<'xml> for Country {
fn matches(id: instant_xml::Id<'_>, _: Option<instant_xml::Id<'_>>) -> bool {
id == instant_xml::Id {
ns: XMLNS,
name: "cc",
}
}
fn deserialize<'cx>(
into: &mut Self::Accumulator,
_: &'static str,
deserializer: &mut instant_xml::Deserializer<'cx, 'xml>,
) -> Result<(), instant_xml::Error> {
from_xml_str(deserializer, into)
}
type Accumulator = Option<Self>;
const KIND: instant_xml::Kind = instant_xml::Kind::Scalar;
}
impl ToXml for Country {
fn serialize<W: fmt::Write + ?Sized>(
&self,
field: Option<instant_xml::Id<'_>>,
serializer: &mut instant_xml::Serializer<W>,
) -> Result<(), instant_xml::Error> {
display_to_xml(&self.0.alpha2, field, serializer)
}
}
impl FromStr for Country { impl FromStr for Country {
type Err = <celes::Country as FromStr>::Err; type Err = <celes::Country as FromStr>::Err;
@ -42,11 +71,12 @@ impl std::ops::Deref for Country {
} }
/// The &lt;authInfo&gt; tag for domain and contact transactions /// The &lt;authInfo&gt; tag for domain and contact transactions
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Debug, Clone, FromXml, ToXml)]
#[xml(rename = "authInfo", ns(XMLNS))]
pub struct ContactAuthInfo<'a> { pub struct ContactAuthInfo<'a> {
/// The &lt;pw&gt; tag under &lt;authInfo&gt; /// The &lt;pw&gt; tag under &lt;authInfo&gt;
#[serde(rename = "contact:pw", alias = "pw")] #[xml(rename = "pw")]
pub password: StringValue<'a>, pub password: Cow<'a, str>,
} }
impl<'a> ContactAuthInfo<'a> { impl<'a> ContactAuthInfo<'a> {
@ -58,18 +88,46 @@ impl<'a> ContactAuthInfo<'a> {
} }
} }
/// The data for &lt;voice&gt; and &lt;fax&gt; types on domain transactions /// The data for &lt;voice&gt; types on domain transactions
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Debug, Clone, FromXml, ToXml)]
pub struct Phone<'a> { #[xml(rename = "voice", ns(XMLNS))]
/// The inner text on the &lt;voice&gt; and &lt;fax&gt; tags pub struct Voice<'a> {
#[serde(rename = "$value")]
pub number: Cow<'a, str>,
/// The value of the 'x' attr on &lt;voice&gt; and &lt;fax&gt; tags /// The value of the 'x' attr on &lt;voice&gt; and &lt;fax&gt; tags
#[serde(rename = "x")] #[xml(rename = "x", attribute)]
pub extension: Option<Cow<'a, str>>, pub extension: Option<Cow<'a, str>>,
/// The inner text on the &lt;voice&gt; and &lt;fax&gt; tags
#[xml(direct)]
pub number: Cow<'a, str>,
} }
impl<'a> Phone<'a> { impl<'a> Voice<'a> {
/// Creates a new Phone instance with a given phone number
pub fn new(number: &'a str) -> Self {
Self {
extension: None,
number: number.into(),
}
}
/// Sets the extension value of the Phone type
pub fn set_extension(&mut self, ext: &'a str) {
self.extension = Some(ext.into());
}
}
/// The data for &lt;voice&gt; and &lt;fax&gt; types on domain transactions
#[derive(Debug, Clone, FromXml, ToXml)]
#[xml(rename = "fax", ns(XMLNS))]
pub struct Fax<'a> {
/// The value of the 'x' attr on &lt;voice&gt; and &lt;fax&gt; tags
#[xml(rename = "x", attribute)]
pub extension: Option<Cow<'a, str>>,
/// The inner text on the &lt;voice&gt; and &lt;fax&gt; tags
#[xml(direct)]
pub number: Cow<'a, str>,
}
impl<'a> Fax<'a> {
/// Creates a new Phone instance with a given phone number /// Creates a new Phone instance with a given phone number
pub fn new(number: &'a str) -> Self { pub fn new(number: &'a str) -> Self {
Self { Self {
@ -85,22 +143,21 @@ impl<'a> Phone<'a> {
} }
/// The &lt;addr&gt; type on contact transactions /// The &lt;addr&gt; type on contact transactions
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Debug, Clone, FromXml, ToXml)]
#[xml(rename = "addr", ns(XMLNS))]
pub struct Address<'a> { pub struct Address<'a> {
/// The &lt;street&gt; tags under &lt;addr&gt; /// The &lt;street&gt; tags under &lt;addr&gt;
#[serde(rename = "contact:street", alias = "street")] pub street: Vec<Cow<'a, str>>,
pub street: Vec<StringValue<'a>>,
/// The &lt;city&gt; tag under &lt;addr&gt; /// The &lt;city&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:city", alias = "city")] pub city: Cow<'a, str>,
pub city: StringValue<'a>,
/// The &lt;sp&gt; tag under &lt;addr&gt; /// The &lt;sp&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:sp", alias = "sp")] #[xml(rename = "sp")]
pub province: StringValue<'a>, pub province: Cow<'a, str>,
/// The &lt;pc&gt; tag under &lt;addr&gt; /// The &lt;pc&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:pc", alias = "pc")] #[xml(rename = "pc")]
pub postal_code: StringValue<'a>, pub postal_code: Cow<'a, str>,
/// The &lt;cc&gt; tag under &lt;addr&gt; /// The &lt;cc&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:cc", alias = "cc")] #[xml(rename = "cc")]
pub country: Country, pub country: Country,
} }
@ -126,35 +183,43 @@ impl<'a> Address<'a> {
} }
/// The &lt;postalInfo&gt; type on contact transactions /// The &lt;postalInfo&gt; type on contact transactions
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Debug, Clone, FromXml, ToXml)]
#[xml(rename = "postalInfo", ns(XMLNS))]
pub struct PostalInfo<'a> { pub struct PostalInfo<'a> {
/// The 'type' attr on &lt;postalInfo&gt; /// The 'type' attr on &lt;postalInfo&gt;
#[serde(rename = "type")] #[xml(rename = "type", attribute)]
pub info_type: String, pub info_type: Cow<'a, str>,
/// The &lt;name&gt; tag under &lt;postalInfo&gt; /// The &lt;name&gt; tag under &lt;postalInfo&gt;
#[serde(rename = "contact:name", alias = "name")] pub name: Cow<'a, str>,
pub name: StringValue<'a>,
/// The &lt;org&gt; tag under &lt;postalInfo&gt; /// The &lt;org&gt; tag under &lt;postalInfo&gt;
#[serde(rename = "contact:org", alias = "org")] #[xml(rename = "org")]
pub organization: StringValue<'a>, pub organization: Cow<'a, str>,
/// The &lt;addr&gt; tag under &lt;postalInfo&gt; /// The &lt;addr&gt; tag under &lt;postalInfo&gt;
#[serde(rename = "contact:addr", alias = "addr")]
pub address: Address<'a>, pub address: Address<'a>,
} }
impl<'a> PostalInfo<'a> { impl<'a> PostalInfo<'a> {
/// Creates a new PostalInfo instance /// Creates a new PostalInfo instance
pub fn new( pub fn new(
info_type: &str, info_type: &'a str,
name: &'a str, name: &'a str,
organization: &'a str, organization: &'a str,
address: Address<'a>, address: Address<'a>,
) -> Self { ) -> Self {
Self { Self {
info_type: info_type.to_string(), info_type: info_type.into(),
name: name.into(), name: name.into(),
organization: organization.into(), organization: organization.into(),
address, address,
} }
} }
} }
/// The &lt;status&gt; type on contact transactions
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "status", ns(XMLNS))]
pub struct Status<'a> {
/// The status name, represented by the 's' attr on &lt;status&gt; tags
#[xml(attribute, rename = "s")]
pub status: Cow<'a, str>,
}

View File

@ -1,57 +1,72 @@
use std::fmt::Debug; //! Types for EPP contact check request
use std::fmt::{self, Debug};
use instant_xml::{FromXml, Serializer, ToXml};
/// Types for EPP contact check request
use super::XMLNS; use super::XMLNS;
use crate::common::{CheckResponse, NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for ContactCheck<'a> {} impl<'a> Transaction<NoExtension> for ContactCheck<'a> {}
impl<'a> Command for ContactCheck<'a> { impl<'a> Command for ContactCheck<'a> {
type Response = CheckResponse; type Response = CheckData;
const COMMAND: &'static str = "check"; const COMMAND: &'static str = "check";
} }
// Request // Request
/// Type that represents the &lt;check&gt; command for contact transactions /// Type that represents the &lt;check&gt; command for contact transactions
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "check", ns(XMLNS))]
struct ContactList<'a> { struct ContactList<'a> {
/// The XML namespace for the contact &lt;check&gt;
#[serde(rename = "xmlns:contact")]
xmlns: &'a str,
/// The list of contact ids to check for availability /// The list of contact ids to check for availability
#[serde(rename = "contact:id")] id: &'a [&'a str],
contact_ids: Vec<StringValue<'a>>,
} }
#[derive(Serialize, Debug)] fn serialize_contacts<W: fmt::Write + ?Sized>(
struct SerializeContactCheck<'a> { ids: &[&str],
/// The &lt;check&gt; tag for the contact check command serializer: &mut Serializer<W>,
#[serde(rename = "contact:check")] ) -> Result<(), instant_xml::Error> {
list: ContactList<'a>, ContactList { id: ids }.serialize(None, serializer)
}
impl<'a> From<ContactCheck<'a>> for SerializeContactCheck<'a> {
fn from(check: ContactCheck<'a>) -> Self {
Self {
list: ContactList {
xmlns: XMLNS,
contact_ids: check.contact_ids.iter().map(|&id| id.into()).collect(),
},
}
}
} }
/// The EPP `check` command for contacts /// The EPP `check` command for contacts
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, ToXml)]
#[serde(into = "SerializeContactCheck")] #[xml(rename = "check", ns(EPP_XMLNS))]
pub struct ContactCheck<'a> { pub struct ContactCheck<'a> {
/// The list of contact IDs to be checked #[xml(serialize_with = "serialize_contacts")]
pub contact_ids: &'a [&'a str], pub contact_ids: &'a [&'a str],
} }
// Response
#[derive(Debug, FromXml)]
#[xml(rename = "id", ns(XMLNS))]
pub struct Checked {
#[xml(attribute, rename = "avail")]
pub available: bool,
#[xml(attribute)]
pub reason: Option<String>,
#[xml(direct)]
pub id: String,
}
#[derive(Debug, FromXml)]
#[xml(rename = "cd", ns(XMLNS))]
pub struct CheckedContact {
/// Data under the &lt;cd&gt; tag
pub inner: Checked,
}
/// Type that represents the &lt;chkData&gt; tag for host check response
#[derive(Debug, FromXml)]
#[xml(rename = "chkData", ns(XMLNS))]
pub struct CheckData {
pub list: Vec<CheckedContact>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ContactCheck; use super::ContactCheck;
@ -72,12 +87,12 @@ mod tests {
let results = object.res_data().unwrap(); let results = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(results.list[0].id, "eppdev-contact-1"); assert_eq!(results.list[0].inner.id, "eppdev-contact-1");
assert!(!results.list[0].available); assert!(!results.list[0].inner.available);
assert_eq!(results.list[1].id, "eppdev-contact-2"); assert_eq!(results.list[1].inner.id, "eppdev-contact-2");
assert!(results.list[1].available); assert!(results.list[1].inner.available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,53 +1,45 @@
//! Types for EPP contact create request //! Types for EPP contact create request
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::{ContactAuthInfo, Phone, PostalInfo, XMLNS}; use super::{ContactAuthInfo, Fax, PostalInfo, Voice, XMLNS};
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for ContactCreate<'a> {} impl<'a> Transaction<NoExtension> for ContactCreate<'a> {}
impl<'a> Command for ContactCreate<'a> { impl<'a> Command for ContactCreate<'a> {
type Response = ContactCreateResponse; type Response = ContactCreateData;
const COMMAND: &'static str = "create"; const COMMAND: &'static str = "create";
} }
// Request // Request
/// Type for elements under the contact &lt;create&gt; tag /// Type for elements under the contact &lt;create&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct Contact<'a> { #[xml(rename = "create", ns(XMLNS))]
/// XML namespace for contact commands pub struct ContactCreateRequest<'a> {
#[serde(rename = "xmlns:contact")]
xmlns: &'a str,
/// Contact &lt;id&gt; tag /// Contact &lt;id&gt; tag
#[serde(rename = "contact:id")] id: &'a str,
id: StringValue<'a>,
/// Contact &lt;postalInfo&gt; tag /// Contact &lt;postalInfo&gt; tag
#[serde(rename = "contact:postalInfo")]
postal_info: PostalInfo<'a>, postal_info: PostalInfo<'a>,
/// Contact &lt;voice&gt; tag /// Contact &lt;voice&gt; tag
#[serde(rename = "contact:voice")] voice: Voice<'a>,
voice: Phone<'a>, /// Contact &lt;fax&gt; tag,]
/// Contact &lt;fax&gt; tag, fax: Option<Fax<'a>>,
#[serde(rename = "contact:fax")]
fax: Option<Phone<'a>>,
/// Contact &lt;email&gt; tag /// Contact &lt;email&gt; tag
#[serde(rename = "contact:email")] email: &'a str,
email: StringValue<'a>,
/// Contact &lt;authInfo&gt; tag /// Contact &lt;authInfo&gt; tag
#[serde(rename = "contact:authInfo")]
auth_info: ContactAuthInfo<'a>, auth_info: ContactAuthInfo<'a>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;create&gt; command for contacts /// Type for EPP XML &lt;create&gt; command for contacts
#[derive(Debug, ToXml)]
#[xml(rename = "create", ns(EPP_XMLNS))]
pub struct ContactCreate<'a> { pub struct ContactCreate<'a> {
/// Data for &lt;create&gt; command for contact /// Data for &lt;create&gt; command for contact
#[serde(rename = "contact:create")] pub contact: ContactCreateRequest<'a>,
pub contact: Contact<'a>,
} }
impl<'a> ContactCreate<'a> { impl<'a> ContactCreate<'a> {
@ -55,24 +47,23 @@ impl<'a> ContactCreate<'a> {
id: &'a str, id: &'a str,
email: &'a str, email: &'a str,
postal_info: PostalInfo<'a>, postal_info: PostalInfo<'a>,
voice: Phone<'a>, voice: Voice<'a>,
auth_password: &'a str, auth_password: &'a str,
) -> Self { ) -> Self {
Self { Self {
contact: Contact { contact: ContactCreateRequest {
xmlns: XMLNS, id,
id: id.into(),
postal_info, postal_info,
voice, voice,
fax: None, fax: None,
email: email.into(), email,
auth_info: ContactAuthInfo::new(auth_password), auth_info: ContactAuthInfo::new(auth_password),
}, },
} }
} }
/// Sets the &lt;fax&gt; data for the request /// Sets the &lt;fax&gt; data for the request
pub fn set_fax(&mut self, fax: Phone<'a>) { pub fn set_fax(&mut self, fax: Fax<'a>) {
self.contact.fax = Some(fax); self.contact.fax = Some(fax);
} }
} }
@ -80,28 +71,21 @@ impl<'a> ContactCreate<'a> {
// Response // Response
/// Type that represents the &lt;creData&gt; tag for contact create response /// Type that represents the &lt;creData&gt; tag for contact create response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[xml(rename = "creData", ns(XMLNS))]
pub struct ContactCreateData { pub struct ContactCreateData {
/// The contact id /// The contact id
pub id: StringValue<'static>, pub id: String,
#[serde(rename = "crDate")] #[xml(rename = "crDate")]
/// The contact creation date /// The contact creation date
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
} }
/// Type that represents the &lt;resData&gt; tag for contact create response
#[derive(Deserialize, Debug)]
pub struct ContactCreateResponse {
/// Data under the &lt;creData&gt; tag
#[serde(rename = "creData")]
pub create_data: ContactCreateData,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use super::{ContactCreate, Phone, PostalInfo}; use super::{ContactCreate, Fax, PostalInfo, Voice};
use crate::contact::Address; use crate::contact::Address;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
@ -111,9 +95,9 @@ mod tests {
let street = &["58", "Orchid Road"]; let street = &["58", "Orchid Road"];
let address = Address::new(street, "Paris", "Paris", "392374", "FR".parse().unwrap()); let address = Address::new(street, "Paris", "Paris", "392374", "FR".parse().unwrap());
let postal_info = PostalInfo::new("int", "John Doe", "Acme Widgets", address); let postal_info = PostalInfo::new("int", "John Doe", "Acme Widgets", address);
let mut voice = Phone::new("+33.47237942"); let mut voice = Voice::new("+33.47237942");
voice.set_extension("123"); voice.set_extension("123");
let mut fax = Phone::new("+33.86698799"); let mut fax = Fax::new("+33.86698799");
fax.set_extension("677"); fax.set_extension("677");
let mut object = ContactCreate::new( let mut object = ContactCreate::new(
@ -134,13 +118,13 @@ mod tests {
let results = object.res_data().unwrap(); let results = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(results.create_data.id, "eppdev-contact-4".into()); assert_eq!(results.id, "eppdev-contact-4");
assert_eq!( assert_eq!(
results.create_data.created_at, results.created_at,
Utc.with_ymd_and_hms(2021, 7, 25, 16, 5, 32).unwrap(), Utc.with_ymd_and_hms(2021, 7, 25, 16, 5, 32).unwrap(),
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,10 @@
//! Types for EPP contact delete request //! Types for EPP contact delete request
use instant_xml::ToXml;
use super::XMLNS; use super::XMLNS;
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for ContactDelete<'a> {} impl<'a> Transaction<NoExtension> for ContactDelete<'a> {}
@ -13,31 +14,25 @@ impl<'a> Command for ContactDelete<'a> {
} }
/// Type containing the data for the &lt;delete&gt; tag for contacts /// Type containing the data for the &lt;delete&gt; tag for contacts
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct ContactDeleteRequestData<'a> { #[xml(rename = "delete", ns(XMLNS))]
/// XML namespace for the &lt;delete&gt; command for contacts pub struct ContactDeleteRequest<'a> {
#[serde(rename = "xmlns:contact")]
xmlns: &'a str,
/// The id of the contact to be deleted /// The id of the contact to be deleted
#[serde(rename = "contact:id")] id: &'a str,
id: StringValue<'a>,
} }
#[derive(Serialize, Debug)]
/// The &lt;delete&gt; type for the contact delete EPP command /// The &lt;delete&gt; type for the contact delete EPP command
#[derive(Debug, ToXml)]
#[xml(rename = "delete", ns(EPP_XMLNS))]
pub struct ContactDelete<'a> { pub struct ContactDelete<'a> {
#[serde(rename = "contact:delete")]
/// The data for the &lt;delete&gt; tag for a contact delete command /// The data for the &lt;delete&gt; tag for a contact delete command
contact: ContactDeleteRequestData<'a>, contact: ContactDeleteRequest<'a>,
} }
impl<'a> ContactDelete<'a> { impl<'a> ContactDelete<'a> {
pub fn new(id: &'a str) -> Self { pub fn new(id: &'a str) -> Self {
Self { Self {
contact: ContactDeleteRequestData { contact: ContactDeleteRequest { id },
xmlns: XMLNS,
id: id.into(),
},
} }
} }
} }
@ -58,8 +53,8 @@ mod tests {
fn response() { fn response() {
let object = response_from_file::<ContactDelete>("response/contact/delete.xml"); let object = response_from_file::<ContactDelete>("response/contact/delete.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,49 +1,44 @@
//! Types for EPP contact info request //! Types for EPP contact info request
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::{ContactAuthInfo, Phone, PostalInfo, XMLNS}; use super::{ContactAuthInfo, Fax, PostalInfo, Status, Voice, XMLNS};
use crate::common::{NoExtension, ObjectStatus, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for ContactInfo<'a> {} impl<'a> Transaction<NoExtension> for ContactInfo<'a> {}
impl<'a> Command for ContactInfo<'a> { impl<'a> Command for ContactInfo<'a> {
type Response = ContactInfoResponse; type Response = ContactInfoData;
const COMMAND: &'static str = "info"; const COMMAND: &'static str = "info";
} }
// Request // Request
/// Type for elements under the contact &lt;info&gt; tag /// Type for elements under the contact &lt;info&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct ContactInfoRequestData<'a> { #[xml(rename = "info", ns(XMLNS))]
/// XML namespace for contact commands pub struct ContactInfoRequest<'a> {
#[serde(rename = "xmlns:contact")]
xmlns: &'a str,
/// The contact id for the info command /// The contact id for the info command
#[serde(rename = "contact:id")] id: &'a str,
id: StringValue<'a>,
/// The &lt;authInfo&gt; data /// The &lt;authInfo&gt; data
#[serde(rename = "contact:authInfo")]
auth_info: ContactAuthInfo<'a>, auth_info: ContactAuthInfo<'a>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;info&gt; command for contacts /// Type for EPP XML &lt;info&gt; command for contacts
#[derive(Debug, ToXml)]
#[xml(rename = "info", ns(EPP_XMLNS))]
pub struct ContactInfo<'a> { pub struct ContactInfo<'a> {
/// Data for &lt;info&gt; command for contact /// Data for &lt;info&gt; command for contact
#[serde(rename = "contact:info")] info: ContactInfoRequest<'a>,
info: ContactInfoRequestData<'a>,
} }
impl<'a> ContactInfo<'a> { impl<'a> ContactInfo<'a> {
pub fn new(id: &'a str, auth_password: &'a str) -> ContactInfo<'a> { pub fn new(id: &'a str, auth_password: &'a str) -> ContactInfo<'a> {
Self { Self {
info: ContactInfoRequestData { info: ContactInfoRequest {
xmlns: XMLNS, id,
id: id.into(),
auth_info: ContactAuthInfo::new(auth_password), auth_info: ContactAuthInfo::new(auth_password),
}, },
} }
@ -53,53 +48,44 @@ impl<'a> ContactInfo<'a> {
// Response // Response
/// Type that represents the &lt;infData&gt; tag for contact check response /// Type that represents the &lt;infData&gt; tag for contact check response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
pub struct ContactInfoData<'a> { #[xml(rename = "infData", ns(XMLNS))]
pub struct ContactInfoData {
/// The contact id /// The contact id
pub id: StringValue<'a>, pub id: String,
/// The contact ROID /// The contact ROID
pub roid: StringValue<'a>, pub roid: String,
/// The list of contact statuses /// The list of contact statuses
#[serde(rename = "status")] pub statuses: Vec<Status<'static>>,
pub statuses: Vec<ObjectStatus<'a>>,
/// The postal info for the contact /// The postal info for the contact
#[serde(rename = "postalInfo")] pub postal_info: PostalInfo<'static>,
pub postal_info: PostalInfo<'a>,
/// The voice data for the contact /// The voice data for the contact
pub voice: Phone<'a>, pub voice: Voice<'static>,
/// The fax data for the contact /// The fax data for the contact
pub fax: Option<Phone<'a>>, pub fax: Option<Fax<'static>>,
/// The email for the contact /// The email for the contact
pub email: StringValue<'a>, pub email: String,
/// The epp user to whom the contact belongs /// The epp user to whom the contact belongs
#[serde(rename = "clID")] #[xml(rename = "clID")]
pub client_id: StringValue<'a>, pub client_id: String,
/// The epp user who created the contact /// The epp user who created the contact
#[serde(rename = "crID")] #[xml(rename = "crID")]
pub creator_id: StringValue<'a>, pub creator_id: String,
/// The creation date /// The creation date
#[serde(rename = "crDate")] #[xml(rename = "crDate")]
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
/// The epp user who last updated the contact /// The epp user who last updated the contact
#[serde(rename = "upID")] #[xml(rename = "upID")]
pub updater_id: Option<StringValue<'a>>, pub updater_id: Option<String>,
/// The last update date /// The last update date
#[serde(rename = "upDate")] #[xml(rename = "upDate")]
pub updated_at: Option<DateTime<Utc>>, pub updated_at: Option<DateTime<Utc>>,
/// The contact transfer date /// The contact transfer date
#[serde(rename = "trDate")] #[xml(rename = "trDate")]
pub transferred_at: Option<DateTime<Utc>>, pub transferred_at: Option<DateTime<Utc>>,
/// The contact auth info /// The contact auth info
#[serde(rename = "authInfo")] #[xml(rename = "authInfo")]
pub auth_info: Option<ContactAuthInfo<'a>>, pub auth_info: Option<ContactAuthInfo<'static>>,
}
/// Type that represents the &lt;resData&gt; tag for contact info response
#[derive(Deserialize, Debug)]
pub struct ContactInfoResponse {
/// Data under the &lt;infData&gt; tag
#[serde(rename = "infData")]
pub info_data: ContactInfoData<'static>,
} }
#[cfg(test)] #[cfg(test)]
@ -121,58 +107,43 @@ mod tests {
let object = response_from_file::<ContactInfo>("response/contact/info.xml"); let object = response_from_file::<ContactInfo>("response/contact/info.xml");
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
let fax = result.info_data.fax.as_ref().unwrap(); let fax = result.fax.as_ref().unwrap();
let voice_ext = result.info_data.voice.extension.as_ref().unwrap(); let voice_ext = result.voice.extension.as_ref().unwrap();
let fax_ext = fax.extension.as_ref().unwrap(); let fax_ext = fax.extension.as_ref().unwrap();
let auth_info = result.info_data.auth_info.as_ref().unwrap(); let auth_info = result.auth_info.as_ref().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.info_data.id, "eppdev-contact-3".into()); assert_eq!(result.id, "eppdev-contact-3");
assert_eq!(result.info_data.roid, "UNDEF-ROID".into()); assert_eq!(result.roid, "UNDEF-ROID");
assert_eq!(result.info_data.statuses[0].status, "ok".to_string()); assert_eq!(result.statuses[0].status, "ok");
assert_eq!(result.info_data.postal_info.info_type, "loc".to_string()); assert_eq!(result.postal_info.info_type, "loc");
assert_eq!(result.info_data.postal_info.name, "John Doe".into()); assert_eq!(result.postal_info.name, "John Doe");
assert_eq!( assert_eq!(result.postal_info.organization, "Acme Widgets");
result.info_data.postal_info.organization, assert_eq!(result.postal_info.address.street[0], "58");
"Acme Widgets".into() assert_eq!(result.postal_info.address.street[1], "Orchid Road");
); assert_eq!(result.postal_info.address.city, "Paris");
assert_eq!(result.info_data.postal_info.address.street[0], "58".into()); assert_eq!(result.postal_info.address.province, "Paris");
assert_eq!( assert_eq!(result.postal_info.address.postal_code, "392374");
result.info_data.postal_info.address.street[1], assert_eq!(result.postal_info.address.country.alpha2, "FR");
"Orchid Road".into() assert_eq!(result.voice.number, "+33.47237942".to_string());
);
assert_eq!(result.info_data.postal_info.address.city, "Paris".into());
assert_eq!(
result.info_data.postal_info.address.province,
"Paris".into()
);
assert_eq!(
result.info_data.postal_info.address.postal_code,
"392374".into()
);
assert_eq!(result.info_data.postal_info.address.country.alpha2, "FR");
assert_eq!(result.info_data.voice.number, "+33.47237942".to_string());
assert_eq!(*voice_ext, "123".to_string()); assert_eq!(*voice_ext, "123".to_string());
assert_eq!(fax.number, "+33.86698799".to_string()); assert_eq!(fax.number, "+33.86698799".to_string());
assert_eq!(*fax_ext, "243".to_string()); assert_eq!(*fax_ext, "243".to_string());
assert_eq!(result.info_data.email, "contact@eppdev.net".into()); assert_eq!(result.email, "contact@eppdev.net");
assert_eq!(result.info_data.client_id, "eppdev".into()); assert_eq!(result.client_id, "eppdev");
assert_eq!(result.info_data.creator_id, "SYSTEM".into()); assert_eq!(result.creator_id, "SYSTEM");
assert_eq!( assert_eq!(
result.info_data.created_at, result.created_at,
Utc.with_ymd_and_hms(2021, 7, 23, 13, 9, 9).unwrap(), Utc.with_ymd_and_hms(2021, 7, 23, 13, 9, 9).unwrap(),
); );
assert_eq!(*(result.updater_id.as_ref().unwrap()), "SYSTEM");
assert_eq!( assert_eq!(
*(result.info_data.updater_id.as_ref().unwrap()), result.updated_at,
"SYSTEM".into()
);
assert_eq!(
result.info_data.updated_at,
Utc.with_ymd_and_hms(2021, 7, 23, 13, 9, 9).single() Utc.with_ymd_and_hms(2021, 7, 23, 13, 9, 9).single()
); );
assert_eq!(auth_info.password, "eppdev-387323".into()); assert_eq!(auth_info.password, "eppdev-387323");
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,10 @@
//! Types for EPP contact create request //! Types for EPP contact create request
use super::{ContactAuthInfo, Phone, PostalInfo, XMLNS}; use instant_xml::ToXml;
use crate::common::{NoExtension, ObjectStatus, StringValue};
use super::{ContactAuthInfo, Fax, PostalInfo, Status, Voice, XMLNS};
use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for ContactUpdate<'a> {} impl<'a> Transaction<NoExtension> for ContactUpdate<'a> {}
@ -15,9 +16,8 @@ impl<'a> Command for ContactUpdate<'a> {
impl<'a> ContactUpdate<'a> { impl<'a> ContactUpdate<'a> {
pub fn new(id: &'a str) -> ContactUpdate { pub fn new(id: &'a str) -> ContactUpdate {
Self { Self {
contact: ContactUpdateRequestData { contact: ContactUpdateRequest {
xmlns: XMLNS, id,
id: id.into(),
add_statuses: None, add_statuses: None,
remove_statuses: None, remove_statuses: None,
change_info: None, change_info: None,
@ -30,11 +30,11 @@ impl<'a> ContactUpdate<'a> {
&mut self, &mut self,
email: &'a str, email: &'a str,
postal_info: PostalInfo<'a>, postal_info: PostalInfo<'a>,
voice: Phone<'a>, voice: Voice<'a>,
auth_password: &'a str, auth_password: &'a str,
) { ) {
self.contact.change_info = Some(ContactChangeInfo { self.contact.change_info = Some(ContactChangeInfo {
email: Some(email.into()), email: Some(email),
postal_info: Some(postal_info), postal_info: Some(postal_info),
voice: Some(voice), voice: Some(voice),
auth_info: Some(ContactAuthInfo::new(auth_password)), auth_info: Some(ContactAuthInfo::new(auth_password)),
@ -43,72 +43,74 @@ impl<'a> ContactUpdate<'a> {
} }
/// Sets the data for the &lt;fax&gt; tag under &lt;chg&gt; for the contact update request /// Sets the data for the &lt;fax&gt; tag under &lt;chg&gt; for the contact update request
pub fn set_fax(&mut self, fax: Phone<'a>) { pub fn set_fax(&mut self, fax: Fax<'a>) {
if let Some(info) = &mut self.contact.change_info { if let Some(info) = &mut self.contact.change_info {
info.fax = Some(fax) info.fax = Some(fax)
} }
} }
/// Sets the data for the &lt;add&gt; tag for the contact update request /// Sets the data for the &lt;add&gt; tag for the contact update request
pub fn add(&mut self, status: &'a [ObjectStatus]) { pub fn add(&mut self, statuses: &'a [Status]) {
self.contact.add_statuses = Some(StatusList { status }); self.contact.add_statuses = Some(AddStatuses { statuses });
} }
/// Sets the data for the &lt;rem&gt; tag for the contact update request /// Sets the data for the &lt;rem&gt; tag for the contact update request
pub fn remove(&mut self, status: &'a [ObjectStatus]) { pub fn remove(&mut self, statuses: &'a [Status]) {
self.contact.remove_statuses = Some(StatusList { status }); self.contact.remove_statuses = Some(RemoveStatuses { statuses });
} }
} }
/// Type for elements under the &lt;chg&gt; tag for contact update request /// Type for elements under the &lt;chg&gt; tag for contact update request
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "chg", ns(XMLNS))]
pub struct ContactChangeInfo<'a> { pub struct ContactChangeInfo<'a> {
#[serde(rename = "contact:postalInfo")]
postal_info: Option<PostalInfo<'a>>, postal_info: Option<PostalInfo<'a>>,
#[serde(rename = "contact:voice")] voice: Option<Voice<'a>>,
voice: Option<Phone<'a>>, fax: Option<Fax<'a>>,
#[serde(rename = "contact:fax")] email: Option<&'a str>,
fax: Option<Phone<'a>>,
#[serde(rename = "contact:email")]
email: Option<StringValue<'a>>,
#[serde(rename = "contact:authInfo")]
auth_info: Option<ContactAuthInfo<'a>>, auth_info: Option<ContactAuthInfo<'a>>,
} }
/// Type for list of elements of the &lt;status&gt; tag for contact update request /// Type for list of elements of the &lt;status&gt; tag for contact update request
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct StatusList<'a> { pub struct StatusList<'a> {
#[serde(rename = "contact:status")] status: &'a [Status<'a>],
status: &'a [ObjectStatus<'a>], }
#[derive(Debug, ToXml)]
#[xml(rename = "add", ns(XMLNS))]
struct AddStatuses<'a> {
statuses: &'a [Status<'a>],
}
#[derive(Debug, ToXml)]
#[xml(rename = "rem", ns(XMLNS))]
struct RemoveStatuses<'a> {
statuses: &'a [Status<'a>],
} }
/// Type for elements under the contact &lt;update&gt; tag /// Type for elements under the contact &lt;update&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct ContactUpdateRequestData<'a> { #[xml(rename = "update", ns(XMLNS))]
#[serde(rename = "xmlns:contact")] pub struct ContactUpdateRequest<'a> {
xmlns: &'a str, id: &'a str,
#[serde(rename = "contact:id")] add_statuses: Option<AddStatuses<'a>>,
id: StringValue<'a>, #[xml(rename = "rem")]
#[serde(rename = "contact:add")] remove_statuses: Option<RemoveStatuses<'a>>,
add_statuses: Option<StatusList<'a>>,
#[serde(rename = "contact:rem")]
remove_statuses: Option<StatusList<'a>>,
#[serde(rename = "contact:chg")]
change_info: Option<ContactChangeInfo<'a>>, change_info: Option<ContactChangeInfo<'a>>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;update&gt; command for contacts /// Type for EPP XML &lt;update&gt; command for contacts
#[derive(Debug, ToXml)]
#[xml(rename = "update", ns(EPP_XMLNS))]
pub struct ContactUpdate<'a> { pub struct ContactUpdate<'a> {
/// The data under the &lt;update&gt; tag for the contact update /// The data under the &lt;update&gt; tag for the contact update
#[serde(rename = "contact:update")] contact: ContactUpdateRequest<'a>,
contact: ContactUpdateRequestData<'a>,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ContactUpdate, Phone, PostalInfo}; use super::{ContactUpdate, PostalInfo, Status, Voice};
use crate::common::ObjectStatus;
use crate::contact::Address; use crate::contact::Address;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
@ -120,14 +122,14 @@ mod tests {
let street = &["58", "Orchid Road"]; let street = &["58", "Orchid Road"];
let address = Address::new(street, "Paris", "Paris", "392374", "FR".parse().unwrap()); let address = Address::new(street, "Paris", "Paris", "392374", "FR".parse().unwrap());
let postal_info = PostalInfo::new("loc", "John Doe", "Acme Widgets", address); let postal_info = PostalInfo::new("loc", "John Doe", "Acme Widgets", address);
let voice = Phone::new("+33.47237942"); let voice = Voice::new("+33.47237942");
object.set_info("newemail@eppdev.net", postal_info, voice, "eppdev-387323"); object.set_info("newemail@eppdev.net", postal_info, voice, "eppdev-387323");
let add_statuses = &[ObjectStatus { let add_statuses = &[Status {
status: "clientTransferProhibited".into(), status: "clientTransferProhibited".into(),
}]; }];
object.add(add_statuses); object.add(add_statuses);
let remove_statuses = &[ObjectStatus { let remove_statuses = &[Status {
status: "clientDeleteProhibited".into(), status: "clientDeleteProhibited".into(),
}]; }];
object.remove(remove_statuses); object.remove(remove_statuses);
@ -139,8 +141,8 @@ mod tests {
fn contact_update() { fn contact_update() {
let object = response_from_file::<ContactUpdate>("response/contact/update.xml"); let object = response_from_file::<ContactUpdate>("response/contact/update.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,10 +1,11 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt;
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use serde::{Deserialize, Serialize}; use instant_xml::OptionAccumulator;
use instant_xml::{Accumulate, Deserializer, FromXml, Serializer, ToXml};
use crate::common::{serialize_host_addrs_option, HostAddr, StringValue};
use crate::Error; use crate::Error;
pub mod check; pub mod check;
@ -31,85 +32,128 @@ pub use update::DomainUpdate;
pub const XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0"; pub const XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0";
/// The &lt;hostAttr&gt; type for domain transactions /// The &lt;hostAttr&gt; type for domain transactions
#[derive(Serialize, Deserialize, Debug)] #[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
#[xml(rename = "hostAttr", ns(XMLNS))]
pub struct HostAttr<'a> { pub struct HostAttr<'a> {
/// The &lt;hostName&gt; tag /// The &lt;hostName&gt; tag
#[serde(rename = "domain:hostName", alias = "hostName")] #[xml(rename = "hostName")]
pub name: StringValue<'a>, pub name: Cow<'a, str>,
/// The &lt;hostAddr&gt; tags /// The &lt;hostAddr&gt; tags
#[serde( #[xml(
rename = "domain:hostAddr", rename = "hostAddr",
alias = "hostAddr",
serialize_with = "serialize_host_addrs_option", serialize_with = "serialize_host_addrs_option",
deserialize_with = "deserialize_host_addrs_option" deserialize_with = "deserialize_host_addrs_option"
)] )]
pub addresses: Option<Vec<IpAddr>>, pub addresses: Option<Vec<IpAddr>>,
} }
fn deserialize_host_addrs_option<'de, D>(de: D) -> Result<Option<Vec<IpAddr>>, D::Error> fn deserialize_host_addrs_option<'xml>(
where into: &mut OptionAccumulator<Vec<IpAddr>, Vec<IpAddr>>,
D: serde::de::Deserializer<'de>, field: &'static str,
{ deserializer: &mut Deserializer<'_, 'xml>,
let addrs = Option::<Vec<HostAddr<'static>>>::deserialize(de)?; ) -> Result<(), instant_xml::Error> {
let addrs = match addrs { let mut value = <Option<Vec<HostAddr<'static>>> as FromXml<'xml>>::Accumulator::default();
Some(addrs) => addrs, <Option<Vec<HostAddr<'static>>>>::deserialize(&mut value, field, deserializer)?;
None => return Ok(None), let new = match value.try_done(field)? {
Some(new) => new,
None => return Ok(()),
}; };
let result = addrs let into = into.get_mut();
.into_iter() for addr in new {
.map(|addr| IpAddr::from_str(&addr.address)) match IpAddr::from_str(&addr.address) {
.collect::<Result<_, _>>(); Ok(ip) => into.push(ip),
Err(_) => {
return Err(instant_xml::Error::UnexpectedValue(format!(
"invalid IP address '{}'",
&addr.address
)))
}
}
}
match result { Ok(())
Ok(addrs) => Ok(Some(addrs)), }
Err(e) => Err(serde::de::Error::custom(format!("{}", e))),
/// The &lt;hostAddr&gt; types domain or host transactions
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "hostAddr", ns(super::domain::XMLNS))]
pub(crate) struct HostAddr<'a> {
#[xml(attribute, rename = "ip")]
pub ip_version: Option<Cow<'a, str>>,
#[xml(direct)]
pub address: Cow<'a, str>,
}
impl From<&IpAddr> for HostAddr<'static> {
fn from(addr: &IpAddr) -> Self {
Self {
ip_version: Some(match addr {
IpAddr::V4(_) => "v4".into(),
IpAddr::V6(_) => "v6".into(),
}),
address: addr.to_string().into(),
}
} }
} }
/// The list of &lt;hostAttr&gt; types for domain transactions. Typically under an &lt;ns&gt; tag pub(crate) fn serialize_host_addrs_option<T: AsRef<[IpAddr]>, W: fmt::Write + ?Sized>(
#[derive(Serialize, Debug)] addrs: &Option<T>,
pub struct HostAttrList<'a> { serializer: &mut Serializer<'_, W>,
/// The list of &lt;hostAttr&gt; tags ) -> Result<(), instant_xml::Error> {
#[serde(rename = "domain:hostAttr", alias = "hostAttr")] let addrs = match addrs {
pub hosts: &'a [HostAttr<'a>], Some(addrs) => addrs.as_ref(),
None => return Ok(()),
};
for addr in addrs {
HostAddr::from(addr).serialize(None, serializer)?;
}
Ok(())
} }
/// The list of &lt;hostObj&gt; types for domain transactions. Typically under an &lt;ns&gt; tag #[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
#[derive(Serialize, Debug)] #[xml(rename = "hostObj", ns(XMLNS))]
pub struct HostObjList<'a> { pub struct HostObj<'a> {
/// The list of &lt;hostObj&gt; tags #[xml(direct)]
#[serde(rename = "domain:hostObj", alias = "hostObj")] pub name: Cow<'a, str>,
pub hosts: &'a [StringValue<'a>],
} }
/// Enum that can accept one type which corresponds to either the &lt;hostObj&gt; or &lt;hostAttr&gt; #[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
/// list of tags #[xml(forward)]
#[derive(Serialize, Debug)] pub enum HostInfo<'a> {
#[serde(untagged)] Attr(HostAttr<'a>),
pub enum HostList<'a> { Obj(HostObj<'a>),
HostObjList(HostObjList<'a>), }
HostAttrList(HostAttrList<'a>),
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "ns", ns(XMLNS))]
pub struct NameServers<'a> {
pub ns: Vec<HostInfo<'a>>,
} }
/// The &lt;contact&gt; type on domain creation and update requests /// The &lt;contact&gt; type on domain creation and update requests
#[derive(Serialize, Deserialize, Debug)] #[derive(Debug, FromXml, ToXml)]
#[xml(rename = "contact", ns(XMLNS))]
pub struct DomainContact<'a> { pub struct DomainContact<'a> {
/// The contact id
#[serde(rename = "$value")]
pub id: Cow<'a, str>,
/// The contact type attr (usually admin, billing, or tech in most registries) /// The contact type attr (usually admin, billing, or tech in most registries)
#[serde(rename = "type")] #[xml(attribute, rename = "type")]
pub contact_type: Cow<'a, str>, pub contact_type: Cow<'a, str>,
/// The contact id
#[xml(direct)]
pub id: Cow<'a, str>,
} }
/// The &lt;period&gt; type for registration, renewal or transfer on domain transactions /// The &lt;period&gt; type for registration, renewal or transfer on domain transactions
#[derive(Clone, Copy, Debug, Serialize)] #[derive(Clone, Copy, Debug, ToXml)]
#[xml(rename = "period", ns(XMLNS))]
pub struct Period { pub struct Period {
/// The interval (usually 'y' indicating years) /// The interval (usually 'y' indicating years)
#[xml(attribute)]
unit: char, unit: char,
/// The length of the registration, renewal or transfer period (usually in years) /// The length of the registration, renewal or transfer period (usually in years)
#[serde(rename = "$value")] #[xml(direct)]
length: u8, length: u8,
} }
@ -158,11 +202,12 @@ pub const SIX_MONTHS: Period = Period {
}; };
/// The &lt;authInfo&gt; tag for domain and contact transactions /// The &lt;authInfo&gt; tag for domain and contact transactions
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Clone, Debug, FromXml, ToXml)]
#[xml(rename = "authInfo", ns(XMLNS))]
pub struct DomainAuthInfo<'a> { pub struct DomainAuthInfo<'a> {
/// The &lt;pw&gt; tag under &lt;authInfo&gt; /// The &lt;pw&gt; tag under &lt;authInfo&gt;
#[serde(rename = "domain:pw", alias = "pw")] #[xml(rename = "pw")]
pub password: StringValue<'a>, pub password: Cow<'a, str>,
} }
impl<'a> DomainAuthInfo<'a> { impl<'a> DomainAuthInfo<'a> {
@ -173,3 +218,12 @@ impl<'a> DomainAuthInfo<'a> {
} }
} }
} }
/// The &lt;status&gt; type on contact transactions
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "status", ns(XMLNS))]
pub struct Status<'a> {
/// The status name, represented by the 's' attr on &lt;status&gt; tags
#[xml(attribute, rename = "s")]
pub status: Cow<'a, str>,
}

View File

@ -1,55 +1,72 @@
//! Types for EPP domain check request //! Types for EPP domain check request
use std::fmt;
use instant_xml::{FromXml, Serializer, ToXml};
use super::XMLNS; use super::XMLNS;
use crate::common::{CheckResponse, NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for DomainCheck<'a> {} impl<'a> Transaction<NoExtension> for DomainCheck<'a> {}
impl<'a> Command for DomainCheck<'a> { impl<'a> Command for DomainCheck<'a> {
type Response = CheckResponse; type Response = CheckData;
const COMMAND: &'static str = "check"; const COMMAND: &'static str = "check";
} }
// Request // Request
/// Type for &lt;name&gt; elements under the domain &lt;check&gt; tag #[derive(Debug, ToXml)]
#[derive(Serialize, Debug)] #[xml(rename = "check", ns(XMLNS))]
struct DomainList<'a> { struct DomainList<'a> {
#[serde(rename = "xmlns:domain")] #[xml(rename = "name", ns(XMLNS))]
/// XML namespace for domain commands domains: &'a [&'a str],
xmlns: &'a str,
#[serde(rename = "domain:name")]
/// List of domains to be checked for availability
domains: Vec<StringValue<'a>>,
} }
#[derive(Serialize, Debug)] fn serialize_domains<W: fmt::Write + ?Sized>(
struct SerializeDomainCheck<'a> { domains: &[&str],
#[serde(rename = "domain:check")] serializer: &mut Serializer<W>,
list: DomainList<'a>, ) -> Result<(), instant_xml::Error> {
DomainList { domains }.serialize(None, serializer)
} }
impl<'a> From<DomainCheck<'a>> for SerializeDomainCheck<'a> { #[derive(ToXml, Debug)]
fn from(check: DomainCheck<'a>) -> Self { #[xml(rename = "check", ns(EPP_XMLNS))]
Self {
list: DomainList {
xmlns: XMLNS,
domains: check.domains.iter().map(|&d| d.into()).collect(),
},
}
}
}
/// The EPP `check` command for domains
#[derive(Clone, Debug, Serialize)]
#[serde(into = "SerializeDomainCheck")]
pub struct DomainCheck<'a> { pub struct DomainCheck<'a> {
/// The list of domains to be checked /// The list of domains to be checked for availability
#[xml(serialize_with = "serialize_domains")]
pub domains: &'a [&'a str], pub domains: &'a [&'a str],
} }
// Response
#[derive(Debug, FromXml)]
#[xml(rename = "name", ns(XMLNS))]
pub struct Checked {
#[xml(attribute, rename = "avail")]
pub available: bool,
#[xml(attribute)]
pub reason: Option<String>,
#[xml(direct)]
pub id: String,
}
#[derive(Debug, FromXml)]
#[xml(rename = "cd", ns(XMLNS))]
pub struct CheckedDomain {
/// Data under the &lt;cd&gt; tag
#[xml(rename = "cd")]
pub inner: Checked,
}
/// Type that represents the &lt;chkData&gt; tag for host check response
#[derive(Debug, FromXml)]
#[xml(rename = "chkData", ns(XMLNS))]
pub struct CheckData {
pub list: Vec<CheckedDomain>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::DomainCheck; use super::DomainCheck;
@ -67,15 +84,15 @@ mod tests {
#[test] #[test]
fn response() { fn response() {
let object = response_from_file::<DomainCheck>("response/domain/check.xml"); let object = response_from_file::<DomainCheck>("response/domain/check.xml");
let result = object.res_data().unwrap(); let result = dbg!(&object).res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.list[0].id, "eppdev.com"); assert_eq!(result.list[0].inner.id, "eppdev.com");
assert!(result.list[0].available); assert!(result.list[0].inner.available);
assert_eq!(result.list[1].id, "eppdev.net"); assert_eq!(result.list[1].inner.id, "eppdev.net");
assert!(!result.list[1].available); assert!(!result.list[1].inner.available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,10 +1,10 @@
//! Types for EPP domain create request //! Types for EPP domain create request
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::{DomainAuthInfo, DomainContact, HostList, Period, XMLNS}; use super::{DomainAuthInfo, DomainContact, HostInfo, NameServers, Period, XMLNS};
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for DomainCreate<'a> {} impl<'a> Transaction<NoExtension> for DomainCreate<'a> {}
@ -17,39 +17,31 @@ impl<'a> Command for DomainCreate<'a> {
// Request // Request
/// Type for elements under the domain &lt;create&gt; tag /// Type for elements under the domain &lt;create&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "create", ns(XMLNS))]
pub struct DomainCreateRequestData<'a> { pub struct DomainCreateRequestData<'a> {
/// XML namespace for domain commands
#[serde(rename = "xmlns:domain")]
pub xmlns: &'a str,
/// The domain name /// The domain name
#[serde(rename = "domain:name")] pub name: &'a str,
pub name: StringValue<'a>,
/// The period of registration /// The period of registration
#[serde(rename = "domain:period")]
pub period: Period, pub period: Period,
/// The list of nameserver hosts /// The list of nameserver hosts
/// either of type `HostObjList` or `HostAttrList` /// either of type `HostObjList` or `HostAttrList`
#[serde(rename = "domain:ns")] pub ns: Option<NameServers<'a>>,
pub ns: Option<HostList<'a>>,
/// The domain registrant /// The domain registrant
#[serde(rename = "domain:registrant")] pub registrant: Option<&'a str>,
pub registrant: Option<StringValue<'a>>,
/// The list of contacts for the domain /// The list of contacts for the domain
#[serde(rename = "domain:contact")]
pub contacts: Option<&'a [DomainContact<'a>]>, pub contacts: Option<&'a [DomainContact<'a>]>,
/// The auth info for the domain /// The auth info for the domain
#[serde(rename = "domain:authInfo")]
pub auth_info: DomainAuthInfo<'a>, pub auth_info: DomainAuthInfo<'a>,
} }
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
/// Type for EPP XML &lt;create&gt; command for domains /// Type for EPP XML &lt;create&gt; command for domains
#[xml(rename = "create", ns(EPP_XMLNS))]
pub struct DomainCreate<'a> { pub struct DomainCreate<'a> {
/// The data for the domain to be created with /// The data for the domain to be created with
/// T being the type of nameserver list (`HostObjList` or `HostAttrList`) /// T being the type of nameserver list (`HostObjList` or `HostAttrList`)
/// to be supplied /// to be supplied
#[serde(rename = "domain:create")]
pub domain: DomainCreateRequestData<'a>, pub domain: DomainCreateRequestData<'a>,
} }
@ -57,18 +49,17 @@ impl<'a> DomainCreate<'a> {
pub fn new( pub fn new(
name: &'a str, name: &'a str,
period: Period, period: Period,
ns: Option<HostList<'a>>, ns: Option<&'a [HostInfo<'a>]>,
registrant_id: Option<&'a str>, registrant: Option<&'a str>,
auth_password: &'a str, auth_password: &'a str,
contacts: Option<&'a [DomainContact<'a>]>, contacts: Option<&'a [DomainContact<'a>]>,
) -> Self { ) -> Self {
Self { Self {
domain: DomainCreateRequestData { domain: DomainCreateRequestData {
xmlns: XMLNS, name,
name: name.into(),
period, period,
ns, ns: ns.map(|ns| NameServers { ns: ns.to_vec() }),
registrant: registrant_id.map(|id| id.into()), registrant,
auth_info: DomainAuthInfo::new(auth_password), auth_info: DomainAuthInfo::new(auth_password),
contacts, contacts,
}, },
@ -79,37 +70,27 @@ impl<'a> DomainCreate<'a> {
// Response // Response
/// Type that represents the &lt;chkData&gt; tag for domain create response /// Type that represents the &lt;chkData&gt; tag for domain create response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
pub struct DomainCreateResponseData { #[xml(rename = "creData", ns(XMLNS))]
/// XML namespace for domain response data pub struct DomainCreateResponse {
#[serde(rename = "xmlns:domain")]
pub xmlns: String,
/// The domain name /// The domain name
pub name: StringValue<'static>, pub name: String,
/// The creation date /// The creation date
#[serde(rename = "crDate")] #[xml(rename = "crDate")]
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
/// The expiry date /// The expiry date
#[serde(rename = "exDate")] #[xml(rename = "exDate")]
pub expiring_at: Option<DateTime<Utc>>, pub expiring_at: Option<DateTime<Utc>>,
} }
/// Type that represents the &lt;resData&gt; tag for domain create response
#[derive(Deserialize, Debug)]
pub struct DomainCreateResponse {
/// Data under the &lt;chkData&gt; tag
#[serde(rename = "creData")]
pub create_data: DomainCreateResponseData,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::net::IpAddr; use std::net::IpAddr;
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use super::{DomainContact, DomainCreate, HostList, Period}; use super::{DomainContact, DomainCreate, Period};
use crate::domain::{HostAttr, HostAttrList, HostObjList}; use crate::domain::{HostAttr, HostInfo, HostObj};
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
@ -159,11 +140,18 @@ mod tests {
}, },
]; ];
let hosts = &["ns1.test.com".into(), "ns2.test.com".into()]; let hosts = &[
HostInfo::Obj(HostObj {
name: "ns1.test.com".into(),
}),
HostInfo::Obj(HostObj {
name: "ns2.test.com".into(),
}),
];
let object = DomainCreate::new( let object = DomainCreate::new(
"eppdev-1.com", "eppdev-1.com",
Period::years(1).unwrap(), Period::years(1).unwrap(),
Some(HostList::HostObjList(HostObjList { hosts })), Some(hosts),
Some("eppdev-contact-3"), Some("eppdev-contact-3"),
"epP4uthd#v", "epP4uthd#v",
Some(contacts), Some(contacts),
@ -190,23 +178,23 @@ mod tests {
]; ];
let hosts = &[ let hosts = &[
HostAttr { HostInfo::Attr(HostAttr {
name: "ns1.eppdev-1.com".into(), name: "ns1.eppdev-1.com".into(),
addresses: None, addresses: None,
}, }),
HostAttr { HostInfo::Attr(HostAttr {
name: "ns2.eppdev-1.com".into(), name: "ns2.eppdev-1.com".into(),
addresses: Some(vec![ addresses: Some(vec![
IpAddr::from([177, 232, 12, 58]), IpAddr::from([177, 232, 12, 58]),
IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e]), IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e]),
]), ]),
}, }),
]; ];
let object = DomainCreate::new( let object = DomainCreate::new(
"eppdev-2.com", "eppdev-2.com",
Period::years(1).unwrap(), Period::years(1).unwrap(),
Some(HostList::HostAttrList(HostAttrList { hosts })), Some(hosts),
Some("eppdev-contact-3"), Some("eppdev-contact-3"),
"epP4uthd#v", "epP4uthd#v",
Some(contacts), Some(contacts),
@ -222,17 +210,17 @@ mod tests {
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.create_data.name, "eppdev-2.com".into()); assert_eq!(result.name, "eppdev-2.com");
assert_eq!( assert_eq!(
result.create_data.created_at, result.created_at,
Utc.with_ymd_and_hms(2021, 7, 25, 18, 11, 35).unwrap() Utc.with_ymd_and_hms(2021, 7, 25, 18, 11, 35).unwrap()
); );
assert_eq!( assert_eq!(
*result.create_data.expiring_at.as_ref().unwrap(), *result.expiring_at.as_ref().unwrap(),
Utc.with_ymd_and_hms(2022, 7, 25, 18, 11, 34).unwrap() Utc.with_ymd_and_hms(2022, 7, 25, 18, 11, 34).unwrap()
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,10 @@
//! Types for EPP domain delete request //! Types for EPP domain delete request
use instant_xml::ToXml;
use super::XMLNS; use super::XMLNS;
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for DomainDelete<'a> {} impl<'a> Transaction<NoExtension> for DomainDelete<'a> {}
@ -15,30 +16,24 @@ impl<'a> Command for DomainDelete<'a> {
impl<'a> DomainDelete<'a> { impl<'a> DomainDelete<'a> {
pub fn new(name: &'a str) -> Self { pub fn new(name: &'a str) -> Self {
Self { Self {
domain: DomainDeleteRequestData { domain: DomainDeleteRequestData { name },
xmlns: XMLNS,
name: name.into(),
},
} }
} }
} }
/// Type for &lt;name&gt; element under the domain &lt;delete&gt; tag /// Type for &lt;name&gt; element under the domain &lt;delete&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "delete", ns(XMLNS))]
pub struct DomainDeleteRequestData<'a> { pub struct DomainDeleteRequestData<'a> {
/// XML namespace for domain commands
#[serde(rename = "xmlns:domain")]
xmlns: &'a str,
/// The domain to be deleted /// The domain to be deleted
#[serde(rename = "domain:name")] name: &'a str,
name: StringValue<'a>,
} }
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
/// Type for EPP XML &lt;delete&gt; command for domains /// Type for EPP XML &lt;delete&gt; command for domains
#[xml(rename = "delete", ns(EPP_XMLNS))]
pub struct DomainDelete<'a> { pub struct DomainDelete<'a> {
/// The data under the &lt;delete&gt; tag for domain deletion /// The data under the &lt;delete&gt; tag for domain deletion
#[serde(rename = "domain:delete")]
domain: DomainDeleteRequestData<'a>, domain: DomainDeleteRequestData<'a>,
} }
@ -59,8 +54,8 @@ mod tests {
let object = response_from_file::<DomainDelete>("response/domain/delete.xml"); let object = response_from_file::<DomainDelete>("response/domain/delete.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,10 +1,10 @@
//! Types for EPP domain info request //! Types for EPP domain info request
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::{DomainAuthInfo, DomainContact, HostAttr, XMLNS}; use super::{DomainAuthInfo, DomainContact, HostAttr, NameServers, Status, XMLNS};
use crate::common::{NoExtension, ObjectStatus, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for DomainInfo<'a> {} impl<'a> Transaction<NoExtension> for DomainInfo<'a> {}
@ -18,8 +18,7 @@ impl<'a> DomainInfo<'a> {
pub fn new(name: &'a str, auth_password: Option<&'a str>) -> Self { pub fn new(name: &'a str, auth_password: Option<&'a str>) -> Self {
Self { Self {
info: DomainInfoRequestData { info: DomainInfoRequestData {
xmlns: XMLNS, name: Domain { hosts: "all", name },
domain: Domain { hosts: "all", name },
auth_info: auth_password.map(|password| DomainAuthInfo { auth_info: auth_password.map(|password| DomainAuthInfo {
password: password.into(), password: password.into(),
}), }),
@ -31,34 +30,32 @@ impl<'a> DomainInfo<'a> {
// Request // Request
/// Type for data under the &lt;name&gt; element tag for the domain &lt;info&gt; tag /// Type for data under the &lt;name&gt; element tag for the domain &lt;info&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "name", ns(XMLNS))]
pub struct Domain<'a> { pub struct Domain<'a> {
/// The hosts attribute. Default value is "all" /// The hosts attribute. Default value is "all"
#[xml(attribute)]
hosts: &'a str, hosts: &'a str,
/// The name of the domain /// The name of the domain
#[serde(rename = "$value")] #[xml(direct)]
name: &'a str, name: &'a str,
} }
/// Type for &lt;name&gt; element under the domain &lt;info&gt; tag /// Type for &lt;name&gt; element under the domain &lt;info&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "info", ns(XMLNS))]
pub struct DomainInfoRequestData<'a> { pub struct DomainInfoRequestData<'a> {
/// XML namespace for domain commands
#[serde(rename = "xmlns:domain")]
xmlns: &'a str,
/// The data for the domain to be queried /// The data for the domain to be queried
#[serde(rename = "domain:name")] name: Domain<'a>,
domain: Domain<'a>,
/// The auth info for the domain /// The auth info for the domain
#[serde(rename = "domain:authInfo")]
auth_info: Option<DomainAuthInfo<'a>>, auth_info: Option<DomainAuthInfo<'a>>,
} }
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
/// Type for EPP XML &lt;info&gt; command for domains /// Type for EPP XML &lt;info&gt; command for domains
#[xml(rename = "info", ns(EPP_XMLNS))]
pub struct DomainInfo<'a> { pub struct DomainInfo<'a> {
/// The data under the &lt;info&gt; tag for domain info /// The data under the &lt;info&gt; tag for domain info
#[serde(rename = "domain:info")]
info: DomainInfoRequestData<'a>, info: DomainInfoRequestData<'a>,
} }
@ -66,73 +63,66 @@ pub struct DomainInfo<'a> {
/// The two types of ns lists, hostObj and hostAttr, that may be returned in the /// The two types of ns lists, hostObj and hostAttr, that may be returned in the
/// domain info response /// domain info response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
pub struct DomainNsList { pub struct DomainNsList {
/// List of &lt;hostObj&gt; ns elements /// List of &lt;hostObj&gt; ns elements
#[serde(rename = "hostObj")] #[xml(rename = "hostObj")]
pub host_obj: Option<Vec<StringValue<'static>>>, pub host_obj: Option<Vec<String>>,
/// List of &lt;hostAttr&gt; ns elements /// List of &lt;hostAttr&gt; ns elements
pub host_attr: Option<Vec<HostAttr<'static>>>, pub host_attr: Option<Vec<HostAttr<'static>>>,
} }
/// Type that represents the &lt;infData&gt; tag for domain info response /// Type that represents the &lt;infData&gt; tag for domain info response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
pub struct DomainInfoResponseData { #[xml(rename = "infData", ns(XMLNS))]
pub struct DomainInfoResponse {
/// The domain name /// The domain name
pub name: StringValue<'static>, pub name: String,
/// The domain ROID /// The domain ROID
pub roid: StringValue<'static>, pub roid: String,
/// The list of domain statuses /// The list of domain statuses
#[serde(rename = "status")] #[xml(rename = "status")]
pub statuses: Option<Vec<ObjectStatus<'static>>>, pub statuses: Option<Vec<Status<'static>>>,
/// The domain registrant /// The domain registrant
pub registrant: Option<StringValue<'static>>, pub registrant: Option<String>,
/// The list of domain contacts /// The list of domain contacts
#[serde(rename = "contact")] #[xml(rename = "contact")]
pub contacts: Option<Vec<DomainContact<'static>>>, pub contacts: Option<Vec<DomainContact<'static>>>,
/// The list of domain nameservers /// The list of domain nameservers
#[serde(rename = "ns")] pub ns: Option<NameServers<'static>>,
pub ns: Option<DomainNsList>,
/// The list of domain hosts /// The list of domain hosts
#[serde(rename = "host")] #[xml(rename = "host")]
pub hosts: Option<Vec<StringValue<'static>>>, pub hosts: Option<Vec<String>>,
/// The epp user who owns the domain /// The epp user who owns the domain
#[serde(rename = "clID")] #[xml(rename = "clID")]
pub client_id: StringValue<'static>, pub client_id: String,
/// The epp user who created the domain /// The epp user who created the domain
#[serde(rename = "crID")] #[xml(rename = "crID")]
pub creator_id: Option<StringValue<'static>>, pub creator_id: Option<String>,
/// The domain creation date /// The domain creation date
#[serde(rename = "crDate")] #[xml(rename = "crDate")]
pub created_at: Option<DateTime<Utc>>, pub created_at: Option<DateTime<Utc>>,
/// The domain expiry date /// The domain expiry date
#[serde(rename = "exDate")] #[xml(rename = "exDate")]
pub expiring_at: Option<DateTime<Utc>>, pub expiring_at: Option<DateTime<Utc>>,
/// The epp user who last updated the domain /// The epp user who last updated the domain
#[serde(rename = "upID")] #[xml(rename = "upID")]
pub updater_id: Option<StringValue<'static>>, pub updater_id: Option<String>,
/// The domain last updated date /// The domain last updated date
#[serde(rename = "upDate")] #[xml(rename = "upDate")]
pub updated_at: Option<DateTime<Utc>>, pub updated_at: Option<DateTime<Utc>>,
/// The domain transfer date /// The domain transfer date
#[serde(rename = "trDate")] #[xml(rename = "trDate")]
pub transferred_at: Option<DateTime<Utc>>, pub transferred_at: Option<DateTime<Utc>>,
/// The domain auth info /// The domain auth info
#[serde(rename = "authInfo")] #[xml(rename = "authInfo")]
pub auth_info: Option<DomainAuthInfo<'static>>, pub auth_info: Option<DomainAuthInfo<'static>>,
} }
/// Type that represents the &lt;resData&gt; tag for domain info response
#[derive(Deserialize, Debug)]
pub struct DomainInfoResponse {
/// Data under the &lt;resData&gt; tag
#[serde(rename = "infData")]
pub info_data: DomainInfoResponseData,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::DomainInfo; use super::DomainInfo;
use crate::domain::{HostInfo, HostObj};
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
@ -146,57 +136,61 @@ mod tests {
#[test] #[test]
fn response() { fn response() {
let object = response_from_file::<DomainInfo>("response/domain/info.xml"); let object = response_from_file::<DomainInfo>("response/domain/info.xml");
dbg!(&object);
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
let auth_info = result.info_data.auth_info.as_ref().unwrap(); let auth_info = result.auth_info.as_ref().unwrap();
let ns_list = result.info_data.ns.as_ref().unwrap(); let ns = result.ns.as_ref().unwrap();
let ns = ns_list.host_obj.as_ref().unwrap(); let hosts = result.hosts.as_ref().unwrap();
let hosts = result.info_data.hosts.as_ref().unwrap(); let statuses = result.statuses.as_ref().unwrap();
let statuses = result.info_data.statuses.as_ref().unwrap(); let registrant = result.registrant.as_ref().unwrap();
let registrant = result.info_data.registrant.as_ref().unwrap(); let contacts = result.contacts.as_ref().unwrap();
let contacts = result.info_data.contacts.as_ref().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.info_data.name, "eppdev-1.com".into()); assert_eq!(result.name, "eppdev-1.com");
assert_eq!(result.info_data.roid, "125899511_DOMAIN_COM-VRSN".into()); assert_eq!(result.roid, "125899511_DOMAIN_COM-VRSN");
assert_eq!(statuses[0].status, "ok".to_string()); assert_eq!(statuses[0].status, "ok".to_string());
assert_eq!(statuses[1].status, "clientTransferProhibited".to_string()); assert_eq!(statuses[1].status, "clientTransferProhibited".to_string());
assert_eq!(*registrant, "eppdev-contact-2".into()); assert_eq!(*registrant, "eppdev-contact-2");
assert_eq!(contacts[0].id, "eppdev-contact-2".to_string()); assert_eq!(contacts[0].id, "eppdev-contact-2".to_string());
assert_eq!(contacts[0].contact_type, "admin".to_string()); assert_eq!(contacts[0].contact_type, "admin".to_string());
assert_eq!(contacts[1].id, "eppdev-contact-2".to_string()); assert_eq!(contacts[1].id, "eppdev-contact-2".to_string());
assert_eq!(contacts[1].contact_type, "tech".to_string()); assert_eq!(contacts[1].contact_type, "tech".to_string());
assert_eq!(contacts[2].id, "eppdev-contact-2".to_string()); assert_eq!(contacts[2].id, "eppdev-contact-2".to_string());
assert_eq!(contacts[2].contact_type, "billing".to_string()); assert_eq!(contacts[2].contact_type, "billing".to_string());
assert_eq!((*ns)[0], "ns1.eppdev-1.com".into());
assert_eq!((*ns)[1], "ns2.eppdev-1.com".into());
assert_eq!((*hosts)[0], "ns1.eppdev-1.com".into());
assert_eq!((*hosts)[1], "ns2.eppdev-1.com".into());
assert_eq!(result.info_data.client_id, "eppdev".into());
assert_eq!( assert_eq!(
*result.info_data.creator_id.as_ref().unwrap(), ns.ns[0],
"SYSTEM".into() HostInfo::Obj(HostObj {
name: "ns1.eppdev-1.com".into()
})
); );
assert_eq!( assert_eq!(
*result.info_data.created_at.as_ref().unwrap(), ns.ns[1],
HostInfo::Obj(HostObj {
name: "ns2.eppdev-1.com".into()
})
);
assert_eq!((*hosts)[0], "ns1.eppdev-1.com");
assert_eq!((*hosts)[1], "ns2.eppdev-1.com");
assert_eq!(result.client_id, "eppdev");
assert_eq!(*result.creator_id.as_ref().unwrap(), "SYSTEM");
assert_eq!(
*result.created_at.as_ref().unwrap(),
Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 20).unwrap() Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 20).unwrap()
); );
assert_eq!(*result.updater_id.as_ref().unwrap(), "SYSTEM");
assert_eq!( assert_eq!(
*result.info_data.updater_id.as_ref().unwrap(), *result.updated_at.as_ref().unwrap(),
"SYSTEM".into()
);
assert_eq!(
*result.info_data.updated_at.as_ref().unwrap(),
Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap() Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap()
); );
assert_eq!( assert_eq!(
*result.info_data.expiring_at.as_ref().unwrap(), *result.expiring_at.as_ref().unwrap(),
Utc.with_ymd_and_hms(2023, 7, 23, 15, 31, 20).unwrap() Utc.with_ymd_and_hms(2023, 7, 23, 15, 31, 20).unwrap()
); );
assert_eq!(auth_info.password, "epP4uthd#v".into()); assert_eq!(auth_info.password, "epP4uthd#v");
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]

View File

@ -1,10 +1,10 @@
//! Types for EPP domain renew request //! Types for EPP domain renew request
use chrono::{DateTime, NaiveDate, Utc}; use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::{Period, XMLNS}; use super::{Period, XMLNS};
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for DomainRenew<'a> {} impl<'a> Transaction<NoExtension> for DomainRenew<'a> {}
@ -16,12 +16,10 @@ impl<'a> Command for DomainRenew<'a> {
impl<'a> DomainRenew<'a> { impl<'a> DomainRenew<'a> {
pub fn new(name: &'a str, current_expiry_date: NaiveDate, period: Period) -> Self { pub fn new(name: &'a str, current_expiry_date: NaiveDate, period: Period) -> Self {
let exp_date_str = current_expiry_date.format("%Y-%m-%d").to_string().into();
Self { Self {
domain: DomainRenewRequestData { domain: DomainRenewRequestData {
xmlns: XMLNS, name,
name: name.into(), current_expiry_date,
current_expiry_date: exp_date_str,
period, period,
}, },
} }
@ -31,48 +29,38 @@ impl<'a> DomainRenew<'a> {
// Request // Request
/// Type for data under the domain &lt;renew&gt; tag /// Type for data under the domain &lt;renew&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "renew", ns(XMLNS))]
pub struct DomainRenewRequestData<'a> { pub struct DomainRenewRequestData<'a> {
/// XML namespace for domain commands
#[serde(rename = "xmlns:domain")]
xmlns: &'a str,
/// The name of the domain to be renewed /// The name of the domain to be renewed
#[serde(rename = "domain:name")] name: &'a str,
name: StringValue<'a>,
/// The current expiry date of the domain in 'Y-m-d' format /// The current expiry date of the domain in 'Y-m-d' format
#[serde(rename = "domain:curExpDate")] #[xml(rename = "curExpDate")]
current_expiry_date: StringValue<'a>, current_expiry_date: NaiveDate,
/// The period of renewal /// The period of renewal
#[serde(rename = "domain:period")]
period: Period, period: Period,
} }
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
/// Type for EPP XML &lt;renew&gt; command for domains /// Type for EPP XML &lt;renew&gt; command for domains
#[xml(rename = "renew", ns(EPP_XMLNS))]
pub struct DomainRenew<'a> { pub struct DomainRenew<'a> {
/// The data under the &lt;renew&gt; tag for the domain renewal /// The data under the &lt;renew&gt; tag for the domain renewal
#[serde(rename = "domain:renew")] #[xml(rename = "renew")]
domain: DomainRenewRequestData<'a>, domain: DomainRenewRequestData<'a>,
} }
// Response // Response
/// Type that represents the &lt;renData&gt; tag for domain renew response /// Type that represents the &lt;renData&gt; tag for domain renew response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
pub struct DomainRenewResponseData { #[xml(rename = "renData", ns(XMLNS))]
/// The name of the domain
pub name: StringValue<'static>,
/// The new expiry date after renewal
#[serde(rename = "exDate")]
pub expiring_at: Option<DateTime<Utc>>,
}
/// Type that represents the &lt;resData&gt; tag for domain renew response
#[derive(Deserialize, Debug)]
pub struct DomainRenewResponse { pub struct DomainRenewResponse {
/// Data under the &lt;renData&gt; tag /// The name of the domain
#[serde(rename = "renData")] pub name: String,
pub renew_data: DomainRenewResponseData, /// The new expiry date after renewal
#[xml(rename = "exDate")]
pub expiring_at: Option<DateTime<Utc>>,
} }
#[cfg(test)] #[cfg(test)]
@ -97,13 +85,13 @@ mod tests {
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.renew_data.name, "eppdev-1.com".into()); assert_eq!(result.name, "eppdev-1.com");
assert_eq!( assert_eq!(
*result.renew_data.expiring_at.as_ref().unwrap(), *result.expiring_at.as_ref().unwrap(),
Utc.with_ymd_and_hms(2024, 7, 23, 15, 31, 20).unwrap() Utc.with_ymd_and_hms(2024, 7, 23, 15, 31, 20).unwrap()
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,16 +1,16 @@
//! Types for EPP domain transfer request //! Types for EPP domain transfer request
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::{DomainAuthInfo, Period, XMLNS}; use super::{DomainAuthInfo, Period, XMLNS};
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for DomainTransfer<'a> {} impl<'a> Transaction<NoExtension> for DomainTransfer<'a> {}
impl<'a> Command for DomainTransfer<'a> { impl<'a> Command for DomainTransfer<'a> {
type Response = DomainTransferResponse; type Response = DomainTransferResponseData;
const COMMAND: &'static str = "transfer"; const COMMAND: &'static str = "transfer";
} }
@ -54,8 +54,7 @@ impl<'a> DomainTransfer<'a> {
Self { Self {
operation, operation,
domain: DomainTransferReqData { domain: DomainTransferReqData {
xmlns: XMLNS, name,
name: name.into(),
period, period,
auth_info, auth_info,
}, },
@ -66,71 +65,60 @@ impl<'a> DomainTransfer<'a> {
// Request // Request
/// Type for elements under the domain &lt;transfer&gt; tag /// Type for elements under the domain &lt;transfer&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "transfer", ns(XMLNS))]
pub struct DomainTransferReqData<'a> { pub struct DomainTransferReqData<'a> {
/// XML namespace for domain commands
#[serde(rename = "xmlns:domain")]
xmlns: &'a str,
/// The name of the domain under transfer /// The name of the domain under transfer
#[serde(rename = "domain:name")] name: &'a str,
name: StringValue<'a>,
/// The period of renewal upon a successful transfer /// The period of renewal upon a successful transfer
/// Only applicable in case of a transfer request /// Only applicable in case of a transfer request
#[serde(rename = "domain:period")]
period: Option<Period>, period: Option<Period>,
/// The authInfo for the domain under transfer /// The authInfo for the domain under transfer
/// Only applicable to domain transfer and domain transfer query requests /// Only applicable to domain transfer and domain transfer query requests
#[serde(rename = "domain:authInfo")] #[xml(rename = "authInfo")]
auth_info: Option<DomainAuthInfo<'a>>, auth_info: Option<DomainAuthInfo<'a>>,
} }
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "transfer", ns(EPP_XMLNS))]
/// Type for EPP XML &lt;transfer&gt; command for domains /// Type for EPP XML &lt;transfer&gt; command for domains
pub struct DomainTransfer<'a> { pub struct DomainTransfer<'a> {
/// The transfer operation to perform indicated by the 'op' attr /// The transfer operation to perform indicated by the 'op' attr
/// The values are one of transfer or query /// The values are one of transfer or query
#[serde(rename = "op")] #[xml(rename = "op", attribute)]
operation: &'a str, operation: &'a str,
/// The data under the &lt;transfer&gt; tag in the transfer request /// The data under the &lt;transfer&gt; tag in the transfer request
#[serde(rename = "domain:transfer")]
domain: DomainTransferReqData<'a>, domain: DomainTransferReqData<'a>,
} }
// Response // Response
/// Type that represents the &lt;trnData&gt; tag for domain transfer response /// Type that represents the &lt;trnData&gt; tag for domain transfer response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[xml(rename = "trnData", ns(XMLNS))]
pub struct DomainTransferResponseData { pub struct DomainTransferResponseData {
/// The domain name /// The domain name
pub name: StringValue<'static>, pub name: String,
/// The domain transfer status /// The domain transfer status
#[serde(rename = "trStatus")] #[xml(rename = "trStatus")]
pub transfer_status: StringValue<'static>, pub transfer_status: String,
/// The epp user who requested the transfer /// The epp user who requested the transfer
#[serde(rename = "reID")] #[xml(rename = "reID")]
pub requester_id: StringValue<'static>, pub requester_id: String,
/// The transfer rquest date /// The transfer rquest date
#[serde(rename = "reDate")] #[xml(rename = "reDate")]
pub requested_at: DateTime<Utc>, pub requested_at: DateTime<Utc>,
/// The epp user who should acknowledge the transfer request /// The epp user who should acknowledge the transfer request
#[serde(rename = "acID")] #[xml(rename = "acID")]
pub ack_id: StringValue<'static>, pub ack_id: String,
/// THe date by which the acknowledgment should be made /// THe date by which the acknowledgment should be made
#[serde(rename = "acDate")] #[xml(rename = "acDate")]
pub ack_by: DateTime<Utc>, pub ack_by: DateTime<Utc>,
/// The domain expiry date /// The domain expiry date
#[serde(rename = "exDate")] #[xml(rename = "exDate")]
pub expiring_at: Option<DateTime<Utc>>, pub expiring_at: Option<DateTime<Utc>>,
} }
/// Type that represents the &lt;resData&gt; tag for domain transfer response
#[derive(Deserialize, Debug)]
pub struct DomainTransferResponse {
/// Data under the &lt;trnData&gt; tag
#[serde(rename = "trnData")]
pub transfer_data: DomainTransferResponseData,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
@ -182,26 +170,26 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; action pending".into() "Command completed successfully; action pending"
); );
assert_eq!(result.transfer_data.name, "eppdev-transfer.com".into()); assert_eq!(result.name, "eppdev-transfer.com");
assert_eq!(result.transfer_data.transfer_status, "pending".into()); assert_eq!(result.transfer_status, "pending");
assert_eq!(result.transfer_data.requester_id, "eppdev".into()); assert_eq!(result.requester_id, "eppdev");
assert_eq!( assert_eq!(
result.transfer_data.requested_at, result.requested_at,
Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap(), Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap(),
); );
assert_eq!(result.transfer_data.ack_id, "ClientY".into()); assert_eq!(result.ack_id, "ClientY");
assert_eq!( assert_eq!(
result.transfer_data.ack_by, result.ack_by,
Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap() Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap()
); );
assert_eq!( assert_eq!(
result.transfer_data.expiring_at, result.expiring_at,
Utc.with_ymd_and_hms(2022, 7, 2, 14, 53, 19).single(), Utc.with_ymd_and_hms(2022, 7, 2, 14, 53, 19).single(),
); );
assert_eq!(*object.tr_ids.client_tr_id.as_ref().unwrap(), CLTRID.into()); assert_eq!(*object.tr_ids.client_tr_id.as_ref().unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -209,9 +197,9 @@ mod tests {
let object = response_from_file::<DomainTransfer>("response/domain/transfer_approve.xml"); let object = response_from_file::<DomainTransfer>("response/domain/transfer_approve.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -219,9 +207,9 @@ mod tests {
let object = response_from_file::<DomainTransfer>("response/domain/transfer_reject.xml"); let object = response_from_file::<DomainTransfer>("response/domain/transfer_reject.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -229,9 +217,9 @@ mod tests {
let object = response_from_file::<DomainTransfer>("response/domain/transfer_cancel.xml"); let object = response_from_file::<DomainTransfer>("response/domain/transfer_cancel.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -241,24 +229,24 @@ mod tests {
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.transfer_data.name, "eppdev-transfer.com".into()); assert_eq!(result.name, "eppdev-transfer.com");
assert_eq!(result.transfer_data.transfer_status, "pending".into()); assert_eq!(result.transfer_status, "pending");
assert_eq!(result.transfer_data.requester_id, "eppdev".into()); assert_eq!(result.requester_id, "eppdev");
assert_eq!( assert_eq!(
result.transfer_data.requested_at, result.requested_at,
Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap() Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap()
); );
assert_eq!(result.transfer_data.ack_id, "ClientY".into()); assert_eq!(result.ack_id, "ClientY");
assert_eq!( assert_eq!(
result.transfer_data.ack_by, result.ack_by,
Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap() Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap()
); );
assert_eq!( assert_eq!(
result.transfer_data.expiring_at, result.expiring_at,
Utc.with_ymd_and_hms(2022, 7, 2, 14, 53, 19).single() Utc.with_ymd_and_hms(2022, 7, 2, 14, 53, 19).single()
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,13 +1,13 @@
//! Types for EPP domain check request //! Types for EPP domain check request
//!
use super::{DomainAuthInfo, DomainContact, HostList, XMLNS}; use instant_xml::ToXml;
use super::{DomainAuthInfo, DomainContact, NameServers, Status, XMLNS};
use crate::{ use crate::{
common::{NoExtension, ObjectStatus, StringValue}, common::{NoExtension, EPP_XMLNS},
request::{Command, Transaction}, request::{Command, Transaction},
}; };
use serde::Serialize;
impl<'a> Transaction<NoExtension> for DomainUpdate<'a> {} impl<'a> Transaction<NoExtension> for DomainUpdate<'a> {}
impl<'a> Command for DomainUpdate<'a> { impl<'a> Command for DomainUpdate<'a> {
@ -19,8 +19,7 @@ impl<'a> DomainUpdate<'a> {
pub fn new(name: &'a str) -> Self { pub fn new(name: &'a str) -> Self {
Self { Self {
domain: DomainUpdateRequestData { domain: DomainUpdateRequestData {
xmlns: XMLNS, name,
name: name.into(),
add: None, add: None,
remove: None, remove: None,
change_info: None, change_info: None,
@ -34,75 +33,82 @@ impl<'a> DomainUpdate<'a> {
} }
/// Sets the data for the &lt;add&gt; tag /// Sets the data for the &lt;add&gt; tag
pub fn add(&mut self, add: DomainAddRemove<'a>) { pub fn add(&mut self, add: DomainAdd<'a>) {
self.domain.add = Some(add); self.domain.add = Some(add);
} }
/// Sets the data for the &lt;rem&gt; tag /// Sets the data for the &lt;rem&gt; tag
pub fn remove(&mut self, remove: DomainAddRemove<'a>) { pub fn remove(&mut self, remove: DomainRemove<'a>) {
self.domain.remove = Some(remove); self.domain.remove = Some(remove);
} }
} }
/// Type for elements under the &lt;chg&gt; tag for domain update /// Type for elements under the &lt;chg&gt; tag for domain update
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "chg", ns(XMLNS))]
pub struct DomainChangeInfo<'a> { pub struct DomainChangeInfo<'a> {
/// The new registrant contact for the domain /// The new registrant contact for the domain
#[serde(rename = "domain:registrant")] pub registrant: Option<&'a str>,
pub registrant: Option<StringValue<'a>>,
/// The new auth info for the domain /// The new auth info for the domain
#[serde(rename = "domain:authInfo")]
pub auth_info: Option<DomainAuthInfo<'a>>, pub auth_info: Option<DomainAuthInfo<'a>>,
} }
/// Type for elements under the &lt;add&gt; and &lt;rem&gt; tags for domain update /// Type for elements under the &lt;add&gt; and &lt;rem&gt; tags for domain update
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct DomainAddRemove<'a> { #[xml(rename = "add", ns(XMLNS))]
pub struct DomainAdd<'a> {
/// The list of nameservers to add or remove /// The list of nameservers to add or remove
/// Type T can be either a `HostObjList` or `HostAttrList` /// Type T can be either a `HostObjList` or `HostAttrList`
#[serde(rename = "domain:ns")] pub ns: Option<NameServers<'a>>,
pub ns: Option<HostList<'a>>,
/// The list of contacts to add to or remove from the domain /// The list of contacts to add to or remove from the domain
#[serde(rename = "domain:contact")]
pub contacts: Option<&'a [DomainContact<'a>]>, pub contacts: Option<&'a [DomainContact<'a>]>,
/// The list of statuses to add to or remove from the domain /// The list of statuses to add to or remove from the domain
#[serde(rename = "domain:status")] pub statuses: Option<&'a [Status<'a>]>,
pub statuses: Option<&'a [ObjectStatus<'a>]>, }
/// Type for elements under the &lt;add&gt; and &lt;rem&gt; tags for domain update
#[derive(Debug, ToXml)]
#[xml(rename = "rem", ns(XMLNS))]
pub struct DomainRemove<'a> {
/// The list of nameservers to add or remove
/// Type T can be either a `HostObjList` or `HostAttrList`
pub ns: Option<NameServers<'a>>,
/// The list of contacts to add to or remove from the domain
pub contacts: Option<&'a [DomainContact<'a>]>,
/// The list of statuses to add to or remove from the domain
pub statuses: Option<&'a [Status<'a>]>,
} }
/// Type for elements under the &lt;update&gt; tag for domain update /// Type for elements under the &lt;update&gt; tag for domain update
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "update", ns(XMLNS))]
pub struct DomainUpdateRequestData<'a> { pub struct DomainUpdateRequestData<'a> {
/// XML namespace for domain commands
#[serde(rename = "xmlns:domain")]
pub xmlns: &'a str,
/// The name of the domain to update /// The name of the domain to update
#[serde(rename = "domain:name")] pub name: &'a str,
pub name: StringValue<'a>,
/// `DomainAddRemove` Object containing the list of elements to be added /// `DomainAddRemove` Object containing the list of elements to be added
/// to the domain /// to the domain
#[serde(rename = "domain:add")] pub add: Option<DomainAdd<'a>>,
pub add: Option<DomainAddRemove<'a>>,
/// `DomainAddRemove` Object containing the list of elements to be removed /// `DomainAddRemove` Object containing the list of elements to be removed
/// from the domain /// from the domain
#[serde(rename = "domain:rem")] pub remove: Option<DomainRemove<'a>>,
pub remove: Option<DomainAddRemove<'a>>,
/// The data under the &lt;chg&gt; tag for domain update /// The data under the &lt;chg&gt; tag for domain update
#[serde(rename = "domain:chg")] #[xml(rename = "domain:chg")]
pub change_info: Option<DomainChangeInfo<'a>>, pub change_info: Option<DomainChangeInfo<'a>>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;update&gt; command for domains /// Type for EPP XML &lt;update&gt; command for domains
#[derive(Debug, ToXml)]
#[xml(rename = "update", ns(EPP_XMLNS))]
pub struct DomainUpdate<'a> { pub struct DomainUpdate<'a> {
#[serde(rename = "domain:update")]
pub domain: DomainUpdateRequestData<'a>, pub domain: DomainUpdateRequestData<'a>,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{DomainAddRemove, DomainAuthInfo, DomainChangeInfo, DomainContact, DomainUpdate}; use super::{
use crate::common::ObjectStatus; DomainAdd, DomainAuthInfo, DomainChangeInfo, DomainContact, DomainRemove, DomainUpdate,
};
use crate::domain::Status;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
@ -110,11 +116,11 @@ mod tests {
fn command() { fn command() {
let mut object = DomainUpdate::new("eppdev.com"); let mut object = DomainUpdate::new("eppdev.com");
let statuses = &[ObjectStatus { let statuses = &[Status {
status: "clientDeleteProhibited".into(), status: "clientDeleteProhibited".into(),
}]; }];
let add = DomainAddRemove { let add = DomainAdd {
ns: None, ns: None,
contacts: None, contacts: None,
statuses: Some(statuses), statuses: Some(statuses),
@ -125,7 +131,7 @@ mod tests {
id: "eppdev-contact-2".into(), id: "eppdev-contact-2".into(),
}]; }];
let remove = DomainAddRemove { let remove = DomainRemove {
ns: None, ns: None,
contacts: Some(contacts), contacts: Some(contacts),
statuses: None, statuses: None,
@ -147,8 +153,8 @@ mod tests {
let object = response_from_file::<DomainUpdate>("response/domain/update.xml"); let object = response_from_file::<DomainUpdate>("response/domain/update.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -28,10 +28,10 @@ impl Display for Error {
Error::Command(e) => { Error::Command(e) => {
write!(f, "command error: {}", e.result.message) write!(f, "command error: {}", e.result.message)
} }
Error::Io(e) => write!(f, "I/O error: {}", e), Error::Io(e) => write!(f, "I/O error: {e}"),
Error::Timeout => write!(f, "timeout"), Error::Timeout => write!(f, "timeout"),
Error::Xml(e) => write!(f, "(de)serialization error: {}", e), Error::Xml(e) => write!(f, "(de)serialization error: {e}"),
Error::Other(e) => write!(f, "error: {}", e), Error::Other(e) => write!(f, "error: {e}"),
} }
} }
} }

View File

@ -3,13 +3,13 @@
use std::fmt; use std::fmt;
use chrono::FixedOffset; use chrono::FixedOffset;
use serde::Serialize; use instant_xml::ToXml;
use crate::common::{NoExtension, StringValue}; use crate::common::NoExtension;
use crate::domain::update::DomainUpdate; use crate::domain::update::DomainUpdate;
use crate::request::{Extension, Transaction}; use crate::request::{Extension, Transaction};
use super::namestore::{NameStore, NameStoreData}; use super::namestore::NameStore;
pub const XMLNS: &str = "http://www.verisign.com/epp/sync-1.0"; pub const XMLNS: &str = "http://www.verisign.com/epp/sync-1.0";
@ -70,47 +70,35 @@ impl Update {
/// Create a new sync update request /// Create a new sync update request
pub fn new(expiration: GMonthDay) -> Self { pub fn new(expiration: GMonthDay) -> Self {
Self { Self {
data: UpdateData { exp: expiration.to_string(),
xmlns: XMLNS,
exp: expiration.to_string().into(),
},
} }
} }
} }
impl UpdateWithNameStore<'_> { impl<'a> UpdateWithNameStore<'a> {
/// Create a new sync update with namestore request /// Create a new sync update with namestore request
pub fn new(expiration: GMonthDay, subproduct: &str) -> Self { pub fn new(expiration: GMonthDay, subproduct: &'a str) -> Self {
Self { Self {
sync: Update::new(expiration).data, sync: Update::new(expiration),
namestore: NameStoreData::new(subproduct), namestore: NameStore::new(subproduct),
} }
} }
} }
#[derive(Debug, Serialize)] #[derive(Debug, ToXml)]
pub struct Update { #[xml(inline)]
#[serde(rename = "sync:update")]
pub data: UpdateData,
}
#[derive(Debug, Serialize)]
pub struct UpdateWithNameStore<'a> { pub struct UpdateWithNameStore<'a> {
#[serde(rename = "sync:update")] pub sync: Update,
pub sync: UpdateData, pub namestore: NameStore<'a>,
#[serde(rename = "namestoreExt:namestoreExt")]
pub namestore: NameStoreData<'a>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;consolidate&gt; extension /// Type for EPP XML &lt;consolidate&gt; extension
pub struct UpdateData { #[derive(Debug, ToXml)]
/// XML namespace for the consolidate extension #[xml(rename = "update", ns(XMLNS))]
#[serde(rename = "xmlns:sync")] pub struct Update {
pub xmlns: &'static str,
/// The expiry date of the domain /// The expiry date of the domain
#[serde(rename = "sync:expMonthDay")] #[xml(rename = "expMonthDay")]
pub exp: StringValue<'static>, pub exp: String,
} }
#[cfg(test)] #[cfg(test)]

View File

@ -2,10 +2,10 @@
//! //!
//! https://www.verisign.com/assets/epp-sdk/verisign_epp-extension_low-balance_v01.html //! https://www.verisign.com/assets/epp-sdk/verisign_epp-extension_low-balance_v01.html
use serde::Deserialize; use instant_xml::FromXml;
#[derive(Clone, Debug, Deserialize, PartialEq)] #[derive(Clone, Debug, FromXml, PartialEq)]
#[serde(rename_all = "camelCase")] #[xml(ns(XMLNS), rename = "pollData", rename_all = "camelCase")]
pub struct LowBalance { pub struct LowBalance {
pub registrar_name: String, pub registrar_name: String,
pub credit_limit: String, pub credit_limit: String,
@ -13,24 +13,28 @@ pub struct LowBalance {
pub available_credit: String, pub available_credit: String,
} }
#[derive(Clone, Debug, Deserialize, PartialEq)] #[derive(Clone, Debug, FromXml, PartialEq)]
#[xml(ns(XMLNS), rename = "creditThreshold")]
pub struct Threshold { pub struct Threshold {
#[xml(attribute)]
pub r#type: ThresholdType, pub r#type: ThresholdType,
#[serde(rename = "$value")] #[xml(direct)]
pub value: String, pub value: String,
} }
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] #[derive(Clone, Copy, Debug, FromXml, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[xml(scalar, rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ThresholdType { pub enum ThresholdType {
Fixed, Fixed,
Percent, Percent,
} }
const XMLNS: &str = "http://www.verisign.com/epp/lowbalance-poll-1.0";
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::message::poll::{MessageData, MessagePollResponse}; use crate::message::poll::MessagePollResponse;
use crate::message::MessagePoll; use crate::message::MessagePoll;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{response_from_file, CLTRID, SVTRID}; use crate::tests::{response_from_file, CLTRID, SVTRID};
@ -40,10 +44,8 @@ mod tests {
let object = response_from_file::<MessagePoll>("response/message/poll_low_balance.xml"); let object = response_from_file::<MessagePoll>("response/message/poll_low_balance.xml");
dbg!(&object); dbg!(&object);
let low_balance = match object.res_data { let low_balance = match object.res_data() {
Some(MessagePollResponse { Some(MessagePollResponse::LowBalance(low_balance)) => low_balance,
message_data: MessageData::LowBalance(low_balance),
}) => low_balance,
_ => panic!("Unexpected message data"), _ => panic!("Unexpected message data"),
}; };
@ -64,10 +66,10 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; ack to dequeue".into() "Command completed successfully; ack to dequeue"
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -2,10 +2,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use crate::{ use crate::{
common::StringValue,
contact::{ contact::{
check::ContactCheck, create::ContactCreate, delete::ContactDelete, info::ContactInfo, check::ContactCheck, create::ContactCreate, delete::ContactDelete, info::ContactInfo,
update::ContactUpdate, update::ContactUpdate,
@ -51,22 +50,9 @@ impl Transaction<NameStore<'_>> for HostUpdate<'_> {}
impl<'a> NameStore<'a> { impl<'a> NameStore<'a> {
/// Create a new RGP restore report request /// Create a new RGP restore report request
pub fn new(subproduct: &str) -> NameStore { pub fn new(subproduct: &'a str) -> NameStore {
NameStore { NameStore {
data: NameStoreData { subproduct: subproduct.into(),
xmlns: XMLNS.into(),
subproduct: subproduct.to_owned().into(),
},
}
}
}
impl<'a> NameStoreData<'a> {
/// Create a new RGP restore report request
pub fn new(subproduct: &str) -> Self {
Self {
xmlns: XMLNS.into(),
subproduct: subproduct.to_owned().into(),
} }
} }
} }
@ -75,22 +61,13 @@ impl<'a> Extension for NameStore<'a> {
type Response = NameStore<'static>; type Response = NameStore<'static>;
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, FromXml, ToXml)]
#[serde(rename = "namestoreExt:namestoreExt")]
pub struct NameStore<'a> {
#[serde(rename = "namestoreExt:namestoreExt", alias = "namestoreExt")]
pub data: NameStoreData<'a>,
}
#[derive(Serialize, Deserialize, Debug)]
/// Type for EPP XML &lt;namestoreExt&gt; extension /// Type for EPP XML &lt;namestoreExt&gt; extension
pub struct NameStoreData<'a> { #[xml(rename = "namestoreExt", ns(XMLNS))]
/// XML namespace for the RGP restore extension pub struct NameStore<'a> {
#[serde(rename = "xmlns:namestoreExt", alias = "xmlns")]
pub xmlns: Cow<'a, str>,
/// The object holding the list of domains to be checked /// The object holding the list of domains to be checked
#[serde(rename = "namestoreExt:subProduct", alias = "subProduct")] #[xml(rename = "subProduct")]
pub subproduct: StringValue<'a>, pub subproduct: Cow<'a, str>,
} }
#[cfg(test)] #[cfg(test)]
@ -118,7 +95,7 @@ mod tests {
let object = response_from_file_with_ext::<DomainCheck, NameStore>( let object = response_from_file_with_ext::<DomainCheck, NameStore>(
"response/extensions/namestore.xml", "response/extensions/namestore.xml",
); );
let ext = object.extension.unwrap(); let ext = object.extension().unwrap();
assert_eq!(ext.data.subproduct, "com".into()); assert_eq!(ext.subproduct, "com");
} }
} }

View File

@ -1,17 +1,4 @@
use serde::{Deserialize, Serialize};
pub mod report; pub mod report;
pub mod request; pub mod request;
pub const XMLNS: &str = "urn:ietf:params:xml:ns:rgp-1.0"; pub const XMLNS: &str = "urn:ietf:params:xml:ns:rgp-1.0";
#[derive(Debug, Deserialize, Serialize)]
pub struct Update<T> {
#[serde(
rename = "rgp:update",
alias = "update",
alias = "upData",
alias = "infData"
)]
pub data: T,
}

View File

@ -1,12 +1,13 @@
//! Types for EPP RGP restore report //! Types for EPP RGP restore report
use crate::common::{NoExtension, StringValue}; use chrono::{DateTime, SecondsFormat, Utc};
use instant_xml::ToXml;
use crate::common::NoExtension;
use crate::domain::update::DomainUpdate; use crate::domain::update::DomainUpdate;
use crate::request::{Extension, Transaction}; use crate::request::{Extension, Transaction};
use chrono::{DateTime, SecondsFormat, Utc};
use serde::Serialize;
use super::{Update, XMLNS}; use super::XMLNS;
impl<'a> Transaction<Update<RgpRestoreReport<'a>>> for DomainUpdate<'a> {} impl<'a> Transaction<Update<RgpRestoreReport<'a>>> for DomainUpdate<'a> {}
@ -18,28 +19,19 @@ impl<'a> RgpRestoreReport<'a> {
deleted_at: DateTime<Utc>, deleted_at: DateTime<Utc>,
restored_at: DateTime<Utc>, restored_at: DateTime<Utc>,
restore_reason: &'a str, restore_reason: &'a str,
statements: &[&'a str], statements: &'a [&'a str],
other: &'a str, other: &'a str,
) -> Self { ) -> Self {
let statements = statements.iter().map(|&s| s.into()).collect();
Self { Self {
xmlns: XMLNS, op: "report",
restore: RgpRestoreReportSection { report: RgpRestoreReportSectionData {
op: "report", pre_data,
report: RgpRestoreReportSectionData { post_data,
pre_data: pre_data.into(), deleted_at: deleted_at.to_rfc3339_opts(SecondsFormat::AutoSi, true),
post_data: post_data.into(), restored_at: restored_at.to_rfc3339_opts(SecondsFormat::AutoSi, true),
deleted_at: deleted_at restore_reason,
.to_rfc3339_opts(SecondsFormat::AutoSi, true) statements,
.into(), other,
restored_at: restored_at
.to_rfc3339_opts(SecondsFormat::AutoSi, true)
.into(),
restore_reason: restore_reason.into(),
statements,
other: other.into(),
},
}, },
} }
} }
@ -49,53 +41,51 @@ impl<'a> Extension for Update<RgpRestoreReport<'a>> {
type Response = NoExtension; type Response = NoExtension;
} }
#[derive(Debug, ToXml)]
#[xml(rename = "update", ns(XMLNS))]
pub struct Update<T> {
pub data: T,
}
/// Type corresponding to the &lt;report&gt; section in the EPP rgp restore extension /// Type corresponding to the &lt;report&gt; section in the EPP rgp restore extension
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "report", ns(XMLNS))]
pub struct RgpRestoreReportSectionData<'a> { pub struct RgpRestoreReportSectionData<'a> {
/// The pre-delete registration date /// The pre-delete registration date
#[serde(rename = "rgp:preData")] #[xml(rename = "preData")]
pre_data: StringValue<'a>, pre_data: &'a str,
/// The post-delete registration date /// The post-delete registration date
#[serde(rename = "rgp:postData")] #[xml(rename = "postData")]
post_data: StringValue<'a>, post_data: &'a str,
/// The domain deletion date /// The domain deletion date
#[serde(rename = "rgp:delTime")] #[xml(rename = "delTime")]
deleted_at: StringValue<'a>, deleted_at: String,
/// The domain restore request date /// The domain restore request date
#[serde(rename = "rgp:resTime")] #[xml(rename = "resTime")]
restored_at: StringValue<'a>, restored_at: String,
/// The reason for domain restoration /// The reason for domain restoration
#[serde(rename = "rgp:resReason")] #[xml(rename = "resReason")]
restore_reason: StringValue<'a>, restore_reason: &'a str,
/// The registrar's statements on the domain restoration /// The registrar's statements on the domain restoration
#[serde(rename = "rgp:statement")] #[xml(rename = "statement")]
statements: Vec<StringValue<'a>>, statements: &'a [&'a str],
/// Other remarks for domain restoration /// Other remarks for domain restoration
#[serde(rename = "rgp:other")] #[xml(rename = "other")]
other: StringValue<'a>, other: &'a str,
} }
/// Type corresponding to the &lt;restore&gt; section in the rgp restore extension #[derive(Debug, ToXml)]
#[derive(Serialize, Debug)] /// Type for EPP XML &lt;check&gt; command for domains
pub struct RgpRestoreReportSection<'a> { #[xml(rename = "restore", ns(XMLNS))]
pub struct RgpRestoreReport<'a> {
/// The value of the op attribute for the &lt;restore&gt; tag /// The value of the op attribute for the &lt;restore&gt; tag
#[xml(attribute)]
op: &'a str, op: &'a str,
/// Data for the &lt;report&gt; tag /// Data for the &lt;report&gt; tag
#[serde(rename = "rgp:report")] #[xml(rename = "rgp:report")]
report: RgpRestoreReportSectionData<'a>, report: RgpRestoreReportSectionData<'a>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;check&gt; command for domains
pub struct RgpRestoreReport<'a> {
/// XML namespace for the RGP restore extension
#[serde(rename = "xmlns:rgp")]
xmlns: &'a str,
/// The object holding the list of domains to be checked
#[serde(rename = "rgp:restore")]
restore: RgpRestoreReportSection<'a>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;

View File

@ -1,75 +1,86 @@
//! Types for EPP RGP restore request //! Types for EPP RGP restore request
use instant_xml::{FromXml, ToXml};
use crate::{ use crate::{
domain::{info::DomainInfo, update::DomainUpdate}, domain::{info::DomainInfo, update::DomainUpdate},
request::{Extension, Transaction}, request::{Extension, Transaction},
}; };
use serde::{Deserialize, Serialize}; use super::XMLNS;
use super::{Update, XMLNS};
impl<'a> Transaction<Update<RgpRestoreRequest<'a>>> for DomainUpdate<'a> {} impl<'a> Transaction<Update<RgpRestoreRequest<'a>>> for DomainUpdate<'a> {}
impl<'a> Transaction<Update<RgpRestoreRequest<'a>>> for DomainInfo<'a> {} impl<'a> Transaction<Update<RgpRestoreRequest<'a>>> for DomainInfo<'a> {}
impl<'a> Extension for Update<RgpRestoreRequest<'a>> { impl<'a> Extension for Update<RgpRestoreRequest<'a>> {
type Response = Update<RgpRequestResponse>; type Response = RgpRequestResponse;
} }
// Request // Request
/// Type corresponding to the &lt;restore&gt; tag for an rgp restore request #[derive(Debug, FromXml, ToXml)]
#[derive(Serialize, Debug)] #[xml(rename = "update", ns(XMLNS))]
pub struct RgpRestoreRequestData<'a> { pub struct Update<T> {
/// The value of the op attribute in the &lt;restore&gt; tag pub data: T,
pub op: &'a str,
} }
#[derive(Serialize, Debug)] /// Type corresponding to the &lt;restore&gt; tag for an rgp restore request
/// Type for EPP XML &lt;check&gt; command for domains #[derive(Debug, ToXml)]
#[xml(rename = "restore", ns(XMLNS))]
pub struct RgpRestoreRequest<'a> { pub struct RgpRestoreRequest<'a> {
/// XML namespace for the RGP restore extension /// The value of the op attribute in the &lt;restore&gt; tag
#[serde(rename = "xmlns:rgp")] #[xml(attribute)]
xmlns: &'a str, pub op: &'a str,
/// The object holding the list of domains to be checked
#[serde(rename = "rgp:restore")]
restore: RgpRestoreRequestData<'a>,
} }
impl Default for RgpRestoreRequest<'static> { impl Default for RgpRestoreRequest<'static> {
fn default() -> Self { fn default() -> Self {
Self { Self { op: "request" }
xmlns: XMLNS,
restore: RgpRestoreRequestData { op: "request" },
}
} }
} }
// Response // Response
/// Type that represents the &lt;rgpStatus&gt; tag for domain rgp restore request response /// Type that represents the &lt;rgpStatus&gt; tag for domain rgp restore request response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[xml(rename = "rgpStatus", ns(XMLNS))]
pub struct RgpStatus { pub struct RgpStatus {
/// The domain RGP status /// The domain RGP status
#[serde(rename = "s")] #[xml(rename = "s", attribute)]
pub status: String, pub status: String,
} }
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[serde(rename = "upData")] #[xml(rename = "upData", ns(XMLNS))]
/// Type that represents the &lt;resData&gt; tag for domain transfer response /// Type that represents the &lt;resData&gt; tag for domain transfer response
pub struct RgpRequestResponse { pub struct RgpRequestUpdateResponse {
/// Data under the &lt;rgpStatus&gt; tag /// Data under the &lt;rgpStatus&gt; tag
#[serde(rename = "rgpStatus")]
pub rgp_status: Vec<RgpStatus>, pub rgp_status: Vec<RgpStatus>,
} }
#[derive(Debug, FromXml)]
#[xml(rename = "infData", ns(XMLNS))]
/// Type that represents the &lt;resData&gt; tag for domain transfer response
pub struct RgpRequestInfoResponse {
/// Data under the &lt;rgpStatus&gt; tag
pub rgp_status: Vec<RgpStatus>,
}
/// Type that represents the &lt;resData&gt; tag for domain transfer response
#[derive(Debug, FromXml)]
#[xml(forward)]
pub enum RgpRequestResponse {
Update(RgpRequestUpdateResponse),
Info(RgpRequestInfoResponse),
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{RgpRestoreRequest, Update}; use super::{RgpRestoreRequest, Update};
use crate::domain::info::DomainInfo; use crate::domain::info::DomainInfo;
use crate::domain::update::{DomainChangeInfo, DomainUpdate}; use crate::domain::update::{DomainChangeInfo, DomainUpdate};
use crate::extensions::rgp::request::RgpRequestResponse;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file_with_ext, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file_with_ext, SUCCESS_MSG, SVTRID};
@ -102,9 +113,15 @@ mod tests {
let ext = object.extension.unwrap(); let ext = object.extension.unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(ext.data.rgp_status[0].status, "pendingRestore".to_string());
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); let data = match ext.data {
RgpRequestResponse::Update(data) => data,
_ => panic!("Unexpected response type"),
};
assert_eq!(data.rgp_status[0].status, "pendingRestore".to_string());
assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -114,7 +131,12 @@ mod tests {
); );
let ext = object.extension.unwrap(); let ext = object.extension.unwrap();
assert_eq!(ext.data.rgp_status[0].status, "addPeriod"); let data = match ext.data {
assert_eq!(ext.data.rgp_status[1].status, "renewPeriod"); RgpRequestResponse::Info(data) => data,
_ => panic!("Unexpected response type"),
};
assert_eq!(data.rgp_status[0].status, "addPeriod");
assert_eq!(data.rgp_status[1].status, "renewPeriod");
} }
} }

View File

@ -1,30 +1,15 @@
use std::fmt::Debug; use std::fmt::Debug;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize}; use instant_xml::{Deserializer, FromXml, ToXml};
use crate::common::{Options, ServiceExtension, Services, StringValue, EPP_XMLNS}; use crate::common::{Options, ServiceExtension, Services, EPP_XMLNS};
// Request // Request
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, ToXml)]
struct Hello; #[xml(rename = "hello", ns(EPP_XMLNS))]
pub(crate) struct Hello;
#[derive(Debug, PartialEq, Serialize)]
#[serde(rename = "epp")]
pub struct HelloDocument {
xmlns: &'static str,
hello: Hello,
}
impl Default for HelloDocument {
fn default() -> Self {
Self {
xmlns: EPP_XMLNS,
hello: Hello,
}
}
}
// Response // Response
@ -36,193 +21,235 @@ pub struct ServiceMenu {
} }
/// Simplified service menu type for deserialization to `ServiceMenu` type from EPP greeting XML /// Simplified service menu type for deserialization to `ServiceMenu` type from EPP greeting XML
#[derive(Deserialize, Debug, PartialEq)] #[derive(Debug, FromXml, PartialEq)]
#[xml(ns(EPP_XMLNS), rename = "svcMenu")]
struct FlattenedServiceMenu { struct FlattenedServiceMenu {
pub version: StringValue<'static>, pub version: String,
pub lang: StringValue<'static>, pub lang: String,
#[serde(rename = "objURI")] #[xml(rename = "objURI")]
pub obj_uris: Vec<StringValue<'static>>, pub obj_uris: Vec<String>,
#[serde(rename = "svcExtension")] #[xml(rename = "svcExtension")]
pub svc_ext: Option<ServiceExtension<'static>>, pub svc_ext: Option<ServiceExtension<'static>>,
} }
impl<'a, 'de: 'a> Deserialize<'de> for ServiceMenu { impl<'xml> FromXml<'xml> for ServiceMenu {
/// Deserializes the <svcMenu> data to the `ServiceMenu` type fn matches(id: instant_xml::Id<'_>, field: Option<instant_xml::Id<'_>>) -> bool {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> FlattenedServiceMenu::matches(id, field)
where }
D: Deserializer<'de>,
{
let flattened_svc_menu = FlattenedServiceMenu::deserialize(deserializer)?;
let svc_menu = ServiceMenu { /// Deserializes the <svcMenu> data to the `ServiceMenu` type
options: Options { fn deserialize<'cx>(
version: flattened_svc_menu.version, into: &mut Self::Accumulator,
lang: flattened_svc_menu.lang, field: &'static str,
}, deserializer: &mut Deserializer<'cx, 'xml>,
services: Services { ) -> Result<(), instant_xml::Error> {
obj_uris: flattened_svc_menu.obj_uris, dbg!(&into);
svc_ext: flattened_svc_menu.svc_ext,
}, let mut value = None;
FlattenedServiceMenu::deserialize(&mut value, field, deserializer)?;
let flattened = match value {
Some(value) => value,
None => return Ok(()),
}; };
Ok(svc_menu) *into = Some(ServiceMenu {
options: Options {
version: flattened.version.into(),
lang: flattened.lang.into(),
},
services: Services {
obj_uris: flattened.obj_uris.into_iter().map(|s| s.into()).collect(),
svc_ext: flattened.svc_ext,
},
});
Ok(())
} }
type Accumulator = Option<Self>;
const KIND: instant_xml::Kind = FlattenedServiceMenu::KIND;
} }
/// Type corresponding to <all> in the EPP greeting XML /// Type corresponding to <all> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "all", ns(EPP_XMLNS))]
pub struct All; pub struct All;
/// Type corresponding to <none> in the EPP greeting XML /// Type corresponding to <none> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "noAccess", ns(EPP_XMLNS))]
pub struct NoAccess; pub struct NoAccess;
/// Type corresponding to <null> in the EPP greeting XML /// Type corresponding to <null> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "null", ns(EPP_XMLNS))]
pub struct Null; pub struct Null;
/// Type corresponding to <personal> in the EPP greeting XML /// Type corresponding to <personal> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "personal", ns(EPP_XMLNS))]
pub struct Personal; pub struct Personal;
/// Type corresponding to <personalAndOther> in the EPP greeting XML /// Type corresponding to <personalAndOther> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "personalAndOther", ns(EPP_XMLNS))]
pub struct PersonalAndOther; pub struct PersonalAndOther;
/// Type corresponding to <other> in the EPP greeting XML /// Type corresponding to <other> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "other", ns(EPP_XMLNS))]
pub struct Other; pub struct Other;
/// Type corresponding to possible <retention> type values /// Type corresponding to possible <retention> type values
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum AccessType { pub enum AccessType {
/// Data for the <all> tag /// Data for the <all> tag
#[serde(rename = "all")]
All(All), All(All),
/// Data for the <none> tag /// Data for the <none> tag
#[serde(rename = "none")]
NoAccess(NoAccess), NoAccess(NoAccess),
/// Data for the <null> tag /// Data for the <null> tag
#[serde(rename = "null")]
Null(Null), Null(Null),
/// Data for the <personal> tag /// Data for the <personal> tag
#[serde(rename = "personal")]
Personal(Personal), Personal(Personal),
/// Data for the <personalAndOther> tag /// Data for the <personalAndOther> tag
#[serde(rename = "personalAndOther")]
PersonalAndOther(PersonalAndOther), PersonalAndOther(PersonalAndOther),
/// Data for the <other> tag /// Data for the <other> tag
#[serde(rename = "other")]
Other(Other), Other(Other),
} }
/// Type corresponding to <access> in the EPP greeting XML #[derive(Debug, Eq, FromXml, PartialEq)]
#[derive(Deserialize, Debug, Eq, PartialEq)] #[xml(rename = "access", ns(EPP_XMLNS))]
pub struct Access { pub struct Access {
#[serde(flatten)] inner: AccessType,
pub ty: AccessType,
} }
/// Type corresponding to possible <purpose> type values /// Type corresponding to possible <purpose> type values
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum PurposeType { pub enum PurposeType {
/// Data for the <admin> tag /// Data for the <admin> tag
#[serde(rename = "admin")] Admin(Admin),
Admin,
/// Data for the <contact> tag /// Data for the <contact> tag
#[serde(rename = "contact")] Contact(Contact),
Contact,
/// Data for the <prov> tag /// Data for the <prov> tag
#[serde(rename = "prov")] Prov(Prov),
Prov,
/// Data for the <other> tag /// Data for the <other> tag
#[serde(rename = "other")] OtherPurpose(OtherPurpose),
OtherPurpose,
} }
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "admin", ns(EPP_XMLNS))]
pub struct Admin;
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "contact", ns(EPP_XMLNS))]
pub struct Contact;
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "prov", ns(EPP_XMLNS))]
pub struct Prov;
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "otherPurpose", ns(EPP_XMLNS))]
pub struct OtherPurpose;
/// Type corresponding to <purpose> in the EPP greeting XML /// Type corresponding to <purpose> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "purpose", ns(EPP_XMLNS))]
pub struct Purpose { pub struct Purpose {
#[serde(rename = "$value")]
pub purpose: Vec<PurposeType>, pub purpose: Vec<PurposeType>,
} }
/// Type corresponding to possible <purpose> type values /// Type corresponding to possible <purpose> type values
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum RecipientType { pub enum RecipientType {
/// Data for the <other> tag /// Data for the <other> tag
#[serde(rename = "other")] Other(Other),
Other,
/// Data for the <ours> tag /// Data for the <ours> tag
#[serde(rename = "ours")] Ours(Ours),
Ours,
/// Data for the <public> tag /// Data for the <public> tag
#[serde(rename = "public")] Public(Public),
Public,
/// Data for the <same> tag /// Data for the <same> tag
#[serde(rename = "same")] Same(Same),
Same,
/// Data for the <unrelated> tag /// Data for the <unrelated> tag
#[serde(rename = "unrelated")] Unrelated(Unrelated),
Unrelated,
} }
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "ours", ns(EPP_XMLNS))]
pub struct Ours;
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "public", ns(EPP_XMLNS))]
pub struct Public;
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "unrelated", ns(EPP_XMLNS))]
pub struct Unrelated;
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "same", ns(EPP_XMLNS))]
pub struct Same;
/// Type corresponding to <recipeint> in the EPP greeting XML /// Type corresponding to <recipeint> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "recipient", ns(EPP_XMLNS))]
pub struct Recipient { pub struct Recipient {
#[serde(rename = "$value")]
pub recipient: Vec<RecipientType>, pub recipient: Vec<RecipientType>,
} }
/// Type corresponding to <business> in the EPP greeting XML /// Type corresponding to <business> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "business", ns(EPP_XMLNS))]
pub struct Business; pub struct Business;
/// Type corresponding to <indefinite> in the EPP greeting XML /// Type corresponding to <indefinite> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "indefinite", ns(EPP_XMLNS))]
pub struct Indefinite; pub struct Indefinite;
/// Type corresponding to <legal> in the EPP greeting XML /// Type corresponding to <legal> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "legal", ns(EPP_XMLNS))]
pub struct Legal; pub struct Legal;
/// Type corresponding to <none> in the EPP greeting XML /// Type corresponding to <none> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "none", ns(EPP_XMLNS))]
pub struct No; pub struct No;
/// Type corresponding to <stated> in the EPP greeting XML /// Type corresponding to <stated> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "stated", ns(EPP_XMLNS))]
pub struct Stated; pub struct Stated;
/// Type corresponding to possible <retention> type values /// Type corresponding to possible <retention> type values
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward, rename = "retention", ns(EPP_XMLNS))]
pub enum RetentionType { pub enum RetentionType {
/// Data for the <business> tag /// Data for the <business> tag
#[serde(rename = "business")]
Business(Business), Business(Business),
/// Data for the <indefinite> tag /// Data for the <indefinite> tag
#[serde(rename = "indefinite")]
Indefinite(Indefinite), Indefinite(Indefinite),
/// Data for the <legal> tag /// Data for the <legal> tag
#[serde(rename = "legal")]
Legal(Legal), Legal(Legal),
/// Data for the <none> tag /// Data for the <none> tag
#[serde(rename = "none")] None(No),
No(No),
/// Data for the <stated> tag /// Data for the <stated> tag
#[serde(rename = "stated")]
Stated(Stated), Stated(Stated),
} }
/// Type corresponding to <retention> in the EPP greeting XML #[derive(Debug, Eq, FromXml, PartialEq)]
#[derive(Deserialize, Debug, Eq, PartialEq)] #[xml(rename = "retention", ns(EPP_XMLNS))]
pub struct Retention { pub struct Retention {
#[serde(flatten)] inner: RetentionType,
pub ty: RetentionType,
} }
/// Type corresponding to <statement> in the EPP greeting XML (pending more compliant implementation) /// Type corresponding to <statement> in the EPP greeting XML (pending more compliant implementation)
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "statement", ns(EPP_XMLNS))]
pub struct Statement { pub struct Statement {
/// Data for the <purpose> tag /// Data for the <purpose> tag
pub purpose: Purpose, pub purpose: Purpose,
@ -233,39 +260,35 @@ pub struct Statement {
} }
/// Type corresponding to <absolute> value in the EPP greeting XML /// Type corresponding to <absolute> value in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
pub struct Absolute { #[xml(rename = "absolute", ns(EPP_XMLNS))]
#[serde(rename = "$value")] pub struct Absolute(String);
pub absolute: StringValue<'static>,
}
/// Type corresponding to <relative> value in the EPP greeting XML /// Type corresponding to <relative> value in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
pub struct Relative { #[xml(rename = "relative", ns(EPP_XMLNS))]
#[serde(rename = "$value")] pub struct Relative(String);
pub relative: StringValue<'static>,
}
/// Type corresponding to possible <expiry> type values /// Type corresponding to possible <expiry> type values
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum ExpiryType { pub enum ExpiryType {
/// Data for the <absolute> tag /// Data for the <absolute> tag
#[serde(rename = "absolute")]
Absolute(Absolute), Absolute(Absolute),
/// Data for the <relative> tag /// Data for the <relative> tag
#[serde(rename = "relative")]
Relative(Relative), Relative(Relative),
} }
/// Type corresponding to <expiry> in the EPP greeting XML /// Type corresponding to possible <expiry> type values
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "expiry", ns(EPP_XMLNS))]
pub struct Expiry { pub struct Expiry {
#[serde(flatten)] inner: ExpiryType,
pub ty: ExpiryType,
} }
/// Type corresponding to <dcp> in the EPP greeting XML /// Type corresponding to <dcp> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "dcp", ns(EPP_XMLNS))]
pub struct Dcp { pub struct Dcp {
/// Data for the <access> tag /// Data for the <access> tag
pub access: Access, pub access: Access,
@ -275,42 +298,34 @@ pub struct Dcp {
pub expiry: Option<Expiry>, pub expiry: Option<Expiry>,
} }
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
/// Type corresponding to the <greeting> tag in the EPP greeting XML /// Type corresponding to the <greeting> tag in the EPP greeting XML
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(ns(EPP_XMLNS), rename = "greeting", rename_all = "lowercase")]
pub struct Greeting { pub struct Greeting {
/// The service ID /// The service ID
#[serde(rename = "svID")] #[xml(rename = "svID")]
pub service_id: String, pub service_id: String,
/// The date from the EPP server /// The date from the EPP server
#[serde(rename = "svDate")] #[xml(rename = "svDate")]
pub service_date: DateTime<Utc>, pub service_date: DateTime<Utc>,
/// Data under the <svcMenu> element /// Data under the <svcMenu> element
#[serde(rename = "svcMenu")]
pub svc_menu: ServiceMenu, pub svc_menu: ServiceMenu,
/// Data under the <dcp> element /// Data under the <dcp> element
pub dcp: Dcp, pub dcp: Dcp,
} }
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[serde(rename = "epp")]
pub struct GreetingDocument {
#[serde(rename = "greeting")]
pub data: Greeting,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use super::{ExpiryType, GreetingDocument, HelloDocument, Relative}; use super::{ExpiryType, Greeting, Hello, Relative};
use crate::tests::get_xml; use crate::tests::get_xml;
use crate::xml; use crate::xml;
#[test] #[test]
fn hello() { fn hello() {
let xml = get_xml("request/hello.xml").unwrap(); let xml = get_xml("request/hello.xml").unwrap();
let serialized = xml::serialize(&HelloDocument::default()).unwrap(); let serialized = xml::serialize(Hello).unwrap();
assert_eq!(xml, serialized); assert_eq!(xml, serialized);
} }
@ -318,19 +333,18 @@ mod tests {
#[test] #[test]
fn greeting() { fn greeting() {
let xml = get_xml("response/greeting.xml").unwrap(); let xml = get_xml("response/greeting.xml").unwrap();
let object = xml::deserialize::<GreetingDocument>(xml.as_str()).unwrap(); let object = xml::deserialize::<Greeting>(xml.as_str()).unwrap();
assert_eq!(object.data.service_id, "ISPAPI EPP Server"); assert_eq!(object.service_id, "ISPAPI EPP Server");
assert_eq!( assert_eq!(
object.data.service_date, object.service_date,
Utc.with_ymd_and_hms(2021, 7, 25, 14, 51, 17).unwrap() Utc.with_ymd_and_hms(2021, 7, 25, 14, 51, 17).unwrap()
); );
assert_eq!(object.data.svc_menu.options.version, "1.0".into()); assert_eq!(object.svc_menu.options.version, "1.0");
assert_eq!(object.data.svc_menu.options.lang, "en".into()); assert_eq!(object.svc_menu.options.lang, "en");
assert_eq!(object.data.svc_menu.services.obj_uris.len(), 4); assert_eq!(object.svc_menu.services.obj_uris.len(), 4);
assert_eq!( assert_eq!(
object object
.data
.svc_menu .svc_menu
.services .services
.svc_ext .svc_ext
@ -340,12 +354,10 @@ mod tests {
.len(), .len(),
5 5
); );
assert_eq!(object.data.dcp.statement.len(), 2); assert_eq!(object.dcp.statement.len(), 2);
assert_eq!( assert_eq!(
object.data.dcp.expiry.unwrap().ty, object.dcp.expiry.unwrap().inner,
ExpiryType::Relative(Relative { ExpiryType::Relative(Relative("P1M".into()))
relative: "P1M".into()
})
); );
} }
} }

View File

@ -1,59 +1,74 @@
//! Types for EPP host check request //! Types for EPP host check request
use std::fmt::Debug; use std::fmt::{self, Debug};
use instant_xml::{FromXml, Serializer, ToXml};
use super::XMLNS; use super::XMLNS;
use crate::common::{CheckResponse, NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for HostCheck<'a> {} impl<'a> Transaction<NoExtension> for HostCheck<'a> {}
impl<'a> Command for HostCheck<'a> { impl<'a> Command for HostCheck<'a> {
type Response = CheckResponse; type Response = CheckData;
const COMMAND: &'static str = "check"; const COMMAND: &'static str = "check";
} }
// Request // Request
/// Type for data under the host &lt;check&gt; tag /// Type for data under the host &lt;check&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
struct HostList<'a> { #[xml(rename = "check", ns(XMLNS))]
/// XML namespace for host commands struct HostCheckData<'a> {
#[serde(rename = "xmlns:host")]
xmlns: &'a str,
/// List of hosts to be checked for availability /// List of hosts to be checked for availability
#[serde(rename = "host:name")] name: &'a [&'a str],
hosts: Vec<StringValue<'a>>,
} }
#[derive(Serialize, Debug)] fn serialize_hosts<W: fmt::Write + ?Sized>(
/// Type for EPP XML &lt;check&gt; command for hosts hosts: &[&str],
struct SerializeHostCheck<'a> { serializer: &mut Serializer<W>,
/// The instance holding the list of hosts to be checked ) -> Result<(), instant_xml::Error> {
#[serde(rename = "host:check")] HostCheckData { name: hosts }.serialize(None, serializer)
list: HostList<'a>,
}
impl<'a> From<HostCheck<'a>> for SerializeHostCheck<'a> {
fn from(check: HostCheck<'a>) -> Self {
Self {
list: HostList {
xmlns: XMLNS,
hosts: check.hosts.iter().map(|&id| id.into()).collect(),
},
}
}
} }
/// The EPP `check` command for hosts /// The EPP `check` command for hosts
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, ToXml)]
#[serde(into = "SerializeHostCheck")] #[xml(rename = "check", ns(EPP_XMLNS))]
pub struct HostCheck<'a> { pub struct HostCheck<'a> {
/// The list of hosts to be checked /// The list of hosts to be checked
#[xml(serialize_with = "serialize_hosts")]
pub hosts: &'a [&'a str], pub hosts: &'a [&'a str],
} }
// Response
#[derive(Debug, FromXml)]
#[xml(rename = "name", ns(XMLNS))]
pub struct Checked {
#[xml(attribute, rename = "avail")]
pub available: bool,
#[xml(attribute)]
pub reason: Option<String>,
#[xml(direct)]
pub id: String,
}
#[derive(Debug, FromXml)]
#[xml(rename = "cd", ns(XMLNS))]
pub struct CheckedHost {
/// Data under the &lt;cd&gt; tag
#[xml(rename = "cd")]
pub inner: Checked,
}
/// Type that represents the &lt;chkData&gt; tag for host check response
#[derive(Debug, FromXml)]
#[xml(rename = "chkData", ns(XMLNS))]
pub struct CheckData {
pub list: Vec<CheckedHost>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::HostCheck; use super::HostCheck;
@ -74,12 +89,12 @@ mod tests {
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.list[0].id, "host1.eppdev-1.com"); assert_eq!(result.list[0].inner.id, "host1.eppdev-1.com");
assert!(result.list[0].available); assert!(result.list[0].inner.available);
assert_eq!(result.list[1].id, "ns1.testing.com"); assert_eq!(result.list[1].inner.id, "ns1.testing.com");
assert!(!result.list[1].available); assert!(!result.list[1].inner.available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -3,25 +3,24 @@
use std::net::IpAddr; use std::net::IpAddr;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::XMLNS; use super::{serialize_host_addrs_option, XMLNS};
use crate::common::{serialize_host_addrs_option, NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for HostCreate<'a> {} impl<'a> Transaction<NoExtension> for HostCreate<'a> {}
impl<'a> Command for HostCreate<'a> { impl<'a> Command for HostCreate<'a> {
type Response = HostCreateResponse; type Response = HostCreateData;
const COMMAND: &'static str = "create"; const COMMAND: &'static str = "create";
} }
impl<'a> HostCreate<'a> { impl<'a> HostCreate<'a> {
pub fn new(host: &'a str, addresses: Option<&'a [IpAddr]>) -> Self { pub fn new(name: &'a str, addresses: Option<&'a [IpAddr]>) -> Self {
Self { Self {
host: HostCreateRequestData { host: HostCreateRequest {
xmlns: XMLNS, name,
name: host.into(),
addresses, addresses,
}, },
} }
@ -31,47 +30,37 @@ impl<'a> HostCreate<'a> {
// Request // Request
/// Type for data under the host &lt;create&gt; tag /// Type for data under the host &lt;create&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct HostCreateRequestData<'a> { #[xml(rename = "create", ns(XMLNS))]
/// XML namespace for host commands pub struct HostCreateRequest<'a> {
#[serde(rename = "xmlns:host")]
xmlns: &'a str,
/// The name of the host to be created /// The name of the host to be created
#[serde(rename = "host:name")] pub name: &'a str,
pub name: StringValue<'a>,
/// The list of IP addresses for the host /// The list of IP addresses for the host
#[serde(rename = "host:addr", serialize_with = "serialize_host_addrs_option")] #[xml(serialize_with = "serialize_host_addrs_option")]
pub addresses: Option<&'a [IpAddr]>, pub addresses: Option<&'a [IpAddr]>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;create&gt; command for hosts /// Type for EPP XML &lt;create&gt; command for hosts
#[derive(Debug, ToXml)]
#[xml(rename = "create", ns(EPP_XMLNS))]
pub struct HostCreate<'a> { pub struct HostCreate<'a> {
/// The instance holding the data for the host to be created /// The instance holding the data for the host to be created
#[serde(rename = "host:create")] host: HostCreateRequest<'a>,
host: HostCreateRequestData<'a>,
} }
// Response // Response
/// Type that represents the &lt;creData&gt; tag for host create response /// Type that represents the &lt;creData&gt; tag for host create response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[xml(rename = "creData", ns(XMLNS))]
pub struct HostCreateData { pub struct HostCreateData {
/// The host name /// The host name
pub name: StringValue<'static>, pub name: String,
/// The host creation date /// The host creation date
#[serde(rename = "crDate")] #[xml(rename = "crDate")]
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
} }
/// Type that represents the &lt;resData&gt; tag for host check response
#[derive(Deserialize, Debug)]
pub struct HostCreateResponse {
/// Data under the &lt;creData&gt; tag
#[serde(rename = "creData")]
pub create_data: HostCreateData,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
@ -97,13 +86,13 @@ mod tests {
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.create_data.name, "host2.eppdev-1.com".into()); assert_eq!(result.name, "host2.eppdev-1.com");
assert_eq!( assert_eq!(
result.create_data.created_at, result.created_at,
Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).unwrap() Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).unwrap()
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,10 @@
//! Types for EPP host delete request //! Types for EPP host delete request
use instant_xml::ToXml;
use super::XMLNS; use super::XMLNS;
use crate::common::{NoExtension, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for HostDelete<'a> {} impl<'a> Transaction<NoExtension> for HostDelete<'a> {}
@ -15,31 +16,25 @@ impl<'a> Command for HostDelete<'a> {
impl<'a> HostDelete<'a> { impl<'a> HostDelete<'a> {
pub fn new(name: &'a str) -> Self { pub fn new(name: &'a str) -> Self {
Self { Self {
host: HostDeleteRequestData { host: HostDeleteRequest { name },
xmlns: XMLNS,
name: name.into(),
},
} }
} }
} }
/// Type for data under the host &lt;delete&gt; tag /// Type for data under the host &lt;delete&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct HostDeleteRequestData<'a> { #[xml(rename = "delete", ns(XMLNS))]
/// XML namespace for host commands pub struct HostDeleteRequest<'a> {
#[serde(rename = "xmlns:host")]
xmlns: &'a str,
/// The host to be deleted /// The host to be deleted
#[serde(rename = "host:name")] name: &'a str,
name: StringValue<'a>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;delete&gt; command for hosts /// Type for EPP XML &lt;delete&gt; command for hosts
#[derive(Debug, ToXml)]
#[xml(rename = "delete", ns(EPP_XMLNS))]
pub struct HostDelete<'a> { pub struct HostDelete<'a> {
/// The instance holding the data for the host to be deleted /// The instance holding the data for the host to be deleted
#[serde(rename = "host:delete")] host: HostDeleteRequest<'a>,
host: HostDeleteRequestData<'a>,
} }
#[cfg(test)] #[cfg(test)]
@ -58,8 +53,8 @@ mod tests {
fn response() { fn response() {
let object = response_from_file::<HostDelete>("response/host/delete.xml"); let object = response_from_file::<HostDelete>("response/host/delete.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -4,26 +4,23 @@ use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use super::XMLNS; use super::{HostAddr, Status, XMLNS};
use crate::common::{HostAddr, NoExtension, ObjectStatus, StringValue}; use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
impl<'a> Transaction<NoExtension> for HostInfo<'a> {} impl<'a> Transaction<NoExtension> for HostInfo<'a> {}
impl<'a> Command for HostInfo<'a> { impl<'a> Command for HostInfo<'a> {
type Response = HostInfoResponse; type Response = HostInfoResponseData;
const COMMAND: &'static str = "info"; const COMMAND: &'static str = "info";
} }
impl<'a> HostInfo<'a> { impl<'a> HostInfo<'a> {
pub fn new(name: &'a str) -> Self { pub fn new(name: &'a str) -> Self {
Self { Self {
info: HostInfoRequestData { info: HostInfoRequestData { name },
xmlns: XMLNS,
name: name.into(),
},
} }
} }
} }
@ -31,78 +28,91 @@ impl<'a> HostInfo<'a> {
// Request // Request
/// Type for data under the host &lt;info&gt; tag /// Type for data under the host &lt;info&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "info", ns(XMLNS))]
pub struct HostInfoRequestData<'a> { pub struct HostInfoRequestData<'a> {
/// XML namespace for host commands
#[serde(rename = "xmlns:host")]
xmlns: &'a str,
/// The name of the host to be queried /// The name of the host to be queried
#[serde(rename = "host:name")] name: &'a str,
name: StringValue<'a>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;info&gt; command for hosts /// Type for EPP XML &lt;info&gt; command for hosts
#[derive(Debug, ToXml)]
#[xml(rename = "info", ns(EPP_XMLNS))]
pub struct HostInfo<'a> { pub struct HostInfo<'a> {
/// The instance holding the data for the host query /// The instance holding the data for the host query
#[serde(rename = "host:info")] #[xml(rename = "host:info")]
info: HostInfoRequestData<'a>, info: HostInfoRequestData<'a>,
} }
// Response // Response
/// Type that represents the &lt;infData&gt; tag for host info response /// Type that represents the &lt;infData&gt; tag for host info response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[xml(rename = "infData", ns(XMLNS))]
pub struct HostInfoResponseData { pub struct HostInfoResponseData {
/// The host name /// The host name
pub name: StringValue<'static>, pub name: String,
/// The host ROID /// The host ROID
pub roid: StringValue<'static>, pub roid: String,
/// The list of host statuses /// The list of host statuses
#[serde(rename = "status")] #[xml(rename = "status")]
pub statuses: Vec<ObjectStatus<'static>>, pub statuses: Vec<Status<'static>>,
/// The list of host IP addresses /// The list of host IP addresses
#[serde(rename = "addr", deserialize_with = "deserialize_host_addrs")] #[xml(rename = "addr", deserialize_with = "deserialize_host_addrs")]
pub addresses: Vec<IpAddr>, pub addresses: Vec<IpAddr>,
/// The epp user to whom the host belongs /// The epp user to whom the host belongs
#[serde(rename = "clID")] #[xml(rename = "clID")]
pub client_id: StringValue<'static>, pub client_id: String,
/// THe epp user that created the host /// THe epp user that created the host
#[serde(rename = "crID")] #[xml(rename = "crID")]
pub creator_id: StringValue<'static>, pub creator_id: String,
/// The host creation date /// The host creation date
#[serde(rename = "crDate")] #[xml(rename = "crDate")]
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
/// The epp user that last updated the host /// The epp user that last updated the host
#[serde(rename = "upID")] #[xml(rename = "upID")]
pub updater_id: Option<StringValue<'static>>, pub updater_id: Option<String>,
/// The host last update date /// The host last update date
#[serde(rename = "upDate")] #[xml(rename = "upDate")]
pub updated_at: Option<DateTime<Utc>>, pub updated_at: Option<DateTime<Utc>>,
/// The host transfer date /// The host transfer date
#[serde(rename = "trDate")] #[xml(rename = "trDate")]
pub transferred_at: Option<DateTime<Utc>>, pub transferred_at: Option<DateTime<Utc>>,
} }
fn deserialize_host_addrs<'de, D>(de: D) -> Result<Vec<IpAddr>, D::Error> fn deserialize_host_addrs(
where into: &mut Vec<IpAddr>,
D: serde::de::Deserializer<'de>, field: &'static str,
{ deserializer: &mut instant_xml::de::Deserializer<'_, '_>,
let addrs = Vec::<HostAddr<'static>>::deserialize(de)?; ) -> Result<(), instant_xml::Error> {
addrs let mut addrs = Vec::new();
.into_iter() Vec::<HostAddr<'static>>::deserialize(&mut addrs, field, deserializer)?;
.map(|addr| IpAddr::from_str(&addr.address))
.collect::<Result<_, _>>() for addr in addrs {
.map_err(|e| serde::de::Error::custom(format!("{}", e))) match IpAddr::from_str(&addr.address) {
Ok(ip) => into.push(ip),
Err(_) => {
return Err(instant_xml::Error::UnexpectedValue(format!(
"invalid IP address '{}'",
&addr.address
)))
}
}
}
Ok(())
} }
/*
/// Type that represents the &lt;resData&gt; tag for host info response /// Type that represents the &lt;resData&gt; tag for host info response
#[derive(Deserialize, Debug)] #[derive(Debug, FromXml)]
#[xml(rename = "infData", ns(XMLNS))]
pub struct HostInfoResponse { pub struct HostInfoResponse {
/// Data under the &lt;infData&gt; tag /// Data under the &lt;infData&gt; tag
#[serde(rename = "infData")] #[xml(rename = "infData")]
pub info_data: HostInfoResponseData, pub info_data: HostInfoResponseData,
} }
*/
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -124,33 +134,27 @@ mod tests {
let result = object.res_data().unwrap(); let result = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.info_data.name, "host2.eppdev-1.com".into()); assert_eq!(result.name, "host2.eppdev-1.com");
assert_eq!(result.info_data.roid, "UNDEF-ROID".into()); assert_eq!(result.roid, "UNDEF-ROID");
assert_eq!(result.info_data.statuses[0].status, "ok".to_string()); assert_eq!(result.statuses[0].status, "ok".to_string());
assert_eq!(result.addresses[0], IpAddr::from([29, 245, 122, 14]));
assert_eq!( assert_eq!(
result.info_data.addresses[0], result.addresses[1],
IpAddr::from([29, 245, 122, 14])
);
assert_eq!(
result.info_data.addresses[1],
IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e]) IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e])
); );
assert_eq!(result.info_data.client_id, "eppdev".into()); assert_eq!(result.client_id, "eppdev");
assert_eq!(result.info_data.creator_id, "creator".into()); assert_eq!(result.creator_id, "creator");
assert_eq!( assert_eq!(
result.info_data.created_at, result.created_at,
Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).unwrap() Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).unwrap()
); );
assert_eq!(*(result.updater_id.as_ref().unwrap()), "creator");
assert_eq!( assert_eq!(
*(result.info_data.updater_id.as_ref().unwrap()), result.updated_at,
"creator".into()
);
assert_eq!(
result.info_data.updated_at,
Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).single() Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).single()
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -2,10 +2,11 @@
use std::net::IpAddr; use std::net::IpAddr;
use super::XMLNS; use instant_xml::ToXml;
use crate::common::{serialize_host_addrs_option, NoExtension, ObjectStatus, StringValue};
use super::{serialize_host_addrs_option, Status, XMLNS};
use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for HostUpdate<'a> {} impl<'a> Transaction<NoExtension> for HostUpdate<'a> {}
@ -17,9 +18,8 @@ impl<'a> Command for HostUpdate<'a> {
impl<'a> HostUpdate<'a> { impl<'a> HostUpdate<'a> {
pub fn new(name: &'a str) -> Self { pub fn new(name: &'a str) -> Self {
Self { Self {
host: HostUpdateRequestData { host: HostUpdateRequest {
xmlns: XMLNS, name,
name: name.into(),
add: None, add: None,
remove: None, remove: None,
change_info: None, change_info: None,
@ -33,68 +33,77 @@ impl<'a> HostUpdate<'a> {
} }
/// Sets the data for the &lt;add&gt; element of the host update /// Sets the data for the &lt;add&gt; element of the host update
pub fn add(&mut self, add: HostAddRemove<'a>) { pub fn add(&mut self, add: HostAdd<'a>) {
self.host.add = Some(add); self.host.add = Some(add);
} }
/// Sets the data for the &lt;rem&gt; element of the host update /// Sets the data for the &lt;rem&gt; element of the host update
pub fn remove(&mut self, remove: HostAddRemove<'a>) { pub fn remove(&mut self, remove: HostRemove<'a>) {
self.host.remove = Some(remove); self.host.remove = Some(remove);
} }
} }
/// Type for data under the &lt;chg&gt; tag /// Type for data under the &lt;chg&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
#[xml(rename = "chg", ns(XMLNS))]
pub struct HostChangeInfo<'a> { pub struct HostChangeInfo<'a> {
/// The new name for the host /// The new name for the host
#[serde(rename = "host:name")] pub name: &'a str,
pub name: StringValue<'a>,
} }
/// Type for data under the &lt;add&gt; and &lt;rem&gt; tags /// Type for data under the &lt;add&gt; and &lt;rem&gt; tags
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct HostAddRemove<'a> { #[xml(rename = "add", ns(XMLNS))]
pub struct HostAdd<'a> {
/// The IP addresses to be added to or removed from the host /// The IP addresses to be added to or removed from the host
#[serde(rename = "host:addr", serialize_with = "serialize_host_addrs_option")] #[xml(rename = "host:addr", serialize_with = "serialize_host_addrs_option")]
pub addresses: Option<&'a [IpAddr]>, pub addresses: Option<&'a [IpAddr]>,
/// The statuses to be added to or removed from the host /// The statuses to be added to or removed from the host
#[serde(rename = "host:status")] #[xml(rename = "host:status")]
pub statuses: Option<&'a [ObjectStatus<'a>]>, pub statuses: Option<&'a [Status<'a>]>,
}
/// Type for data under the &lt;add&gt; and &lt;rem&gt; tags
#[derive(Debug, ToXml)]
#[xml(rename = "rem", ns(XMLNS))]
pub struct HostRemove<'a> {
/// The IP addresses to be added to or removed from the host
#[xml(rename = "host:addr", serialize_with = "serialize_host_addrs_option")]
pub addresses: Option<&'a [IpAddr]>,
/// The statuses to be added to or removed from the host
#[xml(rename = "host:status")]
pub statuses: Option<&'a [Status<'a>]>,
} }
/// Type for data under the host &lt;update&gt; tag /// Type for data under the host &lt;update&gt; tag
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
pub struct HostUpdateRequestData<'a> { #[xml(rename = "update", ns(XMLNS))]
/// XML namespace for host commands pub struct HostUpdateRequest<'a> {
#[serde(rename = "xmlns:host")]
xmlns: &'a str,
/// The name of the host /// The name of the host
#[serde(rename = "host:name")] name: &'a str,
name: StringValue<'a>,
/// The IP addresses and statuses to be added to the host /// The IP addresses and statuses to be added to the host
#[serde(rename = "host:add")] #[xml(rename = "host:add")]
add: Option<HostAddRemove<'a>>, add: Option<HostAdd<'a>>,
/// The IP addresses and statuses to be removed from the host /// The IP addresses and statuses to be removed from the host
#[serde(rename = "host:rem")] #[xml(rename = "host:rem")]
remove: Option<HostAddRemove<'a>>, remove: Option<HostRemove<'a>>,
/// The host details that need to be updated /// The host details that need to be updated
#[serde(rename = "host:chg")] #[xml(rename = "host:chg")]
change_info: Option<HostChangeInfo<'a>>, change_info: Option<HostChangeInfo<'a>>,
} }
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;update&gt; command for hosts /// Type for EPP XML &lt;update&gt; command for hosts
#[derive(Debug, ToXml)]
#[xml(rename = "update", ns(EPP_XMLNS))]
pub struct HostUpdate<'a> { pub struct HostUpdate<'a> {
/// The instance holding the data for the host to be updated /// The instance holding the data for the host to be updated
#[serde(rename = "host:update")] host: HostUpdateRequest<'a>,
host: HostUpdateRequestData<'a>,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::IpAddr; use super::IpAddr;
use super::{HostAddRemove, HostChangeInfo, HostUpdate}; use super::{HostAdd, HostChangeInfo, HostRemove, HostUpdate, Status};
use crate::common::ObjectStatus;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
@ -104,16 +113,16 @@ mod tests {
0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e, 0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e,
])]; ])];
let add = HostAddRemove { let add = HostAdd {
addresses: Some(addr), addresses: Some(addr),
statuses: None, statuses: None,
}; };
let statuses = &[ObjectStatus { let statuses = &[Status {
status: "clientDeleteProhibited".into(), status: "clientDeleteProhibited".into(),
}]; }];
let remove = HostAddRemove { let remove = HostRemove {
addresses: None, addresses: None,
statuses: Some(statuses), statuses: Some(statuses),
}; };
@ -123,7 +132,7 @@ mod tests {
object.add(add); object.add(add);
object.remove(remove); object.remove(remove);
object.info(HostChangeInfo { object.info(HostChangeInfo {
name: "host2.eppdev-1.com".into(), name: "host2.eppdev-1.com",
}); });
assert_serialized("request/host/update.xml", &object); assert_serialized("request/host/update.xml", &object);
@ -134,8 +143,8 @@ mod tests {
let object = response_from_file::<HostUpdate>("response/host/update.xml"); let object = response_from_file::<HostUpdate>("response/host/update.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -60,7 +60,7 @@
//! use std::time::Duration; //! use std::time::Duration;
//! //!
//! use epp_client::EppClient; //! use epp_client::EppClient;
//! use epp_client::domain::DomainCheck; //! use epp_client::domain::check::DomainCheck;
//! use epp_client::login::Login; //! use epp_client::login::Login;
//! //!
//! #[tokio::main] //! #[tokio::main]
@ -79,9 +79,11 @@
//! let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] }; //! let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] };
//! let response = client.transact(&domain_check, "transaction-id").await.unwrap(); //! let response = client.transact(&domain_check, "transaction-id").await.unwrap();
//! //!
//! response.res_data.unwrap().list //! response.res_data()
//! .unwrap()
//! .list
//! .iter() //! .iter()
//! .for_each(|chk| println!("Domain: {}, Available: {}", chk.id, chk.available)); //! .for_each(|chk| println!("Domain: {}, Available: {}", chk.inner.id, chk.inner.available));
//! } //! }
//! ``` //! ```
//! //!
@ -113,6 +115,12 @@ pub mod extensions {
} }
pub mod host { pub mod host {
use std::borrow::Cow;
use std::fmt;
use std::net::IpAddr;
use instant_xml::{FromXml, Serializer, ToXml};
pub mod check; pub mod check;
pub use check::HostCheck; pub use check::HostCheck;
@ -129,6 +137,53 @@ pub mod host {
pub use update::HostUpdate; pub use update::HostUpdate;
pub const XMLNS: &str = "urn:ietf:params:xml:ns:host-1.0"; pub const XMLNS: &str = "urn:ietf:params:xml:ns:host-1.0";
/// The &lt;status&gt; type on contact transactions
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "status", ns(XMLNS))]
pub struct Status<'a> {
/// The status name, represented by the 's' attr on &lt;status&gt; tags
#[xml(attribute, rename = "s")]
pub status: Cow<'a, str>,
}
/// The &lt;hostAddr&gt; types domain or host transactions
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "addr", ns(XMLNS))]
pub(crate) struct HostAddr<'a> {
#[xml(attribute, rename = "ip")]
pub ip_version: Option<Cow<'a, str>>,
#[xml(direct)]
pub address: Cow<'a, str>,
}
impl From<&IpAddr> for HostAddr<'static> {
fn from(addr: &IpAddr) -> Self {
Self {
ip_version: Some(match addr {
IpAddr::V4(_) => "v4".into(),
IpAddr::V6(_) => "v6".into(),
}),
address: addr.to_string().into(),
}
}
}
pub(crate) fn serialize_host_addrs_option<T: AsRef<[IpAddr]>, W: fmt::Write + ?Sized>(
addrs: &Option<T>,
serializer: &mut Serializer<'_, W>,
) -> Result<(), instant_xml::Error> {
let addrs = match addrs {
Some(addrs) => addrs.as_ref(),
None => return Ok(()),
};
for addr in addrs {
HostAddr::from(addr).serialize(None, serializer)?;
}
Ok(())
}
} }
pub mod message { pub mod message {

View File

@ -1,31 +1,32 @@
use std::fmt::Debug; use std::fmt::Debug;
use serde::Serialize; use instant_xml::ToXml;
use crate::{ use crate::{
common::{NoExtension, Options, ServiceExtension, Services, StringValue}, common::{NoExtension, Options, ServiceExtension, Services, EPP_XMLNS},
contact, domain, host, contact, domain, host,
request::{Command, Transaction, EPP_LANG, EPP_VERSION}, request::{Command, Transaction, EPP_LANG, EPP_VERSION},
}; };
impl<'a> Transaction<NoExtension> for Login<'a> {} impl<'a> Transaction<NoExtension> for Login<'a> {}
#[derive(Serialize, Debug, Eq, PartialEq)]
/// Type corresponding to the &lt;login&gt; tag in an EPP XML login request /// Type corresponding to the &lt;login&gt; tag in an EPP XML login request
#[derive(Debug, Eq, PartialEq, ToXml)]
#[xml(rename = "login", ns(EPP_XMLNS))]
pub struct Login<'a> { pub struct Login<'a> {
/// The username to use for the login /// The username to use for the login
#[serde(rename(serialize = "clID", deserialize = "clID"))] #[xml(rename = "clID")]
username: StringValue<'a>, username: &'a str,
/// The password to use for the login /// The password to use for the login
#[serde(rename = "pw", default)] #[xml(rename = "pw")]
password: StringValue<'a>, password: &'a str,
/// A new password which should be set /// A new password which should be set
#[serde(rename = "newPW", default, skip_serializing_if = "Option::is_none")] #[xml(rename = "newPW")]
new_password: Option<StringValue<'a>>, new_password: Option<&'a str>,
/// Data under the <options> tag /// Data under the <options> tag
options: Options<'a>, options: Options<'a>,
/// Data under the <svcs> tag /// Data under the <svcs> tag
#[serde(rename = "svcs")] #[xml(rename = "svcs")]
services: Services<'a>, services: Services<'a>,
} }
@ -39,9 +40,9 @@ impl<'a> Login<'a> {
let ext_uris = ext_uris.map(|uris| uris.iter().map(|&u| u.into()).collect()); let ext_uris = ext_uris.map(|uris| uris.iter().map(|&u| u.into()).collect());
Self { Self {
username: username.into(), username,
password: password.into(), password,
new_password: new_password.map(Into::into), new_password,
options: Options { options: Options {
version: EPP_VERSION.into(), version: EPP_VERSION.into(),
lang: EPP_LANG.into(), lang: EPP_LANG.into(),
@ -90,8 +91,8 @@ mod tests {
fn response() { fn response() {
let object = response_from_file::<Login>("response/login.xml"); let object = response_from_file::<Login>("response/login.xml");
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,9 @@
use std::fmt::Debug; use std::fmt::Debug;
use serde::{Deserialize, Serialize}; use instant_xml::{FromXml, ToXml};
use crate::{ use crate::{
common::NoExtension, common::{NoExtension, EPP_XMLNS},
request::{Command, Transaction}, request::{Command, Transaction},
}; };
@ -14,8 +14,9 @@ impl Command for Logout {
const COMMAND: &'static str = "logout"; const COMMAND: &'static str = "logout";
} }
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
/// Type corresponding to the &lt;logout&gt; tag in an EPP XML logout request /// Type corresponding to the &lt;logout&gt; tag in an EPP XML logout request
#[xml(rename = "logout", ns(EPP_XMLNS))]
pub struct Logout; pub struct Logout;
#[cfg(test)] #[cfg(test)]
@ -40,9 +41,9 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; ending session".into() "Command completed successfully; ending session"
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,8 +1,9 @@
//! Types for EPP message ack request //! Types for EPP message ack request
use crate::common::NoExtension; use instant_xml::ToXml;
use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for MessageAck<'a> {} impl<'a> Transaction<NoExtension> for MessageAck<'a> {}
@ -11,14 +12,16 @@ impl<'a> Command for MessageAck<'a> {
const COMMAND: &'static str = "poll"; const COMMAND: &'static str = "poll";
} }
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
/// Type for EPP XML &lt;poll&gt; command for message ack /// Type for EPP XML &lt;poll&gt; command for message ack
#[xml(rename = "poll", ns(EPP_XMLNS))]
pub struct MessageAck<'a> { pub struct MessageAck<'a> {
/// The type of operation to perform /// The type of operation to perform
/// The value is "ack" for message acknowledgement /// The value is "ack" for message acknowledgement
#[xml(attribute)]
op: &'a str, op: &'a str,
/// The ID of the message to be acknowledged /// The ID of the message to be acknowledged
#[serde(rename = "msgID")] #[xml(rename = "msgID", attribute)]
message_id: &'a str, message_id: &'a str,
} }
@ -49,9 +52,9 @@ mod tests {
let msg = object.message_queue().unwrap(); let msg = object.message_queue().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into()); assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(msg.count, 4); assert_eq!(msg.count, 4);
assert_eq!(msg.id, "12345".to_string()); assert_eq!(msg.id, "12345".to_string());
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,10 @@
use crate::common::NoExtension; use instant_xml::{FromXml, ToXml};
use crate::common::{NoExtension, EPP_XMLNS};
use crate::domain::transfer::DomainTransferResponseData; use crate::domain::transfer::DomainTransferResponseData;
use crate::extensions::low_balance::LowBalance; use crate::extensions::low_balance::LowBalance;
use crate::host::info::HostInfoResponseData; use crate::host::info::HostInfoResponseData;
use crate::request::{Command, Transaction}; use crate::request::{Command, Transaction};
use serde::{Deserialize, Serialize};
impl<'a> Transaction<NoExtension> for MessagePoll<'a> {} impl<'a> Transaction<NoExtension> for MessagePoll<'a> {}
@ -14,11 +15,13 @@ impl<'a> Command for MessagePoll<'a> {
// Request // Request
#[derive(Serialize, Debug)] #[derive(Debug, ToXml)]
/// Type for EPP XML &lt;poll&gt; command for message poll /// Type for EPP XML &lt;poll&gt; command for message poll
#[xml(rename = "poll", ns(EPP_XMLNS))]
pub struct MessagePoll<'a> { pub struct MessagePoll<'a> {
/// The type of operation to perform /// The type of operation to perform
/// The value is "req" for message polling /// The value is "req" for message polling
#[xml(attribute)]
op: &'a str, op: &'a str,
} }
@ -31,32 +34,20 @@ impl Default for MessagePoll<'static> {
// Response // Response
/// Type that represents the &lt;trnData&gt; tag for message poll response /// Type that represents the &lt;trnData&gt; tag for message poll response
#[non_exhaustive] #[derive(Debug, FromXml)]
#[derive(Deserialize, Debug)] #[xml(forward)]
pub enum MessageData { pub enum MessagePollResponse {
/// Data under the &lt;domain:trnData&gt; tag /// Data under the &lt;domain:trnData&gt; tag
#[serde(rename = "trnData")]
DomainTransfer(DomainTransferResponseData), DomainTransfer(DomainTransferResponseData),
/// Data under the &lt;host:infData&gt; tag /// Data under the &lt;host:infData&gt; tag
#[serde(rename = "infData")]
HostInfo(HostInfoResponseData), HostInfo(HostInfoResponseData),
/// Data under the &lt;lowbalance&gt; tag /// Data under the &lt;lowbalance&gt; tag
#[serde(rename = "pollData")]
LowBalance(LowBalance), LowBalance(LowBalance),
} }
/// Type that represents the &lt;resData&gt; tag for message poll response
#[derive(Deserialize, Debug)]
pub struct MessagePollResponse {
/// Data under the &lt;trnData&gt; tag
#[serde(rename = "trnData", alias = "infData", alias = "pollData")]
pub message_data: MessageData,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::MessagePoll; use super::{MessagePoll, MessagePollResponse};
use crate::message::poll::MessageData;
use crate::response::ResultCode; use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file, CLTRID, SVTRID}; use crate::tests::{assert_serialized, response_from_file, CLTRID, SVTRID};
@ -81,7 +72,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; ack to dequeue".into() "Command completed successfully; ack to dequeue"
); );
assert_eq!(msg.count, 5); assert_eq!(msg.count, 5);
assert_eq!(msg.id, "12345".to_string()); assert_eq!(msg.id, "12345".to_string());
@ -89,20 +80,17 @@ mod tests {
msg.date, msg.date,
Utc.with_ymd_and_hms(2021, 7, 23, 19, 12, 43).single() Utc.with_ymd_and_hms(2021, 7, 23, 19, 12, 43).single()
); );
assert_eq!( assert_eq!(msg.message.as_ref().unwrap().text, "Transfer requested.");
*(msg.message.as_ref().unwrap()),
"Transfer requested.".into()
);
if let MessageData::DomainTransfer(tr) = &result.message_data { if let MessagePollResponse::DomainTransfer(tr) = &result {
assert_eq!(tr.name, "eppdev-transfer.com".into()); assert_eq!(tr.name, "eppdev-transfer.com");
assert_eq!(tr.transfer_status, "pending".into()); assert_eq!(tr.transfer_status, "pending");
assert_eq!(tr.requester_id, "eppdev".into()); assert_eq!(tr.requester_id, "eppdev");
assert_eq!( assert_eq!(
tr.requested_at, tr.requested_at,
Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap() Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap()
); );
assert_eq!(tr.ack_id, "ClientY".into()); assert_eq!(tr.ack_id, "ClientY");
assert_eq!( assert_eq!(
tr.ack_by, tr.ack_by,
Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap() Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap()
@ -115,8 +103,8 @@ mod tests {
panic!("Wrong type"); panic!("Wrong type");
} }
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -131,7 +119,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; ack to dequeue".into() "Command completed successfully; ack to dequeue"
); );
assert_eq!(msg.count, 4); assert_eq!(msg.count, 4);
assert_eq!(msg.id, "12345".to_string()); assert_eq!(msg.id, "12345".to_string());
@ -139,22 +127,19 @@ mod tests {
msg.date, msg.date,
Utc.with_ymd_and_hms(2022, 1, 2, 11, 30, 45).single() Utc.with_ymd_and_hms(2022, 1, 2, 11, 30, 45).single()
); );
assert_eq!( assert_eq!(msg.message.as_ref().unwrap().text, "Unused objects policy");
*(msg.message.as_ref().unwrap()),
"Unused objects policy".into()
);
if let MessageData::HostInfo(host) = &result.message_data { if let MessagePollResponse::HostInfo(host) = &result {
assert_eq!(host.name, "ns.test.com".into()); assert_eq!(host.name, "ns.test.com");
assert_eq!(host.roid, "1234".into()); assert_eq!(host.roid, "1234");
assert!(host.statuses.iter().any(|s| s.status == "ok")); assert!(host.statuses.iter().any(|s| s.status == "ok"));
assert!(host assert!(host
.addresses .addresses
.iter() .iter()
.any(|a| a == &IpAddr::from([1, 1, 1, 1]))); .any(|a| a == &IpAddr::from([1, 1, 1, 1])));
assert_eq!(host.client_id, "1234".into()); assert_eq!(host.client_id, "1234");
assert_eq!(host.creator_id, "user".into()); assert_eq!(host.creator_id, "user");
assert_eq!( assert_eq!(
host.created_at, host.created_at,
Utc.with_ymd_and_hms(2021, 12, 1, 22, 40, 48).unwrap() Utc.with_ymd_and_hms(2021, 12, 1, 22, 40, 48).unwrap()
@ -168,14 +153,15 @@ mod tests {
panic!("Wrong type"); panic!("Wrong type");
} }
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
fn message_only_response() { fn message_only_response() {
let object = response_from_file::<MessagePoll>("response/message/poll_message_only.xml"); let object = response_from_file::<MessagePoll>("response/message/poll_message_only.xml");
let msg = object.message_queue().unwrap(); let msg = object.message_queue().unwrap();
dbg!(&msg);
assert_eq!( assert_eq!(
object.result.code, object.result.code,
@ -183,7 +169,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; ack to dequeue".into() "Command completed successfully; ack to dequeue"
); );
assert_eq!(msg.count, 4); assert_eq!(msg.count, 4);
@ -192,13 +178,10 @@ mod tests {
msg.date, msg.date,
Utc.with_ymd_and_hms(2000, 6, 8, 22, 10, 0).single() Utc.with_ymd_and_hms(2000, 6, 8, 22, 10, 0).single()
); );
assert_eq!( assert_eq!(msg.message.as_ref().unwrap().text, "Credit balance low.");
*(msg.message.as_ref().unwrap()),
"Credit balance low.".into()
);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
#[test] #[test]
@ -211,10 +194,10 @@ mod tests {
); );
assert_eq!( assert_eq!(
object.result.message, object.result.message,
"Command completed successfully; no messages".into() "Command completed successfully; no messages"
); );
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -1,9 +1,10 @@
//! Types for EPP requests //! Types for EPP requests
use serde::{de::DeserializeOwned, ser::SerializeStruct, ser::Serializer, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use crate::common::{StringValue, EPP_XMLNS}; use instant_xml::{FromXmlOwned, ToXml};
use crate::common::EPP_XMLNS;
pub const EPP_VERSION: &str = "1.0"; pub const EPP_VERSION: &str = "1.0";
pub const EPP_LANG: &str = "en"; pub const EPP_LANG: &str = "en";
@ -11,62 +12,63 @@ pub const EPP_LANG: &str = "en";
/// Trait to set correct value for xml tags when tags are being generated from generic types /// Trait to set correct value for xml tags when tags are being generated from generic types
pub trait Transaction<Ext: Extension>: Command + Sized {} pub trait Transaction<Ext: Extension>: Command + Sized {}
pub trait Command: Serialize + Debug { pub trait Command: ToXml + Debug {
type Response: DeserializeOwned + Debug; type Response: FromXmlOwned + Debug;
const COMMAND: &'static str; const COMMAND: &'static str;
} }
pub trait Extension: Serialize + Debug { pub trait Extension: ToXml + Debug {
type Response: DeserializeOwned + Debug; type Response: FromXmlOwned + Debug;
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
/// Type corresponding to the &lt;command&gt; tag in an EPP XML request /// Type corresponding to the &lt;command&gt; tag in an EPP XML request
/// with an &lt;extension&gt; tag /// with an &lt;extension&gt; tag
struct CommandWrapper<'a, D, E> { pub(crate) struct CommandWrapper<'a, D, E> {
pub command: &'static str, pub command: &'static str,
/// The instance that will be used to populate the &lt;command&gt; tag /// The instance that will be used to populate the &lt;command&gt; tag
pub data: &'a D, pub data: &'a D,
/// The client TRID /// The client TRID
pub extension: Option<&'a E>, pub extension: Option<&'a E>,
pub client_tr_id: StringValue<'a>, pub client_tr_id: String,
} }
impl<'a, D: Serialize, E: Serialize> Serialize for CommandWrapper<'a, D, E> { impl<'a, E: Extension, D: Transaction<E>> CommandWrapper<'a, D, E> {
/// Serializes the generic type T to the proper XML tag (set by the `#[element_name(name = <tagname>)]` attribute) for the request pub(crate) fn new(data: &'a D, extension: Option<&'a E>, client_tr_id: &'a str) -> Self {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("command", 3)?;
state.serialize_field(self.command, self.data)?;
state.serialize_field("extension", &self.extension)?;
state.serialize_field("clTRID", &self.client_tr_id)?;
state.end()
}
}
#[derive(Debug, PartialEq, Serialize)]
#[serde(rename = "epp")]
pub(crate) struct CommandDocument<'a, Cmd, Ext> {
xmlns: &'static str,
command: CommandWrapper<'a, Cmd, Ext>,
}
impl<'a, Cmd, Ext> CommandDocument<'a, Cmd, Ext> {
pub(crate) fn new(data: &'a Cmd, extension: Option<&'a Ext>, client_tr_id: &'a str) -> Self
where
Cmd: Transaction<Ext>,
Ext: Extension,
{
Self { Self {
xmlns: EPP_XMLNS, command: D::COMMAND,
command: CommandWrapper { data,
command: Cmd::COMMAND, extension,
data, client_tr_id: client_tr_id.into(),
extension,
client_tr_id: client_tr_id.into(),
},
} }
} }
} }
impl<'a, D: ToXml, E: ToXml> ToXml for CommandWrapper<'a, D, E> {
fn serialize<W: std::fmt::Write + ?Sized>(
&self,
_: Option<instant_xml::Id<'_>>,
serializer: &mut instant_xml::Serializer<W>,
) -> Result<(), instant_xml::Error> {
let prefix = serializer.write_start("command", EPP_XMLNS)?;
serializer.end_start()?;
self.data.serialize(None, serializer)?;
if let Some(extension) = self.extension {
Ext { inner: extension }.serialize(None, serializer)?;
}
let id_prefix = serializer.write_start("clTRID", EPP_XMLNS)?;
serializer.end_start()?;
serializer.write_str(&self.client_tr_id)?;
serializer.write_close(id_prefix, "clTRID")?;
serializer.write_close(prefix, "command")?;
Ok(())
}
}
#[derive(Debug, ToXml)]
#[xml(rename = "extension", ns(EPP_XMLNS))]
struct Ext<E> {
inner: E,
}

View File

@ -3,43 +3,44 @@
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::Deserialize; use instant_xml::{FromXml, Kind};
use crate::common::StringValue; use crate::common::EPP_XMLNS;
/// Type corresponding to the <undef> tag an EPP response XML /// Type corresponding to the <undef> tag an EPP response XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "undef", ns(EPP_XMLNS))]
pub struct Undef; pub struct Undef;
/// Type corresponding to the <value> tag under <extValue> in an EPP response XML /// Type corresponding to the <value> tag under <extValue> in an EPP response XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "value", ns(EPP_XMLNS))]
pub struct ResultValue { pub struct ResultValue {
/// The XML namespace for the <value> tag
#[serde(rename = "xmlns:epp")]
xmlns: String,
/// The <undef> element /// The <undef> element
pub undef: Undef, pub undef: Undef,
} }
/// Type corresponding to the <extValue> tag in an EPP response XML /// Type corresponding to the <extValue> tag in an EPP response XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "extValue", ns(EPP_XMLNS))]
pub struct ExtValue { pub struct ExtValue {
/// Data under the <value> tag /// Data under the <value> tag
pub value: ResultValue, pub value: ResultValue,
/// Data under the <reason> tag /// Data under the <reason> tag
pub reason: StringValue<'static>, pub reason: String,
} }
/// Type corresponding to the <result> tag in an EPP response XML /// Type corresponding to the <result> tag in an EPP response XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "result", ns(EPP_XMLNS))]
pub struct EppResult { pub struct EppResult {
/// The result code /// The result code
#[xml(attribute)]
pub code: ResultCode, pub code: ResultCode,
/// The result message /// The result message
#[serde(rename = "msg")] #[xml(rename = "msg")]
pub message: StringValue<'static>, pub message: String,
/// Data under the <extValue> tag /// Data under the <extValue> tag
#[serde(rename = "extValue")]
pub ext_value: Option<ExtValue>, pub ext_value: Option<ExtValue>,
} }
@ -136,13 +137,37 @@ impl ResultCode {
} }
} }
impl<'de> Deserialize<'de> for ResultCode { impl<'xml> FromXml<'xml> for ResultCode {
fn deserialize<D>(deserializer: D) -> Result<ResultCode, D::Error> fn matches(id: instant_xml::Id<'_>, field: Option<instant_xml::Id<'_>>) -> bool {
where match field {
D: serde::de::Deserializer<'de>, Some(field) => id == field,
{ None => false,
deserializer.deserialize_u16(ResultCodeVisitor) }
} }
fn deserialize<'cx>(
into: &mut Self::Accumulator,
field: &'static str,
deserializer: &mut instant_xml::Deserializer<'cx, 'xml>,
) -> Result<(), instant_xml::Error> {
let mut value = None;
u16::deserialize(&mut value, field, deserializer)?;
if let Some(value) = value {
*into = match ResultCode::from_u16(value) {
Some(value) => Some(value),
None => {
return Err(instant_xml::Error::UnexpectedValue(format!(
"unexpected result code '{value}'"
)))
}
};
}
Ok(())
}
type Accumulator = Option<Self>;
const KIND: instant_xml::Kind = Kind::Scalar;
} }
struct ResultCodeVisitor; struct ResultCodeVisitor;
@ -166,71 +191,82 @@ impl<'de> serde::de::Visitor<'de> for ResultCodeVisitor {
} }
/// Type corresponding to the <trID> tag in an EPP response XML /// Type corresponding to the <trID> tag in an EPP response XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "trID", ns(EPP_XMLNS))]
pub struct ResponseTRID { pub struct ResponseTRID {
/// The client TRID /// The client TRID
#[serde(rename = "clTRID")] #[xml(rename = "clTRID")]
pub client_tr_id: Option<StringValue<'static>>, pub client_tr_id: Option<String>,
/// The server TRID /// The server TRID
#[serde(rename = "svTRID")] #[xml(rename = "svTRID")]
pub server_tr_id: StringValue<'static>, pub server_tr_id: String,
} }
/// Type corresponding to the <msgQ> tag in an EPP response XML /// Type corresponding to the <msgQ> tag in an EPP response XML
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "msgQ", ns(EPP_XMLNS))]
pub struct MessageQueue { pub struct MessageQueue {
/// The message count /// The message count
#[xml(attribute)]
pub count: u32, pub count: u32,
/// The message ID /// The message ID
#[xml(attribute)]
pub id: String, pub id: String,
/// The message date /// The message date
#[serde(rename = "qDate")] #[xml(rename = "qDate")]
pub date: Option<DateTime<Utc>>, pub date: Option<DateTime<Utc>>,
/// The message text /// The message text
#[serde(rename = "msg")] #[xml(rename = "msg")]
pub message: Option<StringValue<'static>>, pub message: Option<Message>,
} }
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "msg", ns(EPP_XMLNS))]
pub struct Message {
#[xml(attribute)]
pub lang: Option<String>,
#[xml(direct)]
pub text: String,
}
#[derive(Debug, FromXml, PartialEq)]
/// Type corresponding to the &lt;response&gt; tag in an EPP response XML /// Type corresponding to the &lt;response&gt; tag in an EPP response XML
/// containing an &lt;extension&gt; tag /// containing an &lt;extension&gt; tag
#[xml(rename = "response", ns(EPP_XMLNS))]
pub struct Response<D, E> { pub struct Response<D, E> {
/// Data under the <result> tag /// Data under the <result> tag
pub result: EppResult, pub result: EppResult,
/// Data under the <msgQ> tag /// Data under the <msgQ> tag
#[serde(rename = "msgQ")] #[xml(rename = "msgQ")]
pub message_queue: Option<MessageQueue>, pub message_queue: Option<MessageQueue>,
#[serde(rename = "resData")]
/// Data under the &lt;resData&gt; tag /// Data under the &lt;resData&gt; tag
pub res_data: Option<D>, pub res_data: Option<ResponseData<D>>,
/// Data under the &lt;extension&gt; tag /// Data under the &lt;extension&gt; tag
pub extension: Option<E>, pub extension: Option<Extension<E>>,
/// Data under the <trID> tag /// Data under the <trID> tag
#[serde(rename = "trID")]
pub tr_ids: ResponseTRID, pub tr_ids: ResponseTRID,
} }
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, Eq, FromXml, PartialEq)]
#[serde(rename = "epp")] #[xml(rename = "resData", ns(EPP_XMLNS))]
pub struct ResponseDocument<D, E> { pub struct ResponseData<D> {
#[serde(rename = "response")] data: D,
pub data: Response<D, E>,
} }
#[derive(Debug, Deserialize, Eq, PartialEq)] impl<D> ResponseData<D> {
#[serde(rename = "epp")] pub fn into_inner(self) -> D {
pub struct ResultDocument { self.data
#[serde(rename = "response")] }
pub data: ResponseStatus,
} }
#[derive(Deserialize, Debug, Eq, PartialEq)] #[derive(Debug, FromXml, PartialEq)]
/// Type corresponding to the &lt;response&gt; tag in an EPP response XML /// Type corresponding to the &lt;response&gt; tag in an EPP response XML
/// without <msgQ> or &lt;resData&gt; sections. Generally used for error handling /// without <msgQ> or &lt;resData&gt; sections. Generally used for error handling
#[xml(rename = "response", ns(EPP_XMLNS))]
pub struct ResponseStatus { pub struct ResponseStatus {
/// Data under the <result> tag /// Data under the <result> tag
pub result: EppResult, pub result: EppResult,
#[serde(rename = "trID")] #[xml(rename = "trID")]
/// Data under the <trID> tag /// Data under the <trID> tag
pub tr_ids: ResponseTRID, pub tr_ids: ResponseTRID,
} }
@ -239,10 +275,18 @@ impl<T, E> Response<T, E> {
/// Returns the data under the corresponding &lt;resData&gt; from the EPP XML /// Returns the data under the corresponding &lt;resData&gt; from the EPP XML
pub fn res_data(&self) -> Option<&T> { pub fn res_data(&self) -> Option<&T> {
match &self.res_data { match &self.res_data {
Some(res_data) => Some(res_data), Some(res_data) => Some(&res_data.data),
None => None, None => None,
} }
} }
pub fn extension(&self) -> Option<&E> {
match &self.extension {
Some(extension) => Some(&extension.data),
None => None,
}
}
/// Returns the data under the corresponding <msgQ> from the EPP XML /// Returns the data under the corresponding <msgQ> from the EPP XML
pub fn message_queue(&self) -> Option<&MessageQueue> { pub fn message_queue(&self) -> Option<&MessageQueue> {
match &self.message_queue { match &self.message_queue {
@ -252,24 +296,30 @@ impl<T, E> Response<T, E> {
} }
} }
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "extension", ns(EPP_XMLNS))]
pub struct Extension<E> {
pub data: E,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ResultCode, ResultDocument}; use super::{ResponseStatus, ResultCode};
use crate::tests::{get_xml, CLTRID, SVTRID}; use crate::tests::{get_xml, CLTRID, SVTRID};
use crate::xml; use crate::xml;
#[test] #[test]
fn error() { fn error() {
let xml = get_xml("response/error.xml").unwrap(); let xml = get_xml("response/error.xml").unwrap();
let object = xml::deserialize::<ResultDocument>(xml.as_str()).unwrap(); let object = xml::deserialize::<ResponseStatus>(xml.as_str()).unwrap();
assert_eq!(object.data.result.code, ResultCode::ObjectDoesNotExist); assert_eq!(object.result.code, ResultCode::ObjectDoesNotExist);
assert_eq!(object.data.result.message, "Object does not exist".into()); assert_eq!(object.result.message, "Object does not exist");
assert_eq!( assert_eq!(
object.data.result.ext_value.unwrap().reason, object.result.ext_value.unwrap().reason,
"545 Object not found".into() "545 Object not found"
); );
assert_eq!(object.data.tr_ids.client_tr_id.unwrap(), CLTRID.into()); assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.data.tr_ids.server_tr_id, SVTRID.into()); assert_eq!(object.tr_ids.server_tr_id, SVTRID);
} }
} }

View File

@ -3,12 +3,13 @@
use std::{error::Error, fs::File, io::Read}; use std::{error::Error, fs::File, io::Read};
use regex::Regex; use regex::Regex;
use similar_asserts::assert_eq;
use crate::{ use crate::{
client::RequestData, client::RequestData,
common::NoExtension, common::NoExtension,
request::{Command, CommandDocument, Extension, Transaction}, request::{Command, CommandWrapper, Extension, Transaction},
response::{Response, ResponseDocument}, response::Response,
xml, xml,
}; };
@ -46,8 +47,8 @@ pub(crate) fn assert_serialized<'c, 'e, Cmd, Ext>(
{ {
let expected = get_xml(path).unwrap(); let expected = get_xml(path).unwrap();
let req = req.into(); let req = req.into();
let document = CommandDocument::new(req.command, req.extension, CLTRID); let document = CommandWrapper::new(req.command, req.extension, CLTRID);
assert_eq!(expected, xml::serialize(&document).unwrap()); assert_eq!(expected, xml::serialize(document).unwrap());
} }
pub(crate) fn response_from_file<'c, Cmd>( pub(crate) fn response_from_file<'c, Cmd>(
@ -67,7 +68,8 @@ where
Ext: Extension, Ext: Extension,
{ {
let xml = get_xml(path).unwrap(); let xml = get_xml(path).unwrap();
let rsp = xml::deserialize::<ResponseDocument<Cmd::Response, Ext::Response>>(&xml).unwrap(); dbg!(&xml);
assert!(rsp.data.result.code.is_success()); let rsp = xml::deserialize::<Response<Cmd::Response, Ext::Response>>(&xml).unwrap();
rsp.data assert!(rsp.result.code.is_success());
rsp
} }

View File

@ -1,19 +1,29 @@
//! Types to use in serialization to and deserialization from EPP XML //! Types to use in serialization to and deserialization from EPP XML
use serde::{de::DeserializeOwned, Serialize}; use instant_xml::{FromXml, FromXmlOwned, ToXml};
use crate::common::EPP_XMLNS;
use crate::error::Error; use crate::error::Error;
pub const EPP_XML_HEADER: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>"#; pub const EPP_XML_HEADER: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>"#;
pub(crate) fn serialize(doc: &impl Serialize) -> Result<String, Error> { pub(crate) fn serialize(data: impl ToXml) -> Result<String, Error> {
Ok(format!( Ok(format!(
"{}\r\n{}", "{}\r\n{}",
EPP_XML_HEADER, EPP_XML_HEADER,
quick_xml::se::to_string(doc).map_err(|e| Error::Xml(e.into()))? instant_xml::to_string(&Epp { data }).map_err(|e| Error::Xml(e.into()))?
)) ))
} }
pub(crate) fn deserialize<T: DeserializeOwned>(xml: &str) -> Result<T, Error> { pub(crate) fn deserialize<T: FromXmlOwned>(xml: &str) -> Result<T, Error> {
quick_xml::de::from_str(xml).map_err(|e| Error::Xml(e.into())) match instant_xml::from_str::<Epp<T>>(xml) {
Ok(Epp { data }) => Ok(data),
Err(e) => Err(Error::Xml(e.into())),
}
}
#[derive(FromXml, ToXml)]
#[xml(rename = "epp", ns(EPP_XMLNS))]
pub(crate) struct Epp<T> {
pub(crate) data: T,
} }

View File

@ -131,7 +131,7 @@ async fn client() {
assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully); assert_eq!(rsp.result.code, ResultCode::CommandCompletedSuccessfully);
let result = rsp.res_data().unwrap(); let result = rsp.res_data().unwrap();
assert_eq!(result.list[0].id, "eppdev.com"); assert_eq!(result.list[0].inner.id, "eppdev.com");
} }
#[tokio::test] #[tokio::test]

View File

@ -2,11 +2,11 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<check> <check>
<contact:check xmlns:contact="urn:ietf:params:xml:ns:contact-1.0"> <check xmlns="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>eppdev-contact-1</contact:id> <id>eppdev-contact-1</id>
<contact:id>eppdev-contact-2</contact:id> <id>eppdev-contact-2</id>
</contact:check> </check>
</check> </check>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,28 +2,28 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<create> <create>
<contact:create xmlns:contact="urn:ietf:params:xml:ns:contact-1.0"> <create xmlns="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>eppdev-contact-3</contact:id> <id>eppdev-contact-3</id>
<contact:postalInfo type="int"> <postalInfo type="int">
<contact:name>John Doe</contact:name> <name>John Doe</name>
<contact:org>Acme Widgets</contact:org> <org>Acme Widgets</org>
<contact:addr> <addr>
<contact:street>58</contact:street> <street>58</street>
<contact:street>Orchid Road</contact:street> <street>Orchid Road</street>
<contact:city>Paris</contact:city> <city>Paris</city>
<contact:sp>Paris</contact:sp> <sp>Paris</sp>
<contact:pc>392374</contact:pc> <pc>392374</pc>
<contact:cc>FR</contact:cc> <cc>FR</cc>
</contact:addr> </addr>
</contact:postalInfo> </postalInfo>
<contact:voice x="123">+33.47237942</contact:voice> <voice x="123">+33.47237942</voice>
<contact:fax x="677">+33.86698799</contact:fax> <fax x="677">+33.86698799</fax>
<contact:email>contact@eppdev.net</contact:email> <email>contact@eppdev.net</email>
<contact:authInfo> <authInfo>
<contact:pw>eppdev-387323</contact:pw> <pw>eppdev-387323</pw>
</contact:authInfo> </authInfo>
</contact:create> </create>
</create> </create>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<delete> <delete>
<contact:delete xmlns:contact="urn:ietf:params:xml:ns:contact-1.0"> <delete xmlns="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>eppdev-contact-3</contact:id> <id>eppdev-contact-3</id>
</contact:delete> </delete>
</delete> </delete>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,13 +2,13 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<info> <info>
<contact:info xmlns:contact="urn:ietf:params:xml:ns:contact-1.0"> <info xmlns="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>eppdev-contact-3</contact:id> <id>eppdev-contact-3</id>
<contact:authInfo> <authInfo>
<contact:pw>eppdev-387323</contact:pw> <pw>eppdev-387323</pw>
</contact:authInfo> </authInfo>
</contact:info> </info>
</info> </info>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,35 +2,35 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<contact:update xmlns:contact="urn:ietf:params:xml:ns:contact-1.0"> <update xmlns="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>eppdev-contact-3</contact:id> <id>eppdev-contact-3</id>
<contact:add> <add>
<contact:status s="clientTransferProhibited"/> <status s="clientTransferProhibited"></status>
</contact:add> </add>
<contact:rem> <rem>
<contact:status s="clientDeleteProhibited"/> <status s="clientDeleteProhibited"></status>
</contact:rem> </rem>
<contact:chg> <chg>
<contact:postalInfo type="loc"> <postalInfo type="loc">
<contact:name>John Doe</contact:name> <name>John Doe</name>
<contact:org>Acme Widgets</contact:org> <org>Acme Widgets</org>
<contact:addr> <addr>
<contact:street>58</contact:street> <street>58</street>
<contact:street>Orchid Road</contact:street> <street>Orchid Road</street>
<contact:city>Paris</contact:city> <city>Paris</city>
<contact:sp>Paris</contact:sp> <sp>Paris</sp>
<contact:pc>392374</contact:pc> <pc>392374</pc>
<contact:cc>FR</contact:cc> <cc>FR</cc>
</contact:addr> </addr>
</contact:postalInfo> </postalInfo>
<contact:voice>+33.47237942</contact:voice> <voice>+33.47237942</voice>
<contact:email>newemail@eppdev.net</contact:email> <email>newemail@eppdev.net</email>
<contact:authInfo> <authInfo>
<contact:pw>eppdev-387323</contact:pw> <pw>eppdev-387323</pw>
</contact:authInfo> </authInfo>
</contact:chg> </chg>
</contact:update> </update>
</update> </update>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,11 +2,11 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<check> <check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <check xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:name>eppdev.net</domain:name> <name>eppdev.net</name>
</domain:check> </check>
</check> </check>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,18 +2,18 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<create> <create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <create xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev-1.com</domain:name> <name>eppdev-1.com</name>
<domain:period unit="y">1</domain:period> <period unit="y">1</period>
<domain:registrant>eppdev-contact-3</domain:registrant> <registrant>eppdev-contact-3</registrant>
<domain:contact type="admin">eppdev-contact-3</domain:contact> <contact type="admin">eppdev-contact-3</contact>
<domain:contact type="tech">eppdev-contact-3</domain:contact> <contact type="tech">eppdev-contact-3</contact>
<domain:contact type="billing">eppdev-contact-3</domain:contact> <contact type="billing">eppdev-contact-3</contact>
<domain:authInfo> <authInfo>
<domain:pw>epP4uthd#v</domain:pw> <pw>epP4uthd#v</pw>
</domain:authInfo> </authInfo>
</domain:create> </create>
</create> </create>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,28 +2,28 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<create> <create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <create xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev-2.com</domain:name> <name>eppdev-2.com</name>
<domain:period unit="y">1</domain:period> <period unit="y">1</period>
<domain:ns> <ns>
<domain:hostAttr> <hostAttr>
<domain:hostName>ns1.eppdev-1.com</domain:hostName> <hostName>ns1.eppdev-1.com</hostName>
</domain:hostAttr> </hostAttr>
<domain:hostAttr> <hostAttr>
<domain:hostName>ns2.eppdev-1.com</domain:hostName> <hostName>ns2.eppdev-1.com</hostName>
<domain:hostAddr ip="v4">177.232.12.58</domain:hostAddr> <hostAddr ip="v4">177.232.12.58</hostAddr>
<domain:hostAddr ip="v6">2404:6800:4001:801::200e</domain:hostAddr> <hostAddr ip="v6">2404:6800:4001:801::200e</hostAddr>
</domain:hostAttr> </hostAttr>
</domain:ns> </ns>
<domain:registrant>eppdev-contact-3</domain:registrant> <registrant>eppdev-contact-3</registrant>
<domain:contact type="admin">eppdev-contact-3</domain:contact> <contact type="admin">eppdev-contact-3</contact>
<domain:contact type="tech">eppdev-contact-3</domain:contact> <contact type="tech">eppdev-contact-3</contact>
<domain:contact type="billing">eppdev-contact-3</domain:contact> <contact type="billing">eppdev-contact-3</contact>
<domain:authInfo> <authInfo>
<domain:pw>epP4uthd#v</domain:pw> <pw>epP4uthd#v</pw>
</domain:authInfo> </authInfo>
</domain:create> </create>
</create> </create>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,22 +2,22 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<create> <create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <create xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev-1.com</domain:name> <name>eppdev-1.com</name>
<domain:period unit="y">1</domain:period> <period unit="y">1</period>
<domain:ns> <ns>
<domain:hostObj>ns1.test.com</domain:hostObj> <hostObj>ns1.test.com</hostObj>
<domain:hostObj>ns2.test.com</domain:hostObj> <hostObj>ns2.test.com</hostObj>
</domain:ns> </ns>
<domain:registrant>eppdev-contact-3</domain:registrant> <registrant>eppdev-contact-3</registrant>
<domain:contact type="admin">eppdev-contact-3</domain:contact> <contact type="admin">eppdev-contact-3</contact>
<domain:contact type="tech">eppdev-contact-3</domain:contact> <contact type="tech">eppdev-contact-3</contact>
<domain:contact type="billing">eppdev-contact-3</domain:contact> <contact type="billing">eppdev-contact-3</contact>
<domain:authInfo> <authInfo>
<domain:pw>epP4uthd#v</domain:pw> <pw>epP4uthd#v</pw>
</domain:authInfo> </authInfo>
</domain:create> </create>
</create> </create>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<delete> <delete>
<domain:delete xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <delete xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
</domain:delete> </delete>
</delete> </delete>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,13 +2,13 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<info> <info>
<domain:info xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <info xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name hosts="all">eppdev.com</domain:name> <name hosts="all">eppdev.com</name>
<domain:authInfo> <authInfo>
<domain:pw>2fooBAR</domain:pw> <pw>2fooBAR</pw>
</domain:authInfo> </authInfo>
</domain:info> </info>
</info> </info>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,12 +2,12 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<renew> <renew>
<domain:renew xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <renew xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:curExpDate>2022-07-23</domain:curExpDate> <curExpDate>2022-07-23</curExpDate>
<domain:period unit="y">1</domain:period> <period unit="y">1</period>
</domain:renew> </renew>
</renew> </renew>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<transfer op="approve"> <transfer op="approve">
<domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <transfer xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>testing.com</domain:name> <name>testing.com</name>
</domain:transfer> </transfer>
</transfer> </transfer>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<transfer op="cancel"> <transfer op="cancel">
<domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <transfer xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>testing.com</domain:name> <name>testing.com</name>
</domain:transfer> </transfer>
</transfer> </transfer>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,13 +2,13 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<transfer op="query"> <transfer op="query">
<domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <transfer xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>testing.com</domain:name> <name>testing.com</name>
<domain:authInfo> <authInfo>
<domain:pw>epP4uthd#v</domain:pw> <pw>epP4uthd#v</pw>
</domain:authInfo> </authInfo>
</domain:transfer> </transfer>
</transfer> </transfer>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<transfer op="reject"> <transfer op="reject">
<domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <transfer xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>testing.com</domain:name> <name>testing.com</name>
</domain:transfer> </transfer>
</transfer> </transfer>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,14 +2,14 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<transfer op="request"> <transfer op="request">
<domain:transfer xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <transfer xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>testing.com</domain:name> <name>testing.com</name>
<domain:period unit="y">1</domain:period> <period unit="y">1</period>
<domain:authInfo> <authInfo>
<domain:pw>epP4uthd#v</domain:pw> <pw>epP4uthd#v</pw>
</domain:authInfo> </authInfo>
</domain:transfer> </transfer>
</transfer> </transfer>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,21 +2,21 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <update xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:add> <add>
<domain:status s="clientDeleteProhibited"/> <status s="clientDeleteProhibited"></status>
</domain:add> </add>
<domain:rem> <rem>
<domain:contact type="billing">eppdev-contact-2</domain:contact> <contact type="billing">eppdev-contact-2</contact>
</domain:rem> </rem>
<domain:chg> <chg>
<domain:authInfo> <authInfo>
<domain:pw>epP5uthd#v</domain:pw> <pw>epP5uthd#v</pw>
</domain:authInfo> </authInfo>
</domain:chg> </chg>
</domain:update> </update>
</update> </update>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,16 +2,16 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <update xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:chg/> <chg></chg>
</domain:update> </update>
</update> </update>
<extension> <extension>
<sync:update xmlns:sync="http://www.verisign.com/epp/sync-1.0"> <update xmlns="http://www.verisign.com/epp/sync-1.0">
<sync:expMonthDay>--05-31</sync:expMonthDay> <expMonthDay>--05-31</expMonthDay>
</sync:update> </update>
</extension> </extension>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,19 +2,19 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <update xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:chg/> <chg></chg>
</domain:update> </update>
</update> </update>
<extension> <extension>
<sync:update xmlns:sync="http://www.verisign.com/epp/sync-1.0"> <update xmlns="http://www.verisign.com/epp/sync-1.0">
<sync:expMonthDay>--05-31</sync:expMonthDay> <expMonthDay>--05-31</expMonthDay>
</sync:update> </update>
<namestoreExt:namestoreExt xmlns:namestoreExt="http://www.verisign-grs.com/epp/namestoreExt-1.1"> <namestoreExt xmlns="http://www.verisign-grs.com/epp/namestoreExt-1.1">
<namestoreExt:subProduct>com</namestoreExt:subProduct> <subProduct>com</subProduct>
</namestoreExt:namestoreExt> </namestoreExt>
</extension> </extension>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,17 +2,17 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<check> <check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <check xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example1.com</domain:name> <name>example1.com</name>
<domain:name>example2.com</domain:name> <name>example2.com</name>
<domain:name>example3.com</domain:name> <name>example3.com</name>
</domain:check> </check>
</check> </check>
<extension> <extension>
<namestoreExt:namestoreExt xmlns:namestoreExt="http://www.verisign-grs.com/epp/namestoreExt-1.1"> <namestoreExt xmlns="http://www.verisign-grs.com/epp/namestoreExt-1.1">
<namestoreExt:subProduct>com</namestoreExt:subProduct> <subProduct>com</subProduct>
</namestoreExt:namestoreExt> </namestoreExt>
</extension> </extension>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,27 +2,27 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <update xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:chg/> <chg></chg>
</domain:update> </update>
</update> </update>
<extension> <extension>
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0"> <update xmlns="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:restore op="report"> <restore op="report">
<rgp:report> <report>
<rgp:preData>Pre-delete registration data goes here. Both XML and free text are allowed.</rgp:preData> <preData>Pre-delete registration data goes here. Both XML and free text are allowed.</preData>
<rgp:postData>Post-restore registration data goes here. Both XML and free text are allowed.</rgp:postData> <postData>Post-restore registration data goes here. Both XML and free text are allowed.</postData>
<rgp:delTime>2021-07-10T22:00:00Z</rgp:delTime> <delTime>2021-07-10T22:00:00Z</delTime>
<rgp:resTime>2021-07-20T22:00:00Z</rgp:resTime> <resTime>2021-07-20T22:00:00Z</resTime>
<rgp:resReason>Registrant error.</rgp:resReason> <resReason>Registrant error.</resReason>
<rgp:statement>This registrar has not restored the Registered Name in order to assume the rights to use or sell the Registered Name for itself or for any third party.</rgp:statement> <statement>This registrar has not restored the Registered Name in order to assume the rights to use or sell the Registered Name for itself or for any third party.</statement>
<rgp:statement>The information in this report is true to best of this registrar&apos;s knowledge, and this registrar acknowledges that intentionally supplying false information in this report shall constitute an incurable material breach of the Registry-Registrar Agreement.</rgp:statement> <statement>The information in this report is true to best of this registrar&apos;s knowledge, and this registrar acknowledges that intentionally supplying false information in this report shall constitute an incurable material breach of the Registry-Registrar Agreement.</statement>
<rgp:other>Supporting information goes here.</rgp:other> <other>Supporting information goes here.</other>
</rgp:report> </report>
</rgp:restore> </restore>
</rgp:update> </update>
</extension> </extension>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,16 +2,16 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"> <update xmlns="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name> <name>eppdev.com</name>
<domain:chg/> <chg></chg>
</domain:update> </update>
</update> </update>
<extension> <extension>
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0"> <update xmlns="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:restore op="request"/> <restore op="request"></restore>
</rgp:update> </update>
</extension> </extension>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello/> <hello />
</epp> </epp>

View File

@ -2,11 +2,11 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<check> <check>
<host:check xmlns:host="urn:ietf:params:xml:ns:host-1.0"> <check xmlns="urn:ietf:params:xml:ns:host-1.0">
<host:name>ns1.eppdev-1.com</host:name> <name>ns1.eppdev-1.com</name>
<host:name>host1.eppdev-1.com</host:name> <name>host1.eppdev-1.com</name>
</host:check> </check>
</check> </check>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,12 +2,12 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<create> <create>
<host:create xmlns:host="urn:ietf:params:xml:ns:host-1.0"> <create xmlns="urn:ietf:params:xml:ns:host-1.0">
<host:name>host1.eppdev-1.com</host:name> <name>host1.eppdev-1.com</name>
<host:addr ip="v4">29.245.122.14</host:addr> <addr ip="v4">29.245.122.14</addr>
<host:addr ip="v6">2404:6800:4001:801::200e</host:addr> <addr ip="v6">2404:6800:4001:801::200e</addr>
</host:create> </create>
</create> </create>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<delete> <delete>
<host:delete xmlns:host="urn:ietf:params:xml:ns:host-1.0"> <delete xmlns="urn:ietf:params:xml:ns:host-1.0">
<host:name>ns1.eppdev-1.com</host:name> <name>ns1.eppdev-1.com</name>
</host:delete> </delete>
</delete> </delete>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,10 +2,10 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<info> <info>
<host:info xmlns:host="urn:ietf:params:xml:ns:host-1.0"> <info xmlns="urn:ietf:params:xml:ns:host-1.0">
<host:name>ns1.eppdev-1.com</host:name> <name>ns1.eppdev-1.com</name>
</host:info> </info>
</info> </info>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -2,19 +2,19 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<update> <update>
<host:update xmlns:host="urn:ietf:params:xml:ns:host-1.0"> <update xmlns="urn:ietf:params:xml:ns:host-1.0">
<host:name>host1.eppdev-1.com</host:name> <name>host1.eppdev-1.com</name>
<host:add> <add>
<host:addr ip="v6">2404:6800:4001:801::200e</host:addr> <addr ip="v6">2404:6800:4001:801::200e</addr>
</host:add> </add>
<host:rem> <rem>
<host:status s="clientDeleteProhibited"/> <status s="clientDeleteProhibited"></status>
</host:rem> </rem>
<host:chg> <chg>
<host:name>host2.eppdev-1.com</host:name> <name>host2.eppdev-1.com</name>
</host:chg> </chg>
</host:update> </update>
</update> </update>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<logout/> <logout />
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<poll op="ack" msgID="12345"/> <poll op="ack" msgID="12345"></poll>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"> <epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command> <command>
<poll op="req"/> <poll op="req"></poll>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
</command> </command>
</epp> </epp>

View File

@ -24,4 +24,4 @@
<svTRID>RO-6879-1627224678242975</svTRID> <svTRID>RO-6879-1627224678242975</svTRID>
</trID> </trID>
</response> </response>
</epp> </epp>

View File

@ -51,4 +51,4 @@
</expiry> </expiry>
</dcp> </dcp>
</greeting> </greeting>
</epp> </epp>

View File

@ -19,4 +19,4 @@
<svTRID>RO-6879-1627224678242975</svTRID> <svTRID>RO-6879-1627224678242975</svTRID>
</trID> </trID>
</response> </response>
</epp> </epp>

View File

@ -21,18 +21,9 @@
<host:upDate>2021-12-01T22:40:48Z</host:upDate> <host:upDate>2021-12-01T22:40:48Z</host:upDate>
</host:infData> </host:infData>
</resData> </resData>
<extension>
<changePoll:changeData state="before" xmlns:changePoll="urn:ietf:params:xml:ns:changePoll-1.0">
<changePoll:operation op="purge">delete</changePoll:operation>
<changePoll:date>2022-01-02T11:30:45Z</changePoll:date>
<changePoll:svTRID>1234</changePoll:svTRID>
<changePoll:who>regy_batch</changePoll:who>
<changePoll:reason>Unused objects policy</changePoll:reason>
</changePoll:changeData>
</extension>
<trID> <trID>
<clTRID>cltrid:1626454866</clTRID> <clTRID>cltrid:1626454866</clTRID>
<svTRID>RO-6879-1627224678242975</svTRID> <svTRID>RO-6879-1627224678242975</svTRID>
</trID> </trID>
</response> </response>
</epp> </epp>