instant_epp/domain/
transfer.rs

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