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