1use crate::error::Result;
16use crate::message::MessageBuilder;
17use crate::model::message::FixMessage;
18use crate::model::types::MsgType;
19use chrono::Utc;
20use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct Heartbeat {
29 pub test_req_id: Option<String>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub enum BusinessRejectReason {
37 Other = 0,
39 UnknownId = 1,
41 UnknownSecurity = 2,
43 UnsupportedMessageType = 3,
45 ApplicationNotAvailable = 4,
47 ConditionallyRequiredFieldMissing = 5,
49 NotAuthorized = 6,
51 DeliverToFirmNotAvailableAtThisTime = 7,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct BusinessMessageReject {
61 pub ref_msg_type: String,
63
64 pub business_reject_reason: BusinessRejectReason,
66
67 pub business_reject_ref_id: Option<String>,
69
70 pub text: Option<String>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct TestRequest {
81 pub test_req_id: String,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct ResendRequest {
93 pub begin_seq_no: u32,
95
96 pub end_seq_no: u32,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107pub struct SequenceReset {
108 pub new_seq_no: u32,
111
112 pub gap_fill_flag: Option<bool>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct Reject {
124 pub ref_seq_num: u32,
126
127 pub ref_tag_id: Option<u32>,
130
131 pub ref_msg_type: Option<String>,
134
135 pub session_reject_reason: Option<u32>,
138
139 pub text: Option<String>,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
146pub enum SessionRejectReason {
147 InvalidTagNumber = 0,
149 RequiredTagMissing = 1,
151 TagNotDefinedForMessageType = 2,
153 UndefinedTag = 3,
155 TagSpecifiedWithoutValue = 4,
157 ValueIncorrectForTag = 5,
159 IncorrectDataFormat = 6,
161 DecryptionProblem = 7,
163 SignatureProblem = 8,
165 CompIdProblem = 9,
167 SendingTimeAccuracyProblem = 10,
169 InvalidMsgType = 11,
171 XmlValidationError = 12,
173 TagAppearsMoreThanOnce = 13,
175 TagSpecifiedOutOfOrder = 14,
177 RepeatingGroupFieldsOutOfOrder = 15,
179 IncorrectNumInGroupCount = 16,
181 NonDataValueIncludesFieldDelimiter = 17,
183 Other = 99,
185}
186
187impl_json_display!(Heartbeat);
189impl_json_display!(TestRequest);
190impl_json_display!(ResendRequest);
191impl_json_display!(SequenceReset);
192impl_json_display!(Reject);
193impl_json_display!(BusinessMessageReject);
194
195impl Heartbeat {
196 pub fn new() -> Self {
198 Self { test_req_id: None }
199 }
200
201 pub fn new_response(test_req_id: String) -> Self {
203 Self {
204 test_req_id: Some(test_req_id),
205 }
206 }
207
208 pub fn is_test_response(&self) -> bool {
210 self.test_req_id.is_some()
211 }
212
213 pub fn to_fix_message(
215 &self,
216 sender_comp_id: String,
217 target_comp_id: String,
218 msg_seq_num: u32,
219 ) -> Result<FixMessage> {
220 let mut builder = MessageBuilder::new()
221 .msg_type(MsgType::Heartbeat)
222 .sender_comp_id(sender_comp_id)
223 .target_comp_id(target_comp_id)
224 .msg_seq_num(msg_seq_num)
225 .sending_time(Utc::now());
226
227 if let Some(ref test_req_id) = self.test_req_id {
229 builder = builder.field(112, test_req_id.clone());
230 }
231
232 builder.build()
233 }
234}
235
236impl TestRequest {
237 pub fn new(test_req_id: String) -> Self {
239 Self { test_req_id }
240 }
241
242 pub fn new_with_timestamp() -> Self {
244 let test_req_id = format!("TESTREQ_{}", Utc::now().timestamp_millis());
245 Self::new(test_req_id)
246 }
247
248 pub fn to_fix_message(
250 &self,
251 sender_comp_id: String,
252 target_comp_id: String,
253 msg_seq_num: u32,
254 ) -> Result<FixMessage> {
255 MessageBuilder::new()
256 .msg_type(MsgType::TestRequest)
257 .sender_comp_id(sender_comp_id)
258 .target_comp_id(target_comp_id)
259 .msg_seq_num(msg_seq_num)
260 .sending_time(Utc::now())
261 .field(112, self.test_req_id.clone()) .build()
263 }
264}
265
266impl ResendRequest {
267 pub fn new(begin_seq_no: u32, end_seq_no: u32) -> Self {
269 Self {
270 begin_seq_no,
271 end_seq_no,
272 }
273 }
274
275 pub fn new_from_sequence(begin_seq_no: u32) -> Self {
277 Self {
278 begin_seq_no,
279 end_seq_no: 0, }
281 }
282
283 pub fn is_infinite_range(&self) -> bool {
285 self.end_seq_no == 0
286 }
287
288 pub fn message_count(&self) -> Option<u32> {
290 if self.is_infinite_range() {
291 None
292 } else {
293 Some(self.end_seq_no.saturating_sub(self.begin_seq_no) + 1)
294 }
295 }
296
297 pub fn to_fix_message(
299 &self,
300 sender_comp_id: String,
301 target_comp_id: String,
302 msg_seq_num: u32,
303 ) -> Result<FixMessage> {
304 MessageBuilder::new()
305 .msg_type(MsgType::ResendRequest)
306 .sender_comp_id(sender_comp_id)
307 .target_comp_id(target_comp_id)
308 .msg_seq_num(msg_seq_num)
309 .sending_time(Utc::now())
310 .field(7, self.begin_seq_no.to_string()) .field(16, self.end_seq_no.to_string()) .build()
313 }
314}
315
316impl SequenceReset {
317 pub fn new(new_seq_no: u32) -> Self {
319 Self {
320 new_seq_no,
321 gap_fill_flag: None,
322 }
323 }
324
325 pub fn new_gap_fill(new_seq_no: u32) -> Self {
327 Self {
328 new_seq_no,
329 gap_fill_flag: Some(true),
330 }
331 }
332
333 pub fn new_reset(new_seq_no: u32) -> Self {
335 Self {
336 new_seq_no,
337 gap_fill_flag: Some(false),
338 }
339 }
340
341 pub fn is_gap_fill(&self) -> bool {
343 self.gap_fill_flag.unwrap_or(false)
344 }
345
346 pub fn to_fix_message(
348 &self,
349 sender_comp_id: String,
350 target_comp_id: String,
351 msg_seq_num: u32,
352 ) -> Result<FixMessage> {
353 let mut builder = MessageBuilder::new()
354 .msg_type(MsgType::SequenceReset)
355 .sender_comp_id(sender_comp_id)
356 .target_comp_id(target_comp_id)
357 .msg_seq_num(msg_seq_num)
358 .sending_time(Utc::now())
359 .field(36, self.new_seq_no.to_string()); if let Some(gap_fill) = self.gap_fill_flag {
363 builder = builder.field(123, if gap_fill { "Y" } else { "N" }.to_string());
364 }
365
366 builder.build()
367 }
368}
369
370impl Reject {
371 pub fn new(ref_seq_num: u32) -> Self {
373 Self {
374 ref_seq_num,
375 ref_tag_id: None,
376 ref_msg_type: None,
377 session_reject_reason: None,
378 text: None,
379 }
380 }
381
382 pub fn new_detailed(
384 ref_seq_num: u32,
385 ref_tag_id: Option<u32>,
386 ref_msg_type: Option<String>,
387 session_reject_reason: Option<SessionRejectReason>,
388 text: Option<String>,
389 ) -> Self {
390 Self {
391 ref_seq_num,
392 ref_tag_id,
393 ref_msg_type,
394 session_reject_reason: session_reject_reason.map(|r| r as u32),
395 text,
396 }
397 }
398
399 pub fn new_invalid_tag(ref_seq_num: u32, tag_id: u32) -> Self {
401 Self::new_detailed(
402 ref_seq_num,
403 Some(tag_id),
404 None,
405 Some(SessionRejectReason::InvalidTagNumber),
406 Some(format!("Invalid tag number: {tag_id}")),
407 )
408 }
409
410 pub fn new_missing_tag(ref_seq_num: u32, tag_id: u32, msg_type: String) -> Self {
412 Self::new_detailed(
413 ref_seq_num,
414 Some(tag_id),
415 Some(msg_type),
416 Some(SessionRejectReason::RequiredTagMissing),
417 Some(format!("Required tag {tag_id} missing")),
418 )
419 }
420
421 pub fn new_incorrect_format(ref_seq_num: u32, tag_id: u32, text: String) -> Self {
423 Self::new_detailed(
424 ref_seq_num,
425 Some(tag_id),
426 None,
427 Some(SessionRejectReason::IncorrectDataFormat),
428 Some(text),
429 )
430 }
431
432 pub fn to_fix_message(
434 &self,
435 sender_comp_id: String,
436 target_comp_id: String,
437 msg_seq_num: u32,
438 ) -> Result<FixMessage> {
439 let mut builder = MessageBuilder::new()
440 .msg_type(MsgType::Reject)
441 .sender_comp_id(sender_comp_id)
442 .target_comp_id(target_comp_id)
443 .msg_seq_num(msg_seq_num)
444 .sending_time(Utc::now())
445 .field(45, self.ref_seq_num.to_string()); if let Some(ref_tag_id) = self.ref_tag_id {
449 builder = builder.field(371, ref_tag_id.to_string()); }
451
452 if let Some(ref ref_msg_type) = self.ref_msg_type {
453 builder = builder.field(372, ref_msg_type.clone()); }
455
456 if let Some(session_reject_reason) = self.session_reject_reason {
457 builder = builder.field(373, session_reject_reason.to_string()); }
459
460 if let Some(ref text) = self.text {
461 builder = builder.field(58, text.clone()); }
463
464 builder.build()
465 }
466}
467
468impl BusinessMessageReject {
469 pub fn new(ref_msg_type: String, business_reject_reason: BusinessRejectReason) -> Self {
471 Self {
472 ref_msg_type,
473 business_reject_reason,
474 business_reject_ref_id: None,
475 text: None,
476 }
477 }
478
479 pub fn with_ref_id(mut self, business_reject_ref_id: String) -> Self {
481 self.business_reject_ref_id = Some(business_reject_ref_id);
482 self
483 }
484
485 pub fn with_text(mut self, text: String) -> Self {
487 self.text = Some(text);
488 self
489 }
490
491 pub fn to_fix_message(
493 &self,
494 sender_comp_id: String,
495 target_comp_id: String,
496 msg_seq_num: u32,
497 ) -> Result<FixMessage> {
498 let mut builder = MessageBuilder::new()
499 .msg_type(MsgType::BusinessMessageReject)
500 .sender_comp_id(sender_comp_id)
501 .target_comp_id(target_comp_id)
502 .msg_seq_num(msg_seq_num)
503 .sending_time(Utc::now())
504 .field(372, self.ref_msg_type.clone()) .field(380, (self.business_reject_reason as u32).to_string()); if let Some(ref ref_id) = self.business_reject_ref_id {
508 builder = builder.field(379, ref_id.clone()); }
510
511 if let Some(ref text) = self.text {
512 builder = builder.field(58, text.clone()); }
514
515 builder.build()
516 }
517}
518
519impl Default for Heartbeat {
520 fn default() -> Self {
521 Self::new()
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_heartbeat_creation() {
531 let heartbeat = Heartbeat::new();
532 assert_eq!(heartbeat.test_req_id, None);
533 assert!(!heartbeat.is_test_response());
534
535 let response = Heartbeat::new_response("TEST123".to_string());
536 assert_eq!(response.test_req_id, Some("TEST123".to_string()));
537 assert!(response.is_test_response());
538 }
539
540 #[test]
541 fn test_test_request_creation() {
542 let test_req = TestRequest::new("REQ123".to_string());
543 assert_eq!(test_req.test_req_id, "REQ123");
544
545 let timestamp_req = TestRequest::new_with_timestamp();
546 assert!(timestamp_req.test_req_id.starts_with("TESTREQ_"));
547 }
548
549 #[test]
550 fn test_resend_request_creation() {
551 let resend = ResendRequest::new(10, 20);
552 assert_eq!(resend.begin_seq_no, 10);
553 assert_eq!(resend.end_seq_no, 20);
554 assert!(!resend.is_infinite_range());
555 assert_eq!(resend.message_count(), Some(11));
556
557 let infinite = ResendRequest::new_from_sequence(15);
558 assert_eq!(infinite.begin_seq_no, 15);
559 assert_eq!(infinite.end_seq_no, 0);
560 assert!(infinite.is_infinite_range());
561 assert_eq!(infinite.message_count(), None);
562 }
563
564 #[test]
565 fn test_reject_creation() {
566 let basic_reject = Reject::new(123);
567 assert_eq!(basic_reject.ref_seq_num, 123);
568 assert_eq!(basic_reject.ref_tag_id, None);
569
570 let invalid_tag = Reject::new_invalid_tag(456, 999);
571 assert_eq!(invalid_tag.ref_seq_num, 456);
572 assert_eq!(invalid_tag.ref_tag_id, Some(999));
573 assert_eq!(
574 invalid_tag.session_reject_reason,
575 Some(SessionRejectReason::InvalidTagNumber as u32)
576 );
577
578 let missing_tag = Reject::new_missing_tag(789, 35, "D".to_string());
579 assert_eq!(missing_tag.ref_seq_num, 789);
580 assert_eq!(missing_tag.ref_tag_id, Some(35));
581 assert_eq!(missing_tag.ref_msg_type, Some("D".to_string()));
582 assert_eq!(
583 missing_tag.session_reject_reason,
584 Some(SessionRejectReason::RequiredTagMissing as u32)
585 );
586 }
587
588 #[test]
589 fn test_session_reject_reason_values() {
590 assert_eq!(SessionRejectReason::InvalidTagNumber as u32, 0);
591 assert_eq!(SessionRejectReason::RequiredTagMissing as u32, 1);
592 assert_eq!(SessionRejectReason::Other as u32, 99);
593 }
594
595 #[test]
596 fn test_heartbeat_to_fix_message() {
597 let heartbeat = Heartbeat::new_response("TEST123".to_string());
598 let fix_msg = heartbeat.to_fix_message("SENDER".to_string(), "TARGET".to_string(), 100);
599
600 assert!(fix_msg.is_ok());
601 let msg = fix_msg.unwrap();
602 assert_eq!(msg.get_field(35), Some(&"0".to_string())); assert_eq!(msg.get_field(112), Some(&"TEST123".to_string())); }
605
606 #[test]
607 fn test_test_request_to_fix_message() {
608 let test_req = TestRequest::new("REQ456".to_string());
609 let fix_msg = test_req.to_fix_message("CLIENT".to_string(), "SERVER".to_string(), 200);
610
611 assert!(fix_msg.is_ok());
612 let msg = fix_msg.unwrap();
613 assert_eq!(msg.get_field(35), Some(&"1".to_string())); assert_eq!(msg.get_field(112), Some(&"REQ456".to_string())); }
616
617 #[test]
618 fn test_business_message_reject_to_fix_message() {
619 let bmr = BusinessMessageReject::new(
620 "D".to_string(),
621 BusinessRejectReason::UnsupportedMessageType,
622 )
623 .with_ref_id("ABC123".to_string())
624 .with_text("Unsupported type".to_string());
625
626 let fix_msg = bmr.to_fix_message("SENDER".to_string(), "TARGET".to_string(), 77);
627 assert!(fix_msg.is_ok());
628 let msg = fix_msg.unwrap();
629 assert_eq!(msg.get_field(35), Some(&"j".to_string())); assert_eq!(msg.get_field(372), Some(&"D".to_string())); assert_eq!(msg.get_field(379), Some(&"ABC123".to_string())); assert_eq!(
633 msg.get_field(380),
634 Some(&(BusinessRejectReason::UnsupportedMessageType as u32).to_string())
635 ); assert_eq!(msg.get_field(58), Some(&"Unsupported type".to_string())); }
638}