1use 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#[derive(Debug, ToXml)]
69#[xml(rename = "transfer", ns(XMLNS))]
70pub struct DomainTransferReqData<'a> {
71 name: &'a str,
73 period: Option<Period>,
76 #[xml(rename = "authInfo")]
79 auth_info: Option<DomainAuthInfo<'a>>,
80}
81
82#[derive(Debug, ToXml)]
83#[xml(rename = "transfer", ns(EPP_XMLNS))]
84pub struct DomainTransfer<'a> {
86 #[xml(rename = "op", attribute)]
89 operation: &'a str,
90 domain: DomainTransferReqData<'a>,
92}
93
94#[derive(Debug, FromXml)]
98#[xml(rename = "trnData", ns(XMLNS))]
99pub struct TransferData {
100 pub name: String,
102 #[xml(rename = "trStatus")]
104 pub transfer_status: String,
105 #[xml(rename = "reID")]
107 pub requester_id: String,
108 #[xml(rename = "reDate")]
110 pub requested_at: DateTime<Utc>,
111 #[xml(rename = "acID")]
113 pub ack_id: String,
114 #[xml(rename = "acDate")]
116 pub ack_by: DateTime<Utc>,
117 #[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}