deribit_fix/message/user/
user_response.rs1use crate::error::Result as DeribitFixResult;
10use crate::message::builder::MessageBuilder;
11use crate::model::types::MsgType;
12use base64::{Engine as _, engine::general_purpose};
13use chrono::Utc;
14use serde::{Deserialize, Serialize};
15
16pub use super::user_request::UserStatus;
18
19#[derive(Clone, PartialEq, Serialize, Deserialize)]
21pub struct UserResponse {
22 pub user_request_id: String,
24 pub username: String,
26 pub user_status: UserStatus,
28 pub user_status_text: Option<String>,
30 pub raw_data_length: Option<i32>,
32 pub raw_data: Option<Vec<u8>>,
34 pub deribit_label: Option<String>,
36 pub user_equity: Option<f64>,
38 pub user_balance: Option<f64>,
40 pub user_initial_margin: Option<f64>,
42 pub user_maintenance_margin: Option<f64>,
44 pub unrealized_pl: Option<f64>,
46 pub realized_pl: Option<f64>,
48 pub total_pl: Option<f64>,
50 pub margin_balance: Option<f64>,
52}
53
54impl UserResponse {
55 pub fn new(user_request_id: String, username: String, user_status: UserStatus) -> Self {
57 Self {
58 user_request_id,
59 username,
60 user_status,
61 user_status_text: None,
62 raw_data_length: None,
63 raw_data: None,
64 deribit_label: None,
65 user_equity: None,
66 user_balance: None,
67 user_initial_margin: None,
68 user_maintenance_margin: None,
69 unrealized_pl: None,
70 realized_pl: None,
71 total_pl: None,
72 margin_balance: None,
73 }
74 }
75
76 pub fn logged_in(user_request_id: String, username: String) -> Self {
78 let mut response = Self::new(user_request_id, username, UserStatus::LoggedIn);
79 response.user_status_text = Some("User logged in successfully".to_string());
80 response
81 }
82
83 pub fn logged_out(user_request_id: String, username: String) -> Self {
85 let mut response = Self::new(user_request_id, username, UserStatus::NotLoggedIn);
86 response.user_status_text = Some("User logged out successfully".to_string());
87 response
88 }
89
90 pub fn password_changed(user_request_id: String, username: String) -> Self {
92 let mut response = Self::new(user_request_id, username, UserStatus::PasswordChanged);
93 response.user_status_text = Some("Password changed successfully".to_string());
94 response
95 }
96
97 pub fn user_not_recognised(user_request_id: String, username: String) -> Self {
99 let mut response = Self::new(user_request_id, username, UserStatus::UserNotRecognised);
100 response.user_status_text = Some("User not recognised".to_string());
101 response
102 }
103
104 pub fn password_incorrect(user_request_id: String, username: String) -> Self {
106 let mut response = Self::new(user_request_id, username, UserStatus::PasswordIncorrect);
107 response.user_status_text = Some("Password incorrect".to_string());
108 response
109 }
110
111 pub fn error(user_request_id: String, username: String, error_text: String) -> Self {
113 let mut response = Self::new(user_request_id, username, UserStatus::Other);
114 response.user_status_text = Some(error_text);
115 response
116 }
117
118 pub fn with_user_status_text(mut self, user_status_text: String) -> Self {
120 self.user_status_text = Some(user_status_text);
121 self
122 }
123
124 pub fn with_raw_data(mut self, raw_data: Vec<u8>) -> Self {
126 self.raw_data_length = Some(raw_data.len() as i32);
127 self.raw_data = Some(raw_data);
128 self
129 }
130
131 pub fn with_label(mut self, label: String) -> Self {
133 self.deribit_label = Some(label);
134 self
135 }
136
137 #[must_use]
139 pub fn with_user_equity(mut self, equity: f64) -> Self {
140 self.user_equity = Some(equity);
141 self
142 }
143
144 #[must_use]
146 pub fn with_user_balance(mut self, balance: f64) -> Self {
147 self.user_balance = Some(balance);
148 self
149 }
150
151 #[must_use]
153 pub fn with_user_initial_margin(mut self, initial_margin: f64) -> Self {
154 self.user_initial_margin = Some(initial_margin);
155 self
156 }
157
158 #[must_use]
160 pub fn with_user_maintenance_margin(mut self, maintenance_margin: f64) -> Self {
161 self.user_maintenance_margin = Some(maintenance_margin);
162 self
163 }
164
165 #[must_use]
167 pub fn with_unrealized_pl(mut self, unrealized_pl: f64) -> Self {
168 self.unrealized_pl = Some(unrealized_pl);
169 self
170 }
171
172 #[must_use]
174 pub fn with_realized_pl(mut self, realized_pl: f64) -> Self {
175 self.realized_pl = Some(realized_pl);
176 self
177 }
178
179 #[must_use]
181 pub fn with_total_pl(mut self, total_pl: f64) -> Self {
182 self.total_pl = Some(total_pl);
183 self
184 }
185
186 #[must_use]
188 pub fn with_margin_balance(mut self, margin_balance: f64) -> Self {
189 self.margin_balance = Some(margin_balance);
190 self
191 }
192
193 pub fn to_fix_message(
195 &self,
196 sender_comp_id: &str,
197 target_comp_id: &str,
198 msg_seq_num: u32,
199 ) -> DeribitFixResult<String> {
200 let mut builder = MessageBuilder::new()
201 .msg_type(MsgType::UserResponse)
202 .sender_comp_id(sender_comp_id.to_string())
203 .target_comp_id(target_comp_id.to_string())
204 .msg_seq_num(msg_seq_num)
205 .sending_time(Utc::now());
206
207 builder = builder
209 .field(923, self.user_request_id.clone()) .field(553, self.username.clone()) .field(926, i32::from(self.user_status).to_string()); if let Some(user_status_text) = &self.user_status_text {
215 builder = builder.field(927, user_status_text.clone());
216 }
217
218 if let Some(raw_data_length) = &self.raw_data_length {
219 builder = builder.field(95, raw_data_length.to_string());
220 }
221
222 if let Some(raw_data) = &self.raw_data {
223 let encoded_data = general_purpose::STANDARD.encode(raw_data);
225 builder = builder.field(96, encoded_data);
226 }
227
228 if let Some(deribit_label) = &self.deribit_label {
229 builder = builder.field(100010, deribit_label.clone());
230 }
231
232 if let Some(equity) = self.user_equity {
234 builder = builder.field(100001, equity.to_string());
235 }
236
237 if let Some(balance) = self.user_balance {
238 builder = builder.field(100002, balance.to_string());
239 }
240
241 if let Some(initial_margin) = self.user_initial_margin {
242 builder = builder.field(100003, initial_margin.to_string());
243 }
244
245 if let Some(maintenance_margin) = self.user_maintenance_margin {
246 builder = builder.field(100004, maintenance_margin.to_string());
247 }
248
249 if let Some(unrealized) = self.unrealized_pl {
250 builder = builder.field(100005, unrealized.to_string());
251 }
252
253 if let Some(realized) = self.realized_pl {
254 builder = builder.field(100006, realized.to_string());
255 }
256
257 if let Some(total) = self.total_pl {
258 builder = builder.field(100011, total.to_string());
259 }
260
261 if let Some(margin) = self.margin_balance {
262 builder = builder.field(100013, margin.to_string());
263 }
264
265 Ok(builder.build()?.to_string())
266 }
267}
268
269impl_json_display!(UserResponse);
270impl_json_debug_pretty!(UserResponse);
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_user_response_creation() {
278 let response = UserResponse::new(
279 "UR123".to_string(),
280 "testuser".to_string(),
281 UserStatus::LoggedIn,
282 );
283
284 assert_eq!(response.user_request_id, "UR123");
285 assert_eq!(response.username, "testuser");
286 assert_eq!(response.user_status, UserStatus::LoggedIn);
287 assert!(response.user_status_text.is_none());
288 assert!(response.raw_data.is_none());
289 }
290
291 #[test]
292 fn test_user_response_logged_in() {
293 let response = UserResponse::logged_in("UR456".to_string(), "user1".to_string());
294
295 assert_eq!(response.user_status, UserStatus::LoggedIn);
296 assert_eq!(response.username, "user1");
297 assert_eq!(
298 response.user_status_text,
299 Some("User logged in successfully".to_string())
300 );
301 }
302
303 #[test]
304 fn test_user_response_logged_out() {
305 let response = UserResponse::logged_out("UR789".to_string(), "user2".to_string());
306
307 assert_eq!(response.user_status, UserStatus::NotLoggedIn);
308 assert_eq!(response.username, "user2");
309 assert_eq!(
310 response.user_status_text,
311 Some("User logged out successfully".to_string())
312 );
313 }
314
315 #[test]
316 fn test_user_response_password_changed() {
317 let response = UserResponse::password_changed("UR999".to_string(), "user3".to_string());
318
319 assert_eq!(response.user_status, UserStatus::PasswordChanged);
320 assert_eq!(response.username, "user3");
321 assert_eq!(
322 response.user_status_text,
323 Some("Password changed successfully".to_string())
324 );
325 }
326
327 #[test]
328 fn test_user_response_user_not_recognised() {
329 let response =
330 UserResponse::user_not_recognised("UR111".to_string(), "unknown_user".to_string());
331
332 assert_eq!(response.user_status, UserStatus::UserNotRecognised);
333 assert_eq!(response.username, "unknown_user");
334 assert_eq!(
335 response.user_status_text,
336 Some("User not recognised".to_string())
337 );
338 }
339
340 #[test]
341 fn test_user_response_password_incorrect() {
342 let response = UserResponse::password_incorrect("UR222".to_string(), "user4".to_string());
343
344 assert_eq!(response.user_status, UserStatus::PasswordIncorrect);
345 assert_eq!(response.username, "user4");
346 assert_eq!(
347 response.user_status_text,
348 Some("Password incorrect".to_string())
349 );
350 }
351
352 #[test]
353 fn test_user_response_error() {
354 let response = UserResponse::error(
355 "UR333".to_string(),
356 "user5".to_string(),
357 "System temporarily unavailable".to_string(),
358 );
359
360 assert_eq!(response.user_status, UserStatus::Other);
361 assert_eq!(response.username, "user5");
362 assert_eq!(
363 response.user_status_text,
364 Some("System temporarily unavailable".to_string())
365 );
366 }
367
368 #[test]
369 fn test_user_response_with_options() {
370 let raw_data = vec![10, 20, 30, 40];
371 let response = UserResponse::new(
372 "UR444".to_string(),
373 "user6".to_string(),
374 UserStatus::LoggedIn,
375 )
376 .with_user_status_text("Custom login message".to_string())
377 .with_raw_data(raw_data.clone())
378 .with_label("test-user-response".to_string());
379
380 assert_eq!(
381 response.user_status_text,
382 Some("Custom login message".to_string())
383 );
384 assert_eq!(response.raw_data, Some(raw_data));
385 assert_eq!(response.raw_data_length, Some(4));
386 assert_eq!(
387 response.deribit_label,
388 Some("test-user-response".to_string())
389 );
390 }
391
392 #[test]
393 fn test_user_response_to_fix_message() {
394 let response = UserResponse::logged_in("UR123".to_string(), "testuser".to_string())
395 .with_label("test-label".to_string());
396
397 let fix_message = response.to_fix_message("SENDER", "TARGET", 1).unwrap();
398
399 assert!(fix_message.contains("35=BF")); assert!(fix_message.contains("923=UR123")); assert!(fix_message.contains("553=testuser")); assert!(fix_message.contains("926=1")); assert!(fix_message.contains("927=User logged in successfully")); assert!(fix_message.contains("100010=test-label")); }
407
408 #[test]
409 fn test_user_response_minimal_fix_message() {
410 let response = UserResponse::new(
411 "UR456".to_string(),
412 "user".to_string(),
413 UserStatus::NotLoggedIn,
414 );
415
416 let fix_message = response.to_fix_message("SENDER", "TARGET", 2).unwrap();
417
418 assert!(fix_message.contains("35=BF")); assert!(fix_message.contains("923=UR456")); assert!(fix_message.contains("553=user")); assert!(fix_message.contains("926=2")); assert!(!fix_message.contains("\x01927=")); assert!(!fix_message.contains("\x0195=")); assert!(!fix_message.contains("\x0196=")); }
430
431 #[test]
432 fn test_user_response_with_raw_data() {
433 let raw_data = vec![0xFF, 0xFE, 0xFD];
434 let response = UserResponse::new(
435 "UR789".to_string(),
436 "datauser".to_string(),
437 UserStatus::LoggedIn,
438 )
439 .with_raw_data(raw_data.clone());
440
441 let fix_message = response.to_fix_message("SENDER", "TARGET", 3).unwrap();
442
443 assert!(fix_message.contains("95=3")); assert!(fix_message.contains("96=")); }
447
448 #[test]
449 fn test_user_response_with_account_info() {
450 let response = UserResponse::new(
451 "UR_ACCT".to_string(),
452 "trader".to_string(),
453 UserStatus::LoggedIn,
454 )
455 .with_user_equity(10000.50)
456 .with_user_balance(9500.25)
457 .with_user_initial_margin(500.0)
458 .with_user_maintenance_margin(250.0)
459 .with_unrealized_pl(100.25)
460 .with_realized_pl(50.0)
461 .with_total_pl(150.25)
462 .with_margin_balance(8000.0);
463
464 assert_eq!(response.user_equity, Some(10000.50));
465 assert_eq!(response.user_balance, Some(9500.25));
466 assert_eq!(response.user_initial_margin, Some(500.0));
467 assert_eq!(response.user_maintenance_margin, Some(250.0));
468 assert_eq!(response.unrealized_pl, Some(100.25));
469 assert_eq!(response.realized_pl, Some(50.0));
470 assert_eq!(response.total_pl, Some(150.25));
471 assert_eq!(response.margin_balance, Some(8000.0));
472 }
473
474 #[test]
475 fn test_user_response_account_info_fix_message() {
476 let response = UserResponse::new(
477 "UR_FIX".to_string(),
478 "user".to_string(),
479 UserStatus::LoggedIn,
480 )
481 .with_user_equity(5000.0)
482 .with_user_balance(4500.0)
483 .with_user_initial_margin(300.0)
484 .with_user_maintenance_margin(150.0);
485
486 let fix_message = response.to_fix_message("SENDER", "TARGET", 1).unwrap();
487
488 assert!(fix_message.contains("100001=5000")); assert!(fix_message.contains("100002=4500")); assert!(fix_message.contains("100003=300")); assert!(fix_message.contains("100004=150")); }
494}