1use 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#[derive(Serialize, Debug)]
70pub struct DomainTransferReqData<'a> {
71 #[serde(rename = "xmlns:domain")]
73 xmlns: &'a str,
74 #[serde(rename = "domain:name")]
76 name: StringValue<'a>,
77 #[serde(rename = "domain:period")]
80 period: Option<Period>,
81 #[serde(rename = "domain:authInfo")]
84 auth_info: Option<DomainAuthInfo<'a>>,
85}
86
87#[derive(Serialize, Debug)]
88pub struct DomainTransfer<'a> {
90 #[serde(rename = "op")]
93 operation: &'a str,
94 #[serde(rename = "domain:transfer")]
96 domain: DomainTransferReqData<'a>,
97}
98
99#[derive(Deserialize, Debug)]
103pub struct DomainTransferResponseData {
104 pub name: StringValue<'static>,
106 #[serde(rename = "trStatus")]
108 pub transfer_status: StringValue<'static>,
109 #[serde(rename = "reID")]
111 pub requester_id: StringValue<'static>,
112 #[serde(rename = "reDate")]
114 pub requested_at: DateTime<Utc>,
115 #[serde(rename = "acID")]
117 pub ack_id: StringValue<'static>,
118 #[serde(rename = "acDate")]
120 pub ack_by: DateTime<Utc>,
121 #[serde(rename = "exDate")]
123 pub expiring_at: Option<DateTime<Utc>>,
124}
125
126#[derive(Deserialize, Debug)]
128pub struct DomainTransferResponse {
129 #[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}