diff --git a/src/common.rs b/src/common.rs index 973b7f2..bbc09c9 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,8 @@ //! Common data types included in EPP Requests and Responses -use std::{borrow::Cow, fmt::Display}; +use std::{borrow::Cow, fmt::Display, net::IpAddr}; +use serde::ser::SerializeSeq; use serde::{Deserialize, Serialize}; use crate::request::Extension; @@ -148,37 +149,42 @@ pub struct Services<'a> { /// The <hostAddr> types domain or host transactions #[derive(Serialize, Deserialize, Debug)] -pub struct HostAddr<'a> { +pub(crate) struct HostAddr<'a> { #[serde(rename = "ip")] pub ip_version: Option>, #[serde(rename = "$value")] pub address: Cow<'a, str>, } -impl<'a> HostAddr<'a> { - /// Creates a 'v4' type HostAddr (mostly useful when you don't want to include an 'ip' attr in the XML) - pub fn new(ip_version: &'a str, address: &'a str) -> Self { +impl From<&IpAddr> for HostAddr<'static> { + fn from(addr: &IpAddr) -> Self { Self { - ip_version: Some(ip_version.into()), - address: address.into(), + ip_version: Some(match addr { + IpAddr::V4(_) => "v4".into(), + IpAddr::V6(_) => "v6".into(), + }), + address: addr.to_string().into(), } } +} - /// Creates a 'v4' type HostAddr - pub fn new_v4(address: &'a str) -> HostAddr { - HostAddr { - ip_version: Some("v4".into()), - address: address.into(), - } - } +pub(crate) fn serialize_host_addrs_option, S>( + addrs: &Option, + ser: S, +) -> Result +where + S: serde::ser::Serializer, +{ + let addrs = match addrs { + Some(addrs) => addrs.as_ref(), + None => return ser.serialize_none(), + }; - /// Creates a 'v6' type HostAddr - pub fn new_v6(address: &'a str) -> HostAddr { - HostAddr { - ip_version: Some("v6".into()), - address: address.into(), - } + let mut seq = ser.serialize_seq(Some(addrs.len()))?; + for addr in addrs { + seq.serialize_element(&HostAddr::from(addr))?; } + seq.end() } /// The <status> type on contact transactions diff --git a/src/domain.rs b/src/domain.rs index b392247..ce842cb 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -1,8 +1,10 @@ use std::borrow::Cow; +use std::net::IpAddr; +use std::str::FromStr; use serde::{Deserialize, Serialize}; -use crate::common::{HostAddr, StringValue}; +use crate::common::{serialize_host_addrs_option, HostAddr, StringValue}; use crate::Error; pub mod check; @@ -35,8 +37,34 @@ pub struct HostAttr<'a> { #[serde(rename = "domain:hostName", alias = "hostName")] pub name: StringValue<'a>, /// The <hostAddr> tags - #[serde(rename = "domain:hostAddr", alias = "hostAddr")] - pub addresses: Option>>, + #[serde( + rename = "domain:hostAddr", + alias = "hostAddr", + serialize_with = "serialize_host_addrs_option", + deserialize_with = "deserialize_host_addrs_option" + )] + pub addresses: Option>, +} + +fn deserialize_host_addrs_option<'de, D>(de: D) -> Result>, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + let addrs = Option::>>::deserialize(de)?; + let addrs = match addrs { + Some(addrs) => addrs, + None => return Ok(None), + }; + + let result = addrs + .into_iter() + .map(|addr| IpAddr::from_str(&addr.address)) + .collect::>(); + + match result { + Ok(addrs) => Ok(Some(addrs)), + Err(e) => Err(serde::de::Error::custom(format!("{}", e))), + } } /// The list of <hostAttr> types for domain transactions. Typically under an <ns> tag diff --git a/src/domain/create.rs b/src/domain/create.rs index 6a7b14f..6897a9c 100644 --- a/src/domain/create.rs +++ b/src/domain/create.rs @@ -104,11 +104,12 @@ pub struct DomainCreateResponse { #[cfg(test)] mod tests { use super::{DomainContact, DomainCreate, HostList, Period}; - use crate::common::{HostAddr, NoExtension}; + use crate::common::NoExtension; use crate::domain::{HostAttr, HostAttrList, HostObjList}; use crate::request::Transaction; use crate::response::ResultCode; use crate::tests::{get_xml, CLTRID, SUCCESS_MSG, SVTRID}; + use std::net::IpAddr; #[test] fn command() { @@ -208,8 +209,8 @@ mod tests { HostAttr { name: "ns2.eppdev-1.com".into(), addresses: Some(vec![ - HostAddr::new_v4("177.232.12.58"), - HostAddr::new_v6("2404:6800:4001:801::200e"), + IpAddr::from([177, 232, 12, 58]), + IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e]), ]), }, ]; diff --git a/src/host/create.rs b/src/host/create.rs index 616afc2..4891709 100644 --- a/src/host/create.rs +++ b/src/host/create.rs @@ -1,7 +1,9 @@ //! Types for EPP host create request +use std::net::IpAddr; + use super::XMLNS; -use crate::common::{HostAddr, NoExtension, StringValue}; +use crate::common::{serialize_host_addrs_option, NoExtension, StringValue}; use crate::request::{Command, Transaction}; use serde::{Deserialize, Serialize}; @@ -13,7 +15,7 @@ impl<'a> Command for HostCreate<'a> { } impl<'a> HostCreate<'a> { - pub fn new(host: &'a str, addresses: Option<&'a [HostAddr]>) -> Self { + pub fn new(host: &'a str, addresses: Option<&'a [IpAddr]>) -> Self { Self { host: HostCreateRequestData { xmlns: XMLNS, @@ -36,8 +38,8 @@ pub struct HostCreateRequestData<'a> { #[serde(rename = "host:name")] pub name: StringValue<'a>, /// The list of IP addresses for the host - #[serde(rename = "host:addr")] - pub addresses: Option<&'a [HostAddr<'a>]>, + #[serde(rename = "host:addr", serialize_with = "serialize_host_addrs_option")] + pub addresses: Option<&'a [IpAddr]>, } #[derive(Serialize, Debug)] @@ -70,8 +72,8 @@ pub struct HostCreateResponse { #[cfg(test)] mod tests { - use super::HostCreate; - use crate::common::{HostAddr, NoExtension}; + use super::{HostCreate, IpAddr}; + use crate::common::NoExtension; use crate::request::Transaction; use crate::response::ResultCode; use crate::tests::{get_xml, CLTRID, SUCCESS_MSG, SVTRID}; @@ -81,8 +83,8 @@ mod tests { let xml = get_xml("request/host/create.xml").unwrap(); let addresses = &[ - HostAddr::new("v4", "29.245.122.14"), - HostAddr::new("v6", "2404:6800:4001:801::200e"), + IpAddr::from([29, 245, 122, 14]), + IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e]), ]; let object = HostCreate::new("host1.eppdev-1.com", Some(addresses)); diff --git a/src/host/info.rs b/src/host/info.rs index f0bc9c0..614029f 100644 --- a/src/host/info.rs +++ b/src/host/info.rs @@ -1,5 +1,8 @@ //! Types for EPP host info request +use std::net::IpAddr; +use std::str::FromStr; + use super::XMLNS; use crate::common::{HostAddr, NoExtension, ObjectStatus, StringValue}; use crate::request::{Command, Transaction}; @@ -57,8 +60,8 @@ pub struct HostInfoResponseData { #[serde(rename = "status")] pub statuses: Vec>, /// The list of host IP addresses - #[serde(rename = "addr")] - pub addresses: Vec>, + #[serde(rename = "addr", deserialize_with = "deserialize_host_addrs")] + pub addresses: Vec, /// The epp user to whom the host belongs #[serde(rename = "clID")] pub client_id: StringValue<'static>, @@ -79,6 +82,18 @@ pub struct HostInfoResponseData { pub transferred_at: Option>, } +fn deserialize_host_addrs<'de, D>(de: D) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + let addrs = Vec::>::deserialize(de)?; + addrs + .into_iter() + .map(|addr| IpAddr::from_str(&addr.address)) + .collect::>() + .map_err(|e| serde::de::Error::custom(format!("{}", e))) +} + /// Type that represents the <resData> tag for host info response #[derive(Deserialize, Debug)] pub struct HostInfoResponse { @@ -89,7 +104,7 @@ pub struct HostInfoResponse { #[cfg(test)] mod tests { - use super::HostInfo; + use super::{HostInfo, IpAddr}; use crate::common::NoExtension; use crate::request::Transaction; use crate::response::ResultCode; @@ -122,20 +137,12 @@ mod tests { 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.addresses[0].ip_version.as_ref().unwrap()), - "v4".to_string() + result.info_data.addresses[0], + IpAddr::from([29, 245, 122, 14]) ); assert_eq!( - result.info_data.addresses[0].address, - "29.245.122.14".to_string() - ); - assert_eq!( - *(result.info_data.addresses[1].ip_version.as_ref().unwrap()), - "v6".to_string() - ); - assert_eq!( - result.info_data.addresses[1].address, - "2404:6800:4001:0801:0000:0000:0000:200e".to_string() + result.info_data.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()); diff --git a/src/host/update.rs b/src/host/update.rs index b89821d..a1e0f5f 100644 --- a/src/host/update.rs +++ b/src/host/update.rs @@ -1,7 +1,9 @@ //! Types for EPP host update request +use std::net::IpAddr; + use super::XMLNS; -use crate::common::{HostAddr, NoExtension, ObjectStatus, StringValue}; +use crate::common::{serialize_host_addrs_option, NoExtension, ObjectStatus, StringValue}; use crate::request::{Command, Transaction}; use serde::Serialize; @@ -53,8 +55,8 @@ pub struct HostChangeInfo<'a> { #[derive(Serialize, Debug)] pub struct HostAddRemove<'a> { /// The IP addresses to be added to or removed from the host - #[serde(rename = "host:addr")] - pub addresses: Option<&'a [HostAddr<'a>]>, + #[serde(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>]>, @@ -90,8 +92,9 @@ pub struct HostUpdate<'a> { #[cfg(test)] mod tests { + use super::IpAddr; use super::{HostAddRemove, HostChangeInfo, HostUpdate}; - use crate::common::{HostAddr, NoExtension, ObjectStatus}; + use crate::common::{NoExtension, ObjectStatus}; use crate::request::Transaction; use crate::response::ResultCode; use crate::tests::{get_xml, CLTRID, SUCCESS_MSG, SVTRID}; @@ -100,7 +103,9 @@ mod tests { fn command() { let xml = get_xml("request/host/update.xml").unwrap(); - let addr = &[HostAddr::new("v6", "2404:6800:4001:801::200e")]; + let addr = &[IpAddr::from([ + 0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e, + ])]; let add = HostAddRemove { addresses: Some(addr), diff --git a/src/message/poll.rs b/src/message/poll.rs index 676f5e1..e07dcf4 100644 --- a/src/message/poll.rs +++ b/src/message/poll.rs @@ -55,6 +55,7 @@ mod tests { use crate::request::Transaction; use crate::response::ResultCode; use crate::tests::{get_xml, CLTRID, SVTRID}; + use std::net::IpAddr; #[test] fn command() { @@ -142,7 +143,10 @@ mod tests { assert_eq!(host.roid, "1234".into()); assert!(host.statuses.iter().any(|s| s.status == "ok")); - assert!(host.addresses.iter().any(|a| a.address == *"1.1.1.1")); + 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.created_at, "2021-12-01T22:40:48Z".into());