1use std::net::IpAddr;
4use std::str::FromStr;
5
6use chrono::{DateTime, Utc};
7use instant_xml::{FromXml, ToXml};
8
9use super::{HostAddr, Status, XMLNS};
10use crate::common::{NoExtension, EPP_XMLNS};
11use crate::request::{Command, Transaction};
12
13impl<'a> Transaction<NoExtension> for HostInfo<'a> {}
14
15impl<'a> Command for HostInfo<'a> {
16 type Response = InfoData;
17 const COMMAND: &'static str = "info";
18}
19
20impl<'a> HostInfo<'a> {
21 pub fn new(name: &'a str) -> Self {
22 Self {
23 info: HostInfoRequestData { name },
24 }
25 }
26}
27
28#[derive(Debug, ToXml)]
32#[xml(rename = "info", ns(XMLNS))]
33pub struct HostInfoRequestData<'a> {
34 name: &'a str,
36}
37
38#[derive(Debug, ToXml)]
40#[xml(rename = "info", ns(EPP_XMLNS))]
41pub struct HostInfo<'a> {
42 #[xml(rename = "host:info")]
44 info: HostInfoRequestData<'a>,
45}
46
47#[derive(Debug, FromXml)]
51#[xml(rename = "infData", ns(XMLNS))]
52pub struct InfoData {
53 pub name: String,
55 pub roid: String,
57 #[xml(rename = "status")]
59 pub statuses: Vec<Status>,
60 #[xml(rename = "addr", deserialize_with = "deserialize_host_addrs")]
62 pub addresses: Vec<IpAddr>,
63 #[xml(rename = "clID")]
65 pub client_id: String,
66 #[xml(rename = "crID")]
68 pub creator_id: String,
69 #[xml(rename = "crDate")]
71 pub created_at: DateTime<Utc>,
72 #[xml(rename = "upID")]
74 pub updater_id: Option<String>,
75 #[xml(rename = "upDate")]
77 pub updated_at: Option<DateTime<Utc>>,
78 #[xml(rename = "trDate")]
80 pub transferred_at: Option<DateTime<Utc>>,
81}
82
83fn deserialize_host_addrs(
84 into: &mut Vec<IpAddr>,
85 field: &'static str,
86 deserializer: &mut instant_xml::de::Deserializer<'_, '_>,
87) -> Result<(), instant_xml::Error> {
88 let mut addrs = Vec::new();
89 Vec::<HostAddr<'static>>::deserialize(&mut addrs, field, deserializer)?;
90
91 for addr in addrs {
92 match IpAddr::from_str(&addr.address) {
93 Ok(ip) => into.push(ip),
94 Err(_) => {
95 return Err(instant_xml::Error::UnexpectedValue(format!(
96 "invalid IP address '{}'",
97 &addr.address
98 )))
99 }
100 }
101 }
102
103 Ok(())
104}
105
106#[cfg(test)]
118mod tests {
119 use chrono::{TimeZone, Utc};
120
121 use super::{HostInfo, IpAddr};
122 use crate::host::Status;
123 use crate::response::ResultCode;
124 use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
125
126 #[test]
127 fn command() {
128 let object = HostInfo::new("ns1.eppdev-1.com");
129 assert_serialized("request/host/info.xml", &object);
130 }
131
132 #[test]
133 fn response() {
134 let object = response_from_file::<HostInfo>("response/host/info.xml");
135 let result = object.res_data().unwrap();
136
137 assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
138 assert_eq!(object.result.message, SUCCESS_MSG);
139 assert_eq!(result.name, "host2.eppdev-1.com");
140 assert_eq!(result.roid, "UNDEF-ROID");
141 assert_eq!(result.statuses[0], Status::Ok);
142 assert_eq!(result.addresses[0], IpAddr::from([29, 245, 122, 14]));
143 assert_eq!(
144 result.addresses[1],
145 IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e])
146 );
147 assert_eq!(result.client_id, "eppdev");
148 assert_eq!(result.creator_id, "creator");
149 assert_eq!(
150 result.created_at,
151 Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).unwrap()
152 );
153 assert_eq!(*(result.updater_id.as_ref().unwrap()), "creator");
154 assert_eq!(
155 result.updated_at,
156 Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).single()
157 );
158 assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
159 assert_eq!(object.tr_ids.server_tr_id, SVTRID);
160 }
161}