1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Types for EPP host info request

use std::net::IpAddr;
use std::str::FromStr;

use chrono::{DateTime, Utc};
use instant_xml::{FromXml, ToXml};

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 = InfoData;
    const COMMAND: &'static str = "info";
}

impl<'a> HostInfo<'a> {
    pub fn new(name: &'a str) -> Self {
        Self {
            info: HostInfoRequestData { name },
        }
    }
}

// Request

/// Type for data under the host `<info>` tag
#[derive(Debug, ToXml)]
#[xml(rename = "info", ns(XMLNS))]
pub struct HostInfoRequestData<'a> {
    /// The name of the host to be queried
    name: &'a str,
}

/// Type for EPP XML `<info>` 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
    #[xml(rename = "host:info")]
    info: HostInfoRequestData<'a>,
}

// Response

/// Type that represents the `<infData>` tag for host info response
#[derive(Debug, FromXml)]
#[xml(rename = "infData", ns(XMLNS))]
pub struct InfoData {
    /// The host name
    pub name: String,
    /// The host ROID
    pub roid: String,
    /// The list of host statuses
    #[xml(rename = "status")]
    pub statuses: Vec<Status>,
    /// The list of host IP addresses
    #[xml(rename = "addr", deserialize_with = "deserialize_host_addrs")]
    pub addresses: Vec<IpAddr>,
    /// The epp user to whom the host belongs
    #[xml(rename = "clID")]
    pub client_id: String,
    /// THe epp user that created the host
    #[xml(rename = "crID")]
    pub creator_id: String,
    /// The host creation date
    #[xml(rename = "crDate")]
    pub created_at: DateTime<Utc>,
    /// The epp user that last updated the host
    #[xml(rename = "upID")]
    pub updater_id: Option<String>,
    /// The host last update date
    #[xml(rename = "upDate")]
    pub updated_at: Option<DateTime<Utc>>,
    /// The host transfer date
    #[xml(rename = "trDate")]
    pub transferred_at: Option<DateTime<Utc>>,
}

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 `<resData>` tag for host info response
#[derive(Debug, FromXml)]
#[xml(rename = "infData", ns(XMLNS))]
pub struct HostInfoResponse {
    /// Data under the `<infData>` tag
    #[xml(rename = "infData")]
    pub info_data: HostInfoResponseData,
}
*/

#[cfg(test)]
mod tests {
    use chrono::{TimeZone, Utc};

    use super::{HostInfo, IpAddr};
    use crate::host::Status;
    use crate::response::ResultCode;
    use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};

    #[test]
    fn command() {
        let object = HostInfo::new("ns1.eppdev-1.com");
        assert_serialized("request/host/info.xml", &object);
    }

    #[test]
    fn response() {
        let object = response_from_file::<HostInfo>("response/host/info.xml");
        let result = object.res_data().unwrap();

        assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
        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);
        assert_eq!(result.addresses[0], IpAddr::from([29, 245, 122, 14]));
        assert_eq!(
            result.addresses[1],
            IpAddr::from([0x2404, 0x6800, 0x4001, 0x801, 0, 0, 0, 0x200e])
        );
        assert_eq!(result.client_id, "eppdev");
        assert_eq!(result.creator_id, "creator");
        assert_eq!(
            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.updated_at,
            Utc.with_ymd_and_hms(2021, 7, 26, 5, 28, 55).single()
        );
        assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
        assert_eq!(object.tr_ids.server_tr_id, SVTRID);
    }
}