epp_client/domain/
transfer.rs

1//! Types for EPP domain transfer request
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::{DomainAuthInfo, Period, XMLNS};
7use crate::common::{NoExtension, StringValue};
8use crate::request::{Command, Transaction};
9
10impl<'a> Transaction<NoExtension> for DomainTransfer<'a> {}
11
12impl<'a> Command for DomainTransfer<'a> {
13    type Response = DomainTransferResponse;
14    const COMMAND: &'static str = "transfer";
15}
16
17impl<'a> DomainTransfer<'a> {
18    pub fn new(name: &'a str, period: Option<Period>, auth_password: &'a str) -> Self {
19        Self::build(
20            "request",
21            name,
22            period,
23            Some(DomainAuthInfo::new(auth_password)),
24        )
25    }
26
27    pub fn query(name: &'a str, auth_password: &'a str) -> Self {
28        Self::build(
29            "query",
30            name,
31            None,
32            Some(DomainAuthInfo::new(auth_password)),
33        )
34    }
35
36    pub fn approve(name: &'a str) -> Self {
37        Self::build("approve", name, None, None)
38    }
39
40    pub fn reject(name: &'a str) -> Self {
41        Self::build("reject", name, None, None)
42    }
43
44    pub fn cancel(name: &'a str) -> Self {
45        Self::build("cancel", name, None, None)
46    }
47
48    fn build(
49        operation: &'a str,
50        name: &'a str,
51        period: Option<Period>,
52        auth_info: Option<DomainAuthInfo<'a>>,
53    ) -> Self {
54        Self {
55            operation,
56            domain: DomainTransferReqData {
57                xmlns: XMLNS,
58                name: name.into(),
59                period,
60                auth_info,
61            },
62        }
63    }
64}
65
66// Request
67
68/// Type for elements under the domain &lt;transfer&gt; tag
69#[derive(Serialize, Debug)]
70pub struct DomainTransferReqData<'a> {
71    /// XML namespace for domain commands
72    #[serde(rename = "xmlns:domain")]
73    xmlns: &'a str,
74    /// The name of the domain under transfer
75    #[serde(rename = "domain:name")]
76    name: StringValue<'a>,
77    /// The period of renewal upon a successful transfer
78    /// Only applicable in case of a transfer request
79    #[serde(rename = "domain:period")]
80    period: Option<Period>,
81    /// The authInfo for the domain under transfer
82    /// Only applicable to domain transfer and domain transfer query requests
83    #[serde(rename = "domain:authInfo")]
84    auth_info: Option<DomainAuthInfo<'a>>,
85}
86
87#[derive(Serialize, Debug)]
88/// Type for EPP XML &lt;transfer&gt; command for domains
89pub struct DomainTransfer<'a> {
90    /// The transfer operation to perform indicated by the 'op' attr
91    /// The values are one of transfer or query
92    #[serde(rename = "op")]
93    operation: &'a str,
94    /// The data under the &lt;transfer&gt; tag in the transfer request
95    #[serde(rename = "domain:transfer")]
96    domain: DomainTransferReqData<'a>,
97}
98
99// Response
100
101/// Type that represents the &lt;trnData&gt; tag for domain transfer response
102#[derive(Deserialize, Debug)]
103pub struct DomainTransferResponseData {
104    /// The domain name
105    pub name: StringValue<'static>,
106    /// The domain transfer status
107    #[serde(rename = "trStatus")]
108    pub transfer_status: StringValue<'static>,
109    /// The epp user who requested the transfer
110    #[serde(rename = "reID")]
111    pub requester_id: StringValue<'static>,
112    /// The transfer rquest date
113    #[serde(rename = "reDate")]
114    pub requested_at: DateTime<Utc>,
115    /// The epp user who should acknowledge the transfer request
116    #[serde(rename = "acID")]
117    pub ack_id: StringValue<'static>,
118    /// THe date by which the acknowledgment should be made
119    #[serde(rename = "acDate")]
120    pub ack_by: DateTime<Utc>,
121    /// The domain expiry date
122    #[serde(rename = "exDate")]
123    pub expiring_at: Option<DateTime<Utc>>,
124}
125
126/// Type that represents the &lt;resData&gt; tag for domain transfer response
127#[derive(Deserialize, Debug)]
128pub struct DomainTransferResponse {
129    /// Data under the &lt;trnData&gt; tag
130    #[serde(rename = "trnData")]
131    pub transfer_data: DomainTransferResponseData,
132}
133
134#[cfg(test)]
135mod tests {
136    use chrono::{TimeZone, Utc};
137
138    use super::{DomainTransfer, Period};
139    use crate::response::ResultCode;
140    use crate::tests::{assert_serialized, response_from_file, CLTRID, SUCCESS_MSG, SVTRID};
141
142    #[test]
143    fn request_command() {
144        let object =
145            DomainTransfer::new("testing.com", Some(Period::years(1).unwrap()), "epP4uthd#v");
146        assert_serialized("request/domain/transfer_request.xml", &object);
147    }
148
149    #[test]
150    fn approve_command() {
151        let object = DomainTransfer::approve("testing.com");
152        assert_serialized("request/domain/transfer_approve.xml", &object);
153    }
154
155    #[test]
156    fn reject_command() {
157        let object = DomainTransfer::reject("testing.com");
158        assert_serialized("request/domain/transfer_reject.xml", &object);
159    }
160
161    #[test]
162    fn cancel_command() {
163        let object = DomainTransfer::cancel("testing.com");
164        assert_serialized("request/domain/transfer_cancel.xml", &object);
165    }
166
167    #[test]
168    fn query_command() {
169        let object = DomainTransfer::query("testing.com", "epP4uthd#v");
170        assert_serialized("request/domain/transfer_query.xml", &object);
171    }
172
173    #[test]
174    fn request_response() {
175        let object = response_from_file::<DomainTransfer>("response/domain/transfer_request.xml");
176
177        let result = object.res_data().unwrap();
178
179        assert_eq!(
180            object.result.code,
181            ResultCode::CommandCompletedSuccessfullyActionPending
182        );
183        assert_eq!(
184            object.result.message,
185            "Command completed successfully; action pending".into()
186        );
187        assert_eq!(result.transfer_data.name, "eppdev-transfer.com".into());
188        assert_eq!(result.transfer_data.transfer_status, "pending".into());
189        assert_eq!(result.transfer_data.requester_id, "eppdev".into());
190        assert_eq!(
191            result.transfer_data.requested_at,
192            Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap(),
193        );
194        assert_eq!(result.transfer_data.ack_id, "ClientY".into());
195        assert_eq!(
196            result.transfer_data.ack_by,
197            Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap()
198        );
199        assert_eq!(
200            result.transfer_data.expiring_at,
201            Utc.with_ymd_and_hms(2022, 7, 2, 14, 53, 19).single(),
202        );
203        assert_eq!(*object.tr_ids.client_tr_id.as_ref().unwrap(), CLTRID.into());
204        assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
205    }
206
207    #[test]
208    fn approve_response() {
209        let object = response_from_file::<DomainTransfer>("response/domain/transfer_approve.xml");
210
211        assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
212        assert_eq!(object.result.message, SUCCESS_MSG.into());
213        assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into());
214        assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
215    }
216
217    #[test]
218    fn reject_response() {
219        let object = response_from_file::<DomainTransfer>("response/domain/transfer_reject.xml");
220
221        assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
222        assert_eq!(object.result.message, SUCCESS_MSG.into());
223        assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into());
224        assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
225    }
226
227    #[test]
228    fn cancel_response() {
229        let object = response_from_file::<DomainTransfer>("response/domain/transfer_cancel.xml");
230
231        assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
232        assert_eq!(object.result.message, SUCCESS_MSG.into());
233        assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into());
234        assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
235    }
236
237    #[test]
238    fn query_response() {
239        let object = response_from_file::<DomainTransfer>("response/domain/transfer_query.xml");
240
241        let result = object.res_data().unwrap();
242
243        assert_eq!(object.result.code, ResultCode::CommandCompletedSuccessfully);
244        assert_eq!(object.result.message, SUCCESS_MSG.into());
245        assert_eq!(result.transfer_data.name, "eppdev-transfer.com".into());
246        assert_eq!(result.transfer_data.transfer_status, "pending".into());
247        assert_eq!(result.transfer_data.requester_id, "eppdev".into());
248        assert_eq!(
249            result.transfer_data.requested_at,
250            Utc.with_ymd_and_hms(2021, 7, 23, 15, 31, 21).unwrap()
251        );
252        assert_eq!(result.transfer_data.ack_id, "ClientY".into());
253        assert_eq!(
254            result.transfer_data.ack_by,
255            Utc.with_ymd_and_hms(2021, 7, 28, 15, 31, 21).unwrap()
256        );
257        assert_eq!(
258            result.transfer_data.expiring_at,
259            Utc.with_ymd_and_hms(2022, 7, 2, 14, 53, 19).single()
260        );
261        assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID.into());
262        assert_eq!(object.tr_ids.server_tr_id, SVTRID.into());
263    }
264}