1use 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 deribit_base::{impl_json_debug_pretty, impl_json_display};
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum UserRequestType {
20 LogOnUser,
22 LogOffUser,
24 ChangePasswordForUser,
26 RequestIndividualUserStatus,
28}
29
30impl From<UserRequestType> for i32 {
31 fn from(request_type: UserRequestType) -> Self {
32 match request_type {
33 UserRequestType::LogOnUser => 1,
34 UserRequestType::LogOffUser => 2,
35 UserRequestType::ChangePasswordForUser => 3,
36 UserRequestType::RequestIndividualUserStatus => 4,
37 }
38 }
39}
40
41impl TryFrom<i32> for UserRequestType {
42 type Error = String;
43
44 fn try_from(value: i32) -> Result<Self, Self::Error> {
45 match value {
46 1 => Ok(UserRequestType::LogOnUser),
47 2 => Ok(UserRequestType::LogOffUser),
48 3 => Ok(UserRequestType::ChangePasswordForUser),
49 4 => Ok(UserRequestType::RequestIndividualUserStatus),
50 _ => Err(format!("Invalid UserRequestType: {}", value)),
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
57pub enum UserStatus {
58 LoggedIn,
60 NotLoggedIn,
62 UserNotRecognised,
64 PasswordIncorrect,
66 PasswordChanged,
68 Other,
70}
71
72impl From<UserStatus> for i32 {
73 fn from(status: UserStatus) -> Self {
74 match status {
75 UserStatus::LoggedIn => 1,
76 UserStatus::NotLoggedIn => 2,
77 UserStatus::UserNotRecognised => 3,
78 UserStatus::PasswordIncorrect => 4,
79 UserStatus::PasswordChanged => 5,
80 UserStatus::Other => 99,
81 }
82 }
83}
84
85impl TryFrom<i32> for UserStatus {
86 type Error = String;
87
88 fn try_from(value: i32) -> Result<Self, Self::Error> {
89 match value {
90 1 => Ok(UserStatus::LoggedIn),
91 2 => Ok(UserStatus::NotLoggedIn),
92 3 => Ok(UserStatus::UserNotRecognised),
93 4 => Ok(UserStatus::PasswordIncorrect),
94 5 => Ok(UserStatus::PasswordChanged),
95 99 => Ok(UserStatus::Other),
96 _ => Err(format!("Invalid UserStatus: {}", value)),
97 }
98 }
99}
100
101#[derive(Clone, PartialEq, Serialize, Deserialize)]
103pub struct UserRequest {
104 pub user_request_id: String,
106 pub user_request_type: UserRequestType,
108 pub username: String,
110 pub password: Option<String>,
112 pub new_password: Option<String>,
114 pub raw_data_length: Option<i32>,
116 pub raw_data: Option<Vec<u8>>,
118 pub user_status: Option<UserStatus>,
120 pub user_status_text: Option<String>,
122 pub deribit_label: Option<String>,
124}
125
126impl UserRequest {
127 pub fn new(
129 user_request_id: String,
130 user_request_type: UserRequestType,
131 username: String,
132 ) -> Self {
133 Self {
134 user_request_id,
135 user_request_type,
136 username,
137 password: None,
138 new_password: None,
139 raw_data_length: None,
140 raw_data: None,
141 user_status: None,
142 user_status_text: None,
143 deribit_label: None,
144 }
145 }
146
147 pub fn log_on_user(user_request_id: String, username: String, password: String) -> Self {
149 let mut request = Self::new(user_request_id, UserRequestType::LogOnUser, username);
150 request.password = Some(password);
151 request
152 }
153
154 pub fn log_off_user(user_request_id: String, username: String) -> Self {
156 Self::new(user_request_id, UserRequestType::LogOffUser, username)
157 }
158
159 pub fn change_password(
161 user_request_id: String,
162 username: String,
163 old_password: String,
164 new_password: String,
165 ) -> Self {
166 let mut request = Self::new(
167 user_request_id,
168 UserRequestType::ChangePasswordForUser,
169 username,
170 );
171 request.password = Some(old_password);
172 request.new_password = Some(new_password);
173 request
174 }
175
176 pub fn status_request(user_request_id: String, username: String) -> Self {
178 Self::new(
179 user_request_id,
180 UserRequestType::RequestIndividualUserStatus,
181 username,
182 )
183 }
184
185 pub fn with_password(mut self, password: String) -> Self {
187 self.password = Some(password);
188 self
189 }
190
191 pub fn with_new_password(mut self, new_password: String) -> Self {
193 self.new_password = Some(new_password);
194 self
195 }
196
197 pub fn with_raw_data(mut self, raw_data: Vec<u8>) -> Self {
199 self.raw_data_length = Some(raw_data.len() as i32);
200 self.raw_data = Some(raw_data);
201 self
202 }
203
204 pub fn with_user_status(mut self, user_status: UserStatus) -> Self {
206 self.user_status = Some(user_status);
207 self
208 }
209
210 pub fn with_user_status_text(mut self, user_status_text: String) -> Self {
212 self.user_status_text = Some(user_status_text);
213 self
214 }
215
216 pub fn with_label(mut self, label: String) -> Self {
218 self.deribit_label = Some(label);
219 self
220 }
221
222 pub fn to_fix_message(
224 &self,
225 sender_comp_id: &str,
226 target_comp_id: &str,
227 msg_seq_num: u32,
228 ) -> DeribitFixResult<String> {
229 let mut builder = MessageBuilder::new()
230 .msg_type(MsgType::UserRequest)
231 .sender_comp_id(sender_comp_id.to_string())
232 .target_comp_id(target_comp_id.to_string())
233 .msg_seq_num(msg_seq_num)
234 .sending_time(Utc::now());
235
236 builder = builder
238 .field(923, self.user_request_id.clone()) .field(924, i32::from(self.user_request_type).to_string()) .field(553, self.username.clone()); if let Some(password) = &self.password {
244 builder = builder.field(554, password.clone());
245 }
246
247 if let Some(new_password) = &self.new_password {
248 builder = builder.field(925, new_password.clone());
249 }
250
251 if let Some(raw_data_length) = &self.raw_data_length {
252 builder = builder.field(95, raw_data_length.to_string());
253 }
254
255 if let Some(raw_data) = &self.raw_data {
256 let encoded_data = general_purpose::STANDARD.encode(raw_data);
258 builder = builder.field(96, encoded_data);
259 }
260
261 if let Some(user_status) = &self.user_status {
262 builder = builder.field(926, i32::from(*user_status).to_string());
263 }
264
265 if let Some(user_status_text) = &self.user_status_text {
266 builder = builder.field(927, user_status_text.clone());
267 }
268
269 if let Some(deribit_label) = &self.deribit_label {
270 builder = builder.field(100010, deribit_label.clone());
271 }
272
273 Ok(builder.build()?.to_string())
274 }
275}
276
277impl_json_display!(UserRequest);
278impl_json_debug_pretty!(UserRequest);
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_user_request_creation() {
286 let request = UserRequest::new(
287 "UR123".to_string(),
288 UserRequestType::RequestIndividualUserStatus,
289 "testuser".to_string(),
290 );
291
292 assert_eq!(request.user_request_id, "UR123");
293 assert_eq!(
294 request.user_request_type,
295 UserRequestType::RequestIndividualUserStatus
296 );
297 assert_eq!(request.username, "testuser");
298 assert!(request.password.is_none());
299 assert!(request.new_password.is_none());
300 }
301
302 #[test]
303 fn test_user_request_log_on() {
304 let request = UserRequest::log_on_user(
305 "UR456".to_string(),
306 "user1".to_string(),
307 "password123".to_string(),
308 );
309
310 assert_eq!(request.user_request_type, UserRequestType::LogOnUser);
311 assert_eq!(request.username, "user1");
312 assert_eq!(request.password, Some("password123".to_string()));
313 }
314
315 #[test]
316 fn test_user_request_log_off() {
317 let request = UserRequest::log_off_user("UR789".to_string(), "user2".to_string());
318
319 assert_eq!(request.user_request_type, UserRequestType::LogOffUser);
320 assert_eq!(request.username, "user2");
321 assert!(request.password.is_none());
322 }
323
324 #[test]
325 fn test_user_request_change_password() {
326 let request = UserRequest::change_password(
327 "UR999".to_string(),
328 "user3".to_string(),
329 "oldpass".to_string(),
330 "newpass".to_string(),
331 );
332
333 assert_eq!(
334 request.user_request_type,
335 UserRequestType::ChangePasswordForUser
336 );
337 assert_eq!(request.username, "user3");
338 assert_eq!(request.password, Some("oldpass".to_string()));
339 assert_eq!(request.new_password, Some("newpass".to_string()));
340 }
341
342 #[test]
343 fn test_user_request_status_request() {
344 let request = UserRequest::status_request("UR111".to_string(), "user4".to_string());
345
346 assert_eq!(
347 request.user_request_type,
348 UserRequestType::RequestIndividualUserStatus
349 );
350 assert_eq!(request.username, "user4");
351 }
352
353 #[test]
354 fn test_user_request_with_options() {
355 let raw_data = vec![1, 2, 3, 4, 5];
356 let request = UserRequest::new(
357 "UR222".to_string(),
358 UserRequestType::LogOnUser,
359 "user5".to_string(),
360 )
361 .with_password("mypass".to_string())
362 .with_raw_data(raw_data.clone())
363 .with_user_status(UserStatus::LoggedIn)
364 .with_user_status_text("User logged in successfully".to_string())
365 .with_label("test-user-request".to_string());
366
367 assert_eq!(request.password, Some("mypass".to_string()));
368 assert_eq!(request.raw_data, Some(raw_data));
369 assert_eq!(request.raw_data_length, Some(5));
370 assert_eq!(request.user_status, Some(UserStatus::LoggedIn));
371 assert_eq!(
372 request.user_status_text,
373 Some("User logged in successfully".to_string())
374 );
375 assert_eq!(request.deribit_label, Some("test-user-request".to_string()));
376 }
377
378 #[test]
379 fn test_user_request_to_fix_message() {
380 let request = UserRequest::log_on_user(
381 "UR123".to_string(),
382 "testuser".to_string(),
383 "secret".to_string(),
384 )
385 .with_label("test-label".to_string());
386
387 let fix_message = request.to_fix_message("SENDER", "TARGET", 1).unwrap();
388
389 assert!(fix_message.contains("35=BE")); assert!(fix_message.contains("923=UR123")); assert!(fix_message.contains("924=1")); assert!(fix_message.contains("553=testuser")); assert!(fix_message.contains("554=secret")); assert!(fix_message.contains("100010=test-label")); }
397
398 #[test]
399 fn test_user_request_type_conversions() {
400 assert_eq!(i32::from(UserRequestType::LogOnUser), 1);
401 assert_eq!(i32::from(UserRequestType::LogOffUser), 2);
402 assert_eq!(i32::from(UserRequestType::ChangePasswordForUser), 3);
403 assert_eq!(i32::from(UserRequestType::RequestIndividualUserStatus), 4);
404
405 assert_eq!(
406 UserRequestType::try_from(1).unwrap(),
407 UserRequestType::LogOnUser
408 );
409 assert_eq!(
410 UserRequestType::try_from(2).unwrap(),
411 UserRequestType::LogOffUser
412 );
413 assert_eq!(
414 UserRequestType::try_from(3).unwrap(),
415 UserRequestType::ChangePasswordForUser
416 );
417 assert_eq!(
418 UserRequestType::try_from(4).unwrap(),
419 UserRequestType::RequestIndividualUserStatus
420 );
421
422 assert!(UserRequestType::try_from(99).is_err());
423 }
424
425 #[test]
426 fn test_user_status_conversions() {
427 assert_eq!(i32::from(UserStatus::LoggedIn), 1);
428 assert_eq!(i32::from(UserStatus::NotLoggedIn), 2);
429 assert_eq!(i32::from(UserStatus::UserNotRecognised), 3);
430 assert_eq!(i32::from(UserStatus::PasswordIncorrect), 4);
431 assert_eq!(i32::from(UserStatus::PasswordChanged), 5);
432 assert_eq!(i32::from(UserStatus::Other), 99);
433
434 assert_eq!(UserStatus::try_from(1).unwrap(), UserStatus::LoggedIn);
435 assert_eq!(UserStatus::try_from(2).unwrap(), UserStatus::NotLoggedIn);
436 assert_eq!(
437 UserStatus::try_from(3).unwrap(),
438 UserStatus::UserNotRecognised
439 );
440 assert_eq!(
441 UserStatus::try_from(4).unwrap(),
442 UserStatus::PasswordIncorrect
443 );
444 assert_eq!(
445 UserStatus::try_from(5).unwrap(),
446 UserStatus::PasswordChanged
447 );
448 assert_eq!(UserStatus::try_from(99).unwrap(), UserStatus::Other);
449
450 assert!(UserStatus::try_from(50).is_err());
451 }
452}