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"
celes = "2.1"
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"] }
tokio = { version = "1.0", features = ["io-util", "net", "time"] }
tokio-rustls = { version = "0.23", optional = true }
@ -26,3 +26,4 @@ regex = "1.5"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
tokio-test = "0.4"
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;
use crate::connection::{self, EppConnection};
use crate::error::Error;
use crate::hello::{Greeting, GreetingDocument, HelloDocument};
use crate::request::{Command, CommandDocument, Extension, Transaction};
use crate::response::{Response, ResponseDocument, ResponseStatus};
use crate::hello::{Greeting, Hello};
use crate::request::{Command, CommandWrapper, Extension, Transaction};
use crate::response::{Response, ResponseStatus};
use crate::xml;
/// 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::time::Duration;
/// #
/// use epp_client::EppClient;
/// use epp_client::domain::DomainCheck;
/// use epp_client::common::NoExtension;
/// use instant_epp::EppClient;
/// use instant_epp::domain::DomainCheck;
/// use instant_epp::common::NoExtension;
///
/// # #[tokio::main]
/// # async fn main() {
@ -55,9 +55,12 @@ use crate::xml;
/// // Execute an EPP Command against the registry with distinct request and response objects
/// let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] };
/// let response = client.transact(&domain_check, "transaction-id").await.unwrap();
/// response.res_data.unwrap().list
/// response
/// .res_data()
/// .unwrap()
/// .list
/// .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`
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);
let response = self.connection.transact(&xml)?.await?;
debug!("{}: greeting: {}", self.connection.registry, &response);
Ok(xml::deserialize::<GreetingDocument>(&response)?.data)
xml::deserialize::<Greeting>(&response)
}
pub async fn transact<'c, 'e, Cmd, Ext>(
@ -122,15 +125,14 @@ impl<C: Connector> EppClient<C> {
Ext: Extension + 'e,
{
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)?;
debug!("{}: request: {}", self.connection.registry, &xml);
let response = self.connection.transact(&xml)?.await?;
debug!("{}: response: {}", self.connection.registry, &response);
let rsp =
match xml::deserialize::<ResponseDocument<Cmd::Response, Ext::Response>>(&response) {
let rsp = match xml::deserialize::<Response<Cmd::Response, Ext::Response>>(&response) {
Ok(rsp) => rsp,
Err(e) => {
error!(%response, "failed to deserialize response for transaction: {e}");
@ -138,13 +140,13 @@ impl<C: Connector> EppClient<C> {
}
};
if rsp.data.result.code.is_success() {
return Ok(rsp.data);
if rsp.result.code.is_success() {
return Ok(rsp);
}
let err = crate::error::Error::Command(Box::new(ResponseStatus {
result: rsp.data.result,
tr_ids: rsp.data.tr_ids,
result: rsp.result,
tr_ids: rsp.tr_ids,
}));
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`
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> {

View File

@ -1,135 +1,45 @@
//! Common data types included in EPP Requests and Responses
use std::ops::Deref;
use std::{borrow::Cow, fmt::Display, net::IpAddr};
use std::borrow::Cow;
use serde::ser::SerializeSeq;
use serde::{Deserialize, Serialize};
use instant_xml::{FromXml, ToXml};
use crate::request::Extension;
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
/// 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.
#[derive(Debug, Eq, PartialEq, ToXml)]
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 {
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
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename = "options")]
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
#[xml(rename = "options", ns(EPP_XMLNS))]
pub struct Options<'a> {
/// The EPP version being used
pub version: StringValue<'a>,
pub version: Cow<'a, str>,
/// The language that will be used during EPP transactions
pub lang: StringValue<'a>,
pub lang: Cow<'a, str>,
}
impl<'a> Options<'a> {
@ -143,73 +53,26 @@ impl<'a> Options<'a> {
}
/// The <svcExtension> type in EPP XML
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename = "svcExtension")]
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
#[xml(rename = "svcExtension", ns(EPP_XMLNS))]
pub struct ServiceExtension<'a> {
/// The service extension URIs being represented by <extURI> in EPP XML
#[serde(rename = "extURI")]
pub ext_uris: Option<Vec<StringValue<'a>>>,
#[xml(rename = "extURI")]
pub ext_uris: Option<Vec<Cow<'a, str>>>,
}
/// 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> {
/// The service URIs being used by this EPP session represented by <objURI> in EPP XML
#[serde(rename = "objURI")]
pub obj_uris: Vec<StringValue<'a>>,
/// The <svcExtention> being used in this EPP session
#[serde(rename = "svcExtension")]
#[xml(rename = "objURI")]
pub obj_uris: Vec<Cow<'a, str>>,
// The <svcExtension> being used in this EPP session
#[xml(rename = "svcExtension")]
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.
///
/// The rustls-pemfile crate can be used to parse a PEM file.

View File

@ -1,9 +1,8 @@
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::common::StringValue;
use instant_xml::{display_to_xml, from_xml_str, FromXml, ToXml};
pub mod check;
pub use check::ContactCheck;
@ -22,9 +21,39 @@ pub use update::ContactUpdate;
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);
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 {
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
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Debug, Clone, FromXml, ToXml)]
#[xml(rename = "authInfo", ns(XMLNS))]
pub struct ContactAuthInfo<'a> {
/// The &lt;pw&gt; tag under &lt;authInfo&gt;
#[serde(rename = "contact:pw", alias = "pw")]
pub password: StringValue<'a>,
#[xml(rename = "pw")]
pub password: Cow<'a, str>,
}
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
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Phone<'a> {
/// The inner text on the &lt;voice&gt; and &lt;fax&gt; tags
#[serde(rename = "$value")]
pub number: Cow<'a, str>,
/// The data for &lt;voice&gt; types on domain transactions
#[derive(Debug, Clone, FromXml, ToXml)]
#[xml(rename = "voice", ns(XMLNS))]
pub struct Voice<'a> {
/// 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>>,
/// 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
pub fn new(number: &'a str) -> Self {
Self {
@ -85,22 +143,21 @@ impl<'a> Phone<'a> {
}
/// 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> {
/// The &lt;street&gt; tags under &lt;addr&gt;
#[serde(rename = "contact:street", alias = "street")]
pub street: Vec<StringValue<'a>>,
pub street: Vec<Cow<'a, str>>,
/// The &lt;city&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:city", alias = "city")]
pub city: StringValue<'a>,
pub city: Cow<'a, str>,
/// The &lt;sp&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:sp", alias = "sp")]
pub province: StringValue<'a>,
#[xml(rename = "sp")]
pub province: Cow<'a, str>,
/// The &lt;pc&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:pc", alias = "pc")]
pub postal_code: StringValue<'a>,
#[xml(rename = "pc")]
pub postal_code: Cow<'a, str>,
/// The &lt;cc&gt; tag under &lt;addr&gt;
#[serde(rename = "contact:cc", alias = "cc")]
#[xml(rename = "cc")]
pub country: Country,
}
@ -126,35 +183,43 @@ impl<'a> Address<'a> {
}
/// 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> {
/// The 'type' attr on &lt;postalInfo&gt;
#[serde(rename = "type")]
pub info_type: String,
#[xml(rename = "type", attribute)]
pub info_type: Cow<'a, str>,
/// The &lt;name&gt; tag under &lt;postalInfo&gt;
#[serde(rename = "contact:name", alias = "name")]
pub name: StringValue<'a>,
pub name: Cow<'a, str>,
/// The &lt;org&gt; tag under &lt;postalInfo&gt;
#[serde(rename = "contact:org", alias = "org")]
pub organization: StringValue<'a>,
#[xml(rename = "org")]
pub organization: Cow<'a, str>,
/// The &lt;addr&gt; tag under &lt;postalInfo&gt;
#[serde(rename = "contact:addr", alias = "addr")]
pub address: Address<'a>,
}
impl<'a> PostalInfo<'a> {
/// Creates a new PostalInfo instance
pub fn new(
info_type: &str,
info_type: &'a str,
name: &'a str,
organization: &'a str,
address: Address<'a>,
) -> Self {
Self {
info_type: info_type.to_string(),
info_type: info_type.into(),
name: name.into(),
organization: organization.into(),
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 crate::common::{CheckResponse, NoExtension, StringValue};
use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for ContactCheck<'a> {}
impl<'a> Command for ContactCheck<'a> {
type Response = CheckResponse;
type Response = CheckData;
const COMMAND: &'static str = "check";
}
// Request
/// 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> {
/// 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
#[serde(rename = "contact:id")]
contact_ids: Vec<StringValue<'a>>,
id: &'a [&'a str],
}
#[derive(Serialize, Debug)]
struct SerializeContactCheck<'a> {
/// The &lt;check&gt; tag for the contact check command
#[serde(rename = "contact:check")]
list: ContactList<'a>,
}
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(),
},
}
}
fn serialize_contacts<W: fmt::Write + ?Sized>(
ids: &[&str],
serializer: &mut Serializer<W>,
) -> Result<(), instant_xml::Error> {
ContactList { id: ids }.serialize(None, serializer)
}
/// The EPP `check` command for contacts
#[derive(Clone, Debug, Serialize)]
#[serde(into = "SerializeContactCheck")]
#[derive(Clone, Debug, ToXml)]
#[xml(rename = "check", ns(EPP_XMLNS))]
pub struct ContactCheck<'a> {
/// The list of contact IDs to be checked
#[xml(serialize_with = "serialize_contacts")]
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)]
mod tests {
use super::ContactCheck;
@ -72,12 +87,12 @@ mod tests {
let results = object.res_data().unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into());
assert_eq!(results.list[0].id, "eppdev-contact-1");
assert!(!results.list[0].available);
assert_eq!(results.list[1].id, "eppdev-contact-2");
assert!(results.list[1].available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into());
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(results.list[0].inner.id, "eppdev-contact-1");
assert!(!results.list[0].inner.available);
assert_eq!(results.list[1].inner.id, "eppdev-contact-2");
assert!(results.list[1].inner.available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,11 @@
use std::borrow::Cow;
use std::fmt;
use std::net::IpAddr;
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;
pub mod check;
@ -31,85 +32,128 @@ pub use update::DomainUpdate;
pub const XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0";
/// 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> {
/// The &lt;hostName&gt; tag
#[serde(rename = "domain:hostName", alias = "hostName")]
pub name: StringValue<'a>,
#[xml(rename = "hostName")]
pub name: Cow<'a, str>,
/// The &lt;hostAddr&gt; tags
#[serde(
rename = "domain:hostAddr",
alias = "hostAddr",
#[xml(
rename = "hostAddr",
serialize_with = "serialize_host_addrs_option",
deserialize_with = "deserialize_host_addrs_option"
)]
pub addresses: Option<Vec<IpAddr>>,
}
fn deserialize_host_addrs_option<'de, D>(de: D) -> Result<Option<Vec<IpAddr>>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let addrs = Option::<Vec<HostAddr<'static>>>::deserialize(de)?;
let addrs = match addrs {
Some(addrs) => addrs,
None => return Ok(None),
fn deserialize_host_addrs_option<'xml>(
into: &mut OptionAccumulator<Vec<IpAddr>, Vec<IpAddr>>,
field: &'static str,
deserializer: &mut Deserializer<'_, 'xml>,
) -> Result<(), instant_xml::Error> {
let mut value = <Option<Vec<HostAddr<'static>>> as FromXml<'xml>>::Accumulator::default();
<Option<Vec<HostAddr<'static>>>>::deserialize(&mut value, field, deserializer)?;
let new = match value.try_done(field)? {
Some(new) => new,
None => return Ok(()),
};
let result = addrs
.into_iter()
.map(|addr| IpAddr::from_str(&addr.address))
.collect::<Result<_, _>>();
let into = into.get_mut();
for addr in new {
match IpAddr::from_str(&addr.address) {
Ok(ip) => into.push(ip),
Err(_) => {
return Err(instant_xml::Error::UnexpectedValue(format!(
"invalid IP address '{}'",
&addr.address
)))
}
}
}
match result {
Ok(addrs) => Ok(Some(addrs)),
Err(e) => Err(serde::de::Error::custom(format!("{}", e))),
Ok(())
}
/// 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
#[derive(Serialize, Debug)]
pub struct HostAttrList<'a> {
/// The list of &lt;hostAttr&gt; tags
#[serde(rename = "domain:hostAttr", alias = "hostAttr")]
pub hosts: &'a [HostAttr<'a>],
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(())
}
/// The list of &lt;hostObj&gt; types for domain transactions. Typically under an &lt;ns&gt; tag
#[derive(Serialize, Debug)]
pub struct HostObjList<'a> {
/// The list of &lt;hostObj&gt; tags
#[serde(rename = "domain:hostObj", alias = "hostObj")]
pub hosts: &'a [StringValue<'a>],
#[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
#[xml(rename = "hostObj", ns(XMLNS))]
pub struct HostObj<'a> {
#[xml(direct)]
pub name: Cow<'a, str>,
}
/// Enum that can accept one type which corresponds to either the &lt;hostObj&gt; or &lt;hostAttr&gt;
/// list of tags
#[derive(Serialize, Debug)]
#[serde(untagged)]
pub enum HostList<'a> {
HostObjList(HostObjList<'a>),
HostAttrList(HostAttrList<'a>),
#[derive(Clone, Debug, Eq, FromXml, PartialEq, ToXml)]
#[xml(forward)]
pub enum HostInfo<'a> {
Attr(HostAttr<'a>),
Obj(HostObj<'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
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "contact", ns(XMLNS))]
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)
#[serde(rename = "type")]
#[xml(attribute, rename = "type")]
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
#[derive(Clone, Copy, Debug, Serialize)]
#[derive(Clone, Copy, Debug, ToXml)]
#[xml(rename = "period", ns(XMLNS))]
pub struct Period {
/// The interval (usually 'y' indicating years)
#[xml(attribute)]
unit: char,
/// The length of the registration, renewal or transfer period (usually in years)
#[serde(rename = "$value")]
#[xml(direct)]
length: u8,
}
@ -158,11 +202,12 @@ pub const SIX_MONTHS: Period = Period {
};
/// 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> {
/// The &lt;pw&gt; tag under &lt;authInfo&gt;
#[serde(rename = "domain:pw", alias = "pw")]
pub password: StringValue<'a>,
#[xml(rename = "pw")]
pub password: Cow<'a, str>,
}
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
use std::fmt;
use instant_xml::{FromXml, Serializer, ToXml};
use super::XMLNS;
use crate::common::{CheckResponse, NoExtension, StringValue};
use crate::common::{NoExtension, EPP_XMLNS};
use crate::request::{Command, Transaction};
use serde::Serialize;
impl<'a> Transaction<NoExtension> for DomainCheck<'a> {}
impl<'a> Command for DomainCheck<'a> {
type Response = CheckResponse;
type Response = CheckData;
const COMMAND: &'static str = "check";
}
// Request
/// Type for &lt;name&gt; elements under the domain &lt;check&gt; tag
#[derive(Serialize, Debug)]
#[derive(Debug, ToXml)]
#[xml(rename = "check", ns(XMLNS))]
struct DomainList<'a> {
#[serde(rename = "xmlns:domain")]
/// XML namespace for domain commands
xmlns: &'a str,
#[serde(rename = "domain:name")]
/// List of domains to be checked for availability
domains: Vec<StringValue<'a>>,
#[xml(rename = "name", ns(XMLNS))]
domains: &'a [&'a str],
}
#[derive(Serialize, Debug)]
struct SerializeDomainCheck<'a> {
#[serde(rename = "domain:check")]
list: DomainList<'a>,
fn serialize_domains<W: fmt::Write + ?Sized>(
domains: &[&str],
serializer: &mut Serializer<W>,
) -> Result<(), instant_xml::Error> {
DomainList { domains }.serialize(None, serializer)
}
impl<'a> From<DomainCheck<'a>> for SerializeDomainCheck<'a> {
fn from(check: DomainCheck<'a>) -> Self {
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")]
#[derive(ToXml, Debug)]
#[xml(rename = "check", ns(EPP_XMLNS))]
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],
}
// 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)]
mod tests {
use super::DomainCheck;
@ -67,15 +84,15 @@ mod tests {
#[test]
fn response() {
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.message, SUCCESS_MSG.into());
assert_eq!(result.list[0].id, "eppdev.com");
assert!(result.list[0].available);
assert_eq!(result.list[1].id, "eppdev.net");
assert!(!result.list[1].available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into());
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
assert_eq!(object.result.message, SUCCESS_MSG);
assert_eq!(result.list[0].inner.id, "eppdev.com");
assert!(result.list[0].inner.available);
assert_eq!(result.list[1].inner.id, "eppdev.net");
assert!(!result.list[1].inner.available);
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,10 +28,10 @@ impl Display for Error {
Error::Command(e) => {
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::Xml(e) => write!(f, "(de)serialization error: {}", e),
Error::Other(e) => write!(f, "error: {}", e),
Error::Xml(e) => write!(f, "(de)serialization error: {e}"),
Error::Other(e) => write!(f, "error: {e}"),
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,17 +1,4 @@
use serde::{Deserialize, Serialize};
pub mod report;
pub mod request;
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
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::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> {}
@ -18,28 +19,19 @@ impl<'a> RgpRestoreReport<'a> {
deleted_at: DateTime<Utc>,
restored_at: DateTime<Utc>,
restore_reason: &'a str,
statements: &[&'a str],
statements: &'a [&'a str],
other: &'a str,
) -> Self {
let statements = statements.iter().map(|&s| s.into()).collect();
Self {
xmlns: XMLNS,
restore: RgpRestoreReportSection {
op: "report",
report: RgpRestoreReportSectionData {
pre_data: pre_data.into(),
post_data: post_data.into(),
deleted_at: deleted_at
.to_rfc3339_opts(SecondsFormat::AutoSi, true)
.into(),
restored_at: restored_at
.to_rfc3339_opts(SecondsFormat::AutoSi, true)
.into(),
restore_reason: restore_reason.into(),
pre_data,
post_data,
deleted_at: deleted_at.to_rfc3339_opts(SecondsFormat::AutoSi, true),
restored_at: restored_at.to_rfc3339_opts(SecondsFormat::AutoSi, true),
restore_reason,
statements,
other: other.into(),
},
other,
},
}
}
@ -49,53 +41,51 @@ impl<'a> Extension for Update<RgpRestoreReport<'a>> {
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
#[derive(Serialize, Debug)]
#[derive(Debug, ToXml)]
#[xml(rename = "report", ns(XMLNS))]
pub struct RgpRestoreReportSectionData<'a> {
/// The pre-delete registration date
#[serde(rename = "rgp:preData")]
pre_data: StringValue<'a>,
#[xml(rename = "preData")]
pre_data: &'a str,
/// The post-delete registration date
#[serde(rename = "rgp:postData")]
post_data: StringValue<'a>,
#[xml(rename = "postData")]
post_data: &'a str,
/// The domain deletion date
#[serde(rename = "rgp:delTime")]
deleted_at: StringValue<'a>,
#[xml(rename = "delTime")]
deleted_at: String,
/// The domain restore request date
#[serde(rename = "rgp:resTime")]
restored_at: StringValue<'a>,
#[xml(rename = "resTime")]
restored_at: String,
/// The reason for domain restoration
#[serde(rename = "rgp:resReason")]
restore_reason: StringValue<'a>,
#[xml(rename = "resReason")]
restore_reason: &'a str,
/// The registrar's statements on the domain restoration
#[serde(rename = "rgp:statement")]
statements: Vec<StringValue<'a>>,
#[xml(rename = "statement")]
statements: &'a [&'a str],
/// Other remarks for domain restoration
#[serde(rename = "rgp:other")]
other: StringValue<'a>,
#[xml(rename = "other")]
other: &'a str,
}
/// Type corresponding to the &lt;restore&gt; section in the rgp restore extension
#[derive(Serialize, Debug)]
pub struct RgpRestoreReportSection<'a> {
#[derive(Debug, ToXml)]
/// Type for EPP XML &lt;check&gt; command for domains
#[xml(rename = "restore", ns(XMLNS))]
pub struct RgpRestoreReport<'a> {
/// The value of the op attribute for the &lt;restore&gt; tag
#[xml(attribute)]
op: &'a str,
/// Data for the &lt;report&gt; tag
#[serde(rename = "rgp:report")]
#[xml(rename = "rgp:report")]
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)]
mod tests {
use std::str::FromStr;

View File

@ -1,75 +1,86 @@
//! Types for EPP RGP restore request
use instant_xml::{FromXml, ToXml};
use crate::{
domain::{info::DomainInfo, update::DomainUpdate},
request::{Extension, Transaction},
};
use serde::{Deserialize, Serialize};
use super::{Update, XMLNS};
use super::XMLNS;
impl<'a> Transaction<Update<RgpRestoreRequest<'a>>> for DomainUpdate<'a> {}
impl<'a> Transaction<Update<RgpRestoreRequest<'a>>> for DomainInfo<'a> {}
impl<'a> Extension for Update<RgpRestoreRequest<'a>> {
type Response = Update<RgpRequestResponse>;
type Response = RgpRequestResponse;
}
// Request
/// Type corresponding to the &lt;restore&gt; tag for an rgp restore request
#[derive(Serialize, Debug)]
pub struct RgpRestoreRequestData<'a> {
/// The value of the op attribute in the &lt;restore&gt; tag
pub op: &'a str,
#[derive(Debug, FromXml, ToXml)]
#[xml(rename = "update", ns(XMLNS))]
pub struct Update<T> {
pub data: T,
}
#[derive(Serialize, Debug)]
/// Type for EPP XML &lt;check&gt; command for domains
/// Type corresponding to the &lt;restore&gt; tag for an rgp restore request
#[derive(Debug, ToXml)]
#[xml(rename = "restore", ns(XMLNS))]
pub struct RgpRestoreRequest<'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: RgpRestoreRequestData<'a>,
/// The value of the op attribute in the &lt;restore&gt; tag
#[xml(attribute)]
pub op: &'a str,
}
impl Default for RgpRestoreRequest<'static> {
fn default() -> Self {
Self {
xmlns: XMLNS,
restore: RgpRestoreRequestData { op: "request" },
}
Self { op: "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 {
/// The domain RGP status
#[serde(rename = "s")]
#[xml(rename = "s", attribute)]
pub status: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename = "upData")]
#[derive(Debug, FromXml)]
#[xml(rename = "upData", ns(XMLNS))]
/// 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
#[serde(rename = "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)]
mod tests {
use super::{RgpRestoreRequest, Update};
use crate::domain::info::DomainInfo;
use crate::domain::update::{DomainChangeInfo, DomainUpdate};
use crate::extensions::rgp::request::RgpRequestResponse;
use crate::response::ResultCode;
use crate::tests::{assert_serialized, response_from_file_with_ext, SUCCESS_MSG, SVTRID};
@ -102,9 +113,15 @@ mod tests {
let ext = object.extension.unwrap();
assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
assert_eq!(object.result.message, SUCCESS_MSG.into());
assert_eq!(ext.data.rgp_status[0].status, "pendingRestore".to_string());
assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
assert_eq!(object.result.message, SUCCESS_MSG);
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]
@ -114,7 +131,12 @@ mod tests {
);
let ext = object.extension.unwrap();
assert_eq!(ext.data.rgp_status[0].status, "addPeriod");
assert_eq!(ext.data.rgp_status[1].status, "renewPeriod");
let data = match ext.data {
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 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
#[derive(Debug, PartialEq, Serialize)]
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,
}
}
}
#[derive(Debug, PartialEq, ToXml)]
#[xml(rename = "hello", ns(EPP_XMLNS))]
pub(crate) struct Hello;
// Response
@ -36,193 +21,235 @@ pub struct ServiceMenu {
}
/// 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 {
pub version: StringValue<'static>,
pub lang: StringValue<'static>,
#[serde(rename = "objURI")]
pub obj_uris: Vec<StringValue<'static>>,
#[serde(rename = "svcExtension")]
pub version: String,
pub lang: String,
#[xml(rename = "objURI")]
pub obj_uris: Vec<String>,
#[xml(rename = "svcExtension")]
pub svc_ext: Option<ServiceExtension<'static>>,
}
impl<'a, 'de: 'a> Deserialize<'de> for ServiceMenu {
/// Deserializes the <svcMenu> data to the `ServiceMenu` type
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let flattened_svc_menu = FlattenedServiceMenu::deserialize(deserializer)?;
impl<'xml> FromXml<'xml> for ServiceMenu {
fn matches(id: instant_xml::Id<'_>, field: Option<instant_xml::Id<'_>>) -> bool {
FlattenedServiceMenu::matches(id, field)
}
let svc_menu = ServiceMenu {
options: Options {
version: flattened_svc_menu.version,
lang: flattened_svc_menu.lang,
},
services: Services {
obj_uris: flattened_svc_menu.obj_uris,
svc_ext: flattened_svc_menu.svc_ext,
},
/// Deserializes the <svcMenu> data to the `ServiceMenu` type
fn deserialize<'cx>(
into: &mut Self::Accumulator,
field: &'static str,
deserializer: &mut Deserializer<'cx, 'xml>,
) -> Result<(), instant_xml::Error> {
dbg!(&into);
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
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "all", ns(EPP_XMLNS))]
pub struct All;
/// 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;
/// 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;
/// 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;
/// 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;
/// 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;
/// Type corresponding to possible <retention> type values
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum AccessType {
/// Data for the <all> tag
#[serde(rename = "all")]
All(All),
/// Data for the <none> tag
#[serde(rename = "none")]
NoAccess(NoAccess),
/// Data for the <null> tag
#[serde(rename = "null")]
Null(Null),
/// Data for the <personal> tag
#[serde(rename = "personal")]
Personal(Personal),
/// Data for the <personalAndOther> tag
#[serde(rename = "personalAndOther")]
PersonalAndOther(PersonalAndOther),
/// Data for the <other> tag
#[serde(rename = "other")]
Other(Other),
}
/// Type corresponding to <access> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "access", ns(EPP_XMLNS))]
pub struct Access {
#[serde(flatten)]
pub ty: AccessType,
inner: AccessType,
}
/// Type corresponding to possible <purpose> type values
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum PurposeType {
/// Data for the <admin> tag
#[serde(rename = "admin")]
Admin,
Admin(Admin),
/// Data for the <contact> tag
#[serde(rename = "contact")]
Contact,
Contact(Contact),
/// Data for the <prov> tag
#[serde(rename = "prov")]
Prov,
Prov(Prov),
/// 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
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "purpose", ns(EPP_XMLNS))]
pub struct Purpose {
#[serde(rename = "$value")]
pub purpose: Vec<PurposeType>,
}
/// Type corresponding to possible <purpose> type values
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum RecipientType {
/// Data for the <other> tag
#[serde(rename = "other")]
Other,
Other(Other),
/// Data for the <ours> tag
#[serde(rename = "ours")]
Ours,
Ours(Ours),
/// Data for the <public> tag
#[serde(rename = "public")]
Public,
Public(Public),
/// Data for the <same> tag
#[serde(rename = "same")]
Same,
Same(Same),
/// 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
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "recipient", ns(EPP_XMLNS))]
pub struct Recipient {
#[serde(rename = "$value")]
pub recipient: Vec<RecipientType>,
}
/// 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;
/// 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;
/// 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;
/// 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;
/// 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;
/// 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 {
/// Data for the <business> tag
#[serde(rename = "business")]
Business(Business),
/// Data for the <indefinite> tag
#[serde(rename = "indefinite")]
Indefinite(Indefinite),
/// Data for the <legal> tag
#[serde(rename = "legal")]
Legal(Legal),
/// Data for the <none> tag
#[serde(rename = "none")]
No(No),
None(No),
/// Data for the <stated> tag
#[serde(rename = "stated")]
Stated(Stated),
}
/// Type corresponding to <retention> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "retention", ns(EPP_XMLNS))]
pub struct Retention {
#[serde(flatten)]
pub ty: RetentionType,
inner: RetentionType,
}
/// 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 {
/// Data for the <purpose> tag
pub purpose: Purpose,
@ -233,39 +260,35 @@ pub struct Statement {
}
/// Type corresponding to <absolute> value in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct Absolute {
#[serde(rename = "$value")]
pub absolute: StringValue<'static>,
}
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "absolute", ns(EPP_XMLNS))]
pub struct Absolute(String);
/// Type corresponding to <relative> value in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)]
pub struct Relative {
#[serde(rename = "$value")]
pub relative: StringValue<'static>,
}
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "relative", ns(EPP_XMLNS))]
pub struct Relative(String);
/// Type corresponding to possible <expiry> type values
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(forward)]
pub enum ExpiryType {
/// Data for the <absolute> tag
#[serde(rename = "absolute")]
Absolute(Absolute),
/// Data for the <relative> tag
#[serde(rename = "relative")]
Relative(Relative),
}
/// Type corresponding to <expiry> in the EPP greeting XML
#[derive(Deserialize, Debug, Eq, PartialEq)]
/// Type corresponding to possible <expiry> type values
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "expiry", ns(EPP_XMLNS))]
pub struct Expiry {
#[serde(flatten)]
pub ty: ExpiryType,
inner: ExpiryType,
}
/// 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 {
/// Data for the <access> tag
pub access: Access,
@ -275,42 +298,34 @@ pub struct Dcp {
pub expiry: Option<Expiry>,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
/// 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 {
/// The service ID
#[serde(rename = "svID")]
#[xml(rename = "svID")]
pub service_id: String,
/// The date from the EPP server
#[serde(rename = "svDate")]
#[xml(rename = "svDate")]
pub service_date: DateTime<Utc>,
/// Data under the <svcMenu> element
#[serde(rename = "svcMenu")]
pub svc_menu: ServiceMenu,
/// Data under the <dcp> element
pub dcp: Dcp,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[serde(rename = "epp")]
pub struct GreetingDocument {
#[serde(rename = "greeting")]
pub data: Greeting,
}
#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
use super::{ExpiryType, GreetingDocument, HelloDocument, Relative};
use super::{ExpiryType, Greeting, Hello, Relative};
use crate::tests::get_xml;
use crate::xml;
#[test]
fn hello() {
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);
}
@ -318,19 +333,18 @@ mod tests {
#[test]
fn greeting() {
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!(
object.data.service_date,
object.service_date,
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.data.svc_menu.options.lang, "en".into());
assert_eq!(object.data.svc_menu.services.obj_uris.len(), 4);
assert_eq!(object.svc_menu.options.version, "1.0");
assert_eq!(object.svc_menu.options.lang, "en");
assert_eq!(object.svc_menu.services.obj_uris.len(), 4);
assert_eq!(
object
.data
.svc_menu
.services
.svc_ext
@ -340,12 +354,10 @@ mod tests {
.len(),
5
);
assert_eq!(object.data.dcp.statement.len(), 2);
assert_eq!(object.dcp.statement.len(), 2);
assert_eq!(
object.data.dcp.expiry.unwrap().ty,
ExpiryType::Relative(Relative {
relative: "P1M".into()
})
object.dcp.expiry.unwrap().inner,
ExpiryType::Relative(Relative("P1M".into()))
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@
//! use std::time::Duration;
//!
//! use epp_client::EppClient;
//! use epp_client::domain::DomainCheck;
//! use epp_client::domain::check::DomainCheck;
//! use epp_client::login::Login;
//!
//! #[tokio::main]
@ -79,9 +79,11 @@
//! let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] };
//! let response = client.transact(&domain_check, "transaction-id").await.unwrap();
//!
//! response.res_data.unwrap().list
//! response.res_data()
//! .unwrap()
//! .list
//! .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 {
use std::borrow::Cow;
use std::fmt;
use std::net::IpAddr;
use instant_xml::{FromXml, Serializer, ToXml};
pub mod check;
pub use check::HostCheck;
@ -129,6 +137,53 @@ pub mod host {
pub use update::HostUpdate;
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 {

View File

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

View File

@ -1,9 +1,9 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use instant_xml::{FromXml, ToXml};
use crate::{
common::NoExtension,
common::{NoExtension, EPP_XMLNS},
request::{Command, Transaction},
};
@ -14,8 +14,9 @@ impl Command for 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
#[xml(rename = "logout", ns(EPP_XMLNS))]
pub struct Logout;
#[cfg(test)]
@ -40,9 +41,9 @@ mod tests {
);
assert_eq!(
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.server_tr_id, SVTRID.into());
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID);
}
}

View File

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

View File

@ -1,9 +1,10 @@
//! Types for EPP requests
use serde::{de::DeserializeOwned, ser::SerializeStruct, ser::Serializer, Serialize};
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_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
pub trait Transaction<Ext: Extension>: Command + Sized {}
pub trait Command: Serialize + Debug {
type Response: DeserializeOwned + Debug;
pub trait Command: ToXml + Debug {
type Response: FromXmlOwned + Debug;
const COMMAND: &'static str;
}
pub trait Extension: Serialize + Debug {
type Response: DeserializeOwned + Debug;
pub trait Extension: ToXml + Debug {
type Response: FromXmlOwned + Debug;
}
#[derive(Debug, PartialEq)]
/// Type corresponding to the &lt;command&gt; tag in an EPP XML request
/// with an &lt;extension&gt; tag
struct CommandWrapper<'a, D, E> {
pub(crate) struct CommandWrapper<'a, D, E> {
pub command: &'static str,
/// The instance that will be used to populate the &lt;command&gt; tag
pub data: &'a D,
/// The client TRID
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> {
/// Serializes the generic type T to the proper XML tag (set by the `#[element_name(name = <tagname>)]` attribute) for the request
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,
{
impl<'a, E: Extension, D: Transaction<E>> CommandWrapper<'a, D, E> {
pub(crate) fn new(data: &'a D, extension: Option<&'a E>, client_tr_id: &'a str) -> Self {
Self {
xmlns: EPP_XMLNS,
command: CommandWrapper {
command: Cmd::COMMAND,
command: D::COMMAND,
data,
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 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
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "undef", ns(EPP_XMLNS))]
pub struct Undef;
/// 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 {
/// The XML namespace for the <value> tag
#[serde(rename = "xmlns:epp")]
xmlns: String,
/// The <undef> element
pub undef: Undef,
}
/// 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 {
/// Data under the <value> tag
pub value: ResultValue,
/// Data under the <reason> tag
pub reason: StringValue<'static>,
pub reason: String,
}
/// 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 {
/// The result code
#[xml(attribute)]
pub code: ResultCode,
/// The result message
#[serde(rename = "msg")]
pub message: StringValue<'static>,
#[xml(rename = "msg")]
pub message: String,
/// Data under the <extValue> tag
#[serde(rename = "extValue")]
pub ext_value: Option<ExtValue>,
}
@ -136,13 +137,37 @@ impl ResultCode {
}
}
impl<'de> Deserialize<'de> for ResultCode {
fn deserialize<D>(deserializer: D) -> Result<ResultCode, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_u16(ResultCodeVisitor)
impl<'xml> FromXml<'xml> for ResultCode {
fn matches(id: instant_xml::Id<'_>, field: Option<instant_xml::Id<'_>>) -> bool {
match field {
Some(field) => id == field,
None => false,
}
}
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;
@ -166,71 +191,82 @@ impl<'de> serde::de::Visitor<'de> for ResultCodeVisitor {
}
/// 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 {
/// The client TRID
#[serde(rename = "clTRID")]
pub client_tr_id: Option<StringValue<'static>>,
#[xml(rename = "clTRID")]
pub client_tr_id: Option<String>,
/// The server TRID
#[serde(rename = "svTRID")]
pub server_tr_id: StringValue<'static>,
#[xml(rename = "svTRID")]
pub server_tr_id: String,
}
/// 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 {
/// The message count
#[xml(attribute)]
pub count: u32,
/// The message ID
#[xml(attribute)]
pub id: String,
/// The message date
#[serde(rename = "qDate")]
#[xml(rename = "qDate")]
pub date: Option<DateTime<Utc>>,
/// The message text
#[serde(rename = "msg")]
pub message: Option<StringValue<'static>>,
#[xml(rename = "msg")]
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
/// containing an &lt;extension&gt; tag
#[xml(rename = "response", ns(EPP_XMLNS))]
pub struct Response<D, E> {
/// Data under the <result> tag
pub result: EppResult,
/// Data under the <msgQ> tag
#[serde(rename = "msgQ")]
#[xml(rename = "msgQ")]
pub message_queue: Option<MessageQueue>,
#[serde(rename = "resData")]
/// 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
pub extension: Option<E>,
pub extension: Option<Extension<E>>,
/// Data under the <trID> tag
#[serde(rename = "trID")]
pub tr_ids: ResponseTRID,
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[serde(rename = "epp")]
pub struct ResponseDocument<D, E> {
#[serde(rename = "response")]
pub data: Response<D, E>,
#[derive(Debug, Eq, FromXml, PartialEq)]
#[xml(rename = "resData", ns(EPP_XMLNS))]
pub struct ResponseData<D> {
data: D,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
#[serde(rename = "epp")]
pub struct ResultDocument {
#[serde(rename = "response")]
pub data: ResponseStatus,
impl<D> ResponseData<D> {
pub fn into_inner(self) -> D {
self.data
}
}
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[derive(Debug, FromXml, PartialEq)]
/// 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
#[xml(rename = "response", ns(EPP_XMLNS))]
pub struct ResponseStatus {
/// Data under the <result> tag
pub result: EppResult,
#[serde(rename = "trID")]
#[xml(rename = "trID")]
/// Data under the <trID> tag
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
pub fn res_data(&self) -> Option<&T> {
match &self.res_data {
Some(res_data) => Some(res_data),
Some(res_data) => Some(&res_data.data),
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
pub fn message_queue(&self) -> Option<&MessageQueue> {
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)]
mod tests {
use super::{ResultCode, ResultDocument};
use super::{ResponseStatus, ResultCode};
use crate::tests::{get_xml, CLTRID, SVTRID};
use crate::xml;
#[test]
fn error() {
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.data.result.message, "Object does not exist".into());
assert_eq!(object.result.code, ResultCode::ObjectDoesNotExist);
assert_eq!(object.result.message, "Object does not exist");
assert_eq!(
object.data.result.ext_value.unwrap().reason,
"545 Object not found".into()
object.result.ext_value.unwrap().reason,
"545 Object not found"
);
assert_eq!(object.data.tr_ids.client_tr_id.unwrap(), CLTRID.into());
assert_eq!(object.data.tr_ids.server_tr_id, SVTRID.into());
assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
assert_eq!(object.tr_ids.server_tr_id, SVTRID);
}
}

View File

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

View File

@ -1,19 +1,29 @@
//! 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;
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!(
"{}\r\n{}",
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> {
quick_xml::de::from_str(xml).map_err(|e| Error::Xml(e.into()))
pub(crate) fn deserialize<T: FromXmlOwned>(xml: &str) -> Result<T, Error> {
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);
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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,26 +2,26 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name>
<domain:chg/>
</domain:update>
<update xmlns="urn:ietf:params:xml:ns:domain-1.0">
<name>eppdev.com</name>
<chg></chg>
</update>
</update>
<extension>
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:restore op="report">
<rgp:report>
<rgp:preData>Pre-delete registration data goes here. Both XML and free text are allowed.</rgp:preData>
<rgp:postData>Post-restore registration data goes here. Both XML and free text are allowed.</rgp:postData>
<rgp:delTime>2021-07-10T22:00:00Z</rgp:delTime>
<rgp:resTime>2021-07-20T22:00:00Z</rgp:resTime>
<rgp:resReason>Registrant error.</rgp: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>
<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>
<rgp:other>Supporting information goes here.</rgp:other>
</rgp:report>
</rgp:restore>
</rgp:update>
<update xmlns="urn:ietf:params:xml:ns:rgp-1.0">
<restore op="report">
<report>
<preData>Pre-delete registration data goes here. Both XML and free text are allowed.</preData>
<postData>Post-restore registration data goes here. Both XML and free text are allowed.</postData>
<delTime>2021-07-10T22:00:00Z</delTime>
<resTime>2021-07-20T22:00:00Z</resTime>
<resReason>Registrant error.</resReason>
<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>
<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>
<other>Supporting information goes here.</other>
</report>
</restore>
</update>
</extension>
<clTRID>cltrid:1626454866</clTRID>
</command>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,15 +21,6 @@
<host:upDate>2021-12-01T22:40:48Z</host:upDate>
</host:infData>
</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>
<clTRID>cltrid:1626454866</clTRID>
<svTRID>RO-6879-1627224678242975</svTRID>