1use crate::errors::{ParseError, Result};
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, PartialEq)]
38pub struct BasicHeader {
39 pub application_id: String,
41 pub service_id: String,
43 pub logical_terminal: String,
45 pub sender_bic: String,
47 pub session_number: String,
49 pub sequence_number: String,
51}
52
53impl serde::Serialize for BasicHeader {
55 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
56 where
57 S: serde::Serializer,
58 {
59 use serde::ser::SerializeStruct;
60
61 let normalized_logical_terminal = if self.logical_terminal.len() > 12 {
63 self.logical_terminal[..12].to_string()
64 } else if self.logical_terminal.len() < 12 {
65 format!("{:X<12}", self.logical_terminal)
66 } else {
67 self.logical_terminal.clone()
68 };
69
70 let mut state = serializer.serialize_struct("BasicHeader", 5)?;
71 state.serialize_field("application_id", &self.application_id)?;
72 state.serialize_field("service_id", &self.service_id)?;
73 state.serialize_field("logical_terminal", &normalized_logical_terminal)?;
74 state.serialize_field("sender_bic", &self.sender_bic)?;
75 state.serialize_field("session_number", &self.session_number)?;
76 state.serialize_field("sequence_number", &self.sequence_number)?;
77 state.end()
78 }
79}
80
81impl<'de> serde::Deserialize<'de> for BasicHeader {
82 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
83 where
84 D: serde::Deserializer<'de>,
85 {
86 #[derive(Deserialize)]
87 struct BasicHeaderHelper {
88 application_id: String,
89 service_id: String,
90 logical_terminal: String,
91 sender_bic: String,
92 session_number: String,
93 sequence_number: String,
94 }
95
96 let helper = BasicHeaderHelper::deserialize(deserializer)?;
97
98 let normalized_logical_terminal = if helper.logical_terminal.len() > 12 {
100 helper.logical_terminal[..12].to_string()
101 } else if helper.logical_terminal.len() < 12 {
102 format!("{:X<12}", helper.logical_terminal)
103 } else {
104 helper.logical_terminal.clone()
105 };
106
107 let sender_bic = helper.sender_bic;
110
111 Ok(BasicHeader {
112 application_id: helper.application_id,
113 service_id: helper.service_id,
114 logical_terminal: normalized_logical_terminal,
115 sender_bic,
116 session_number: helper.session_number,
117 sequence_number: helper.sequence_number,
118 })
119 }
120}
121
122impl BasicHeader {
123 pub fn parse(block1: &str) -> Result<Self> {
125 if block1.len() != 25 {
128 return Err(ParseError::InvalidBlockStructure {
129 block: "1".to_string(),
130 message: format!(
131 "Block 1 must be exactly 25 characters, got {}",
132 block1.len()
133 ),
134 });
135 }
136
137 let application_id = block1[0..1].to_string();
138 let service_id = block1[1..3].to_string();
139 let raw_logical_terminal = block1[3..15].to_string();
140 let session_number = block1[15..19].to_string();
141 let sequence_number = block1[19..25].to_string();
142
143 let logical_terminal = raw_logical_terminal;
146
147 let sender_bic = if logical_terminal.len() == 12 {
155 let last_four = &logical_terminal[8..12];
159 if last_four == "XXXX" || (last_four.len() == 4 && &last_four[1..] == "XXX") {
160 logical_terminal[0..8].to_string()
162 } else {
163 let potential_branch = &logical_terminal[8..11];
165 if potential_branch.chars().all(|c| c.is_ascii_alphanumeric())
166 && potential_branch != "XXX"
167 {
168 logical_terminal[0..11].to_string()
170 } else {
171 logical_terminal[0..8].to_string()
173 }
174 }
175 } else if logical_terminal.len() >= 11 {
176 logical_terminal[0..11].to_string()
177 } else if logical_terminal.len() >= 8 {
178 logical_terminal[0..8].to_string()
179 } else {
180 logical_terminal.clone()
182 };
183
184 Ok(BasicHeader {
185 application_id,
186 service_id,
187 logical_terminal,
188 sender_bic,
189 session_number,
190 sequence_number,
191 })
192 }
193}
194
195impl std::fmt::Display for BasicHeader {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 let logical_terminal = if self.logical_terminal.len() > 12 {
204 self.logical_terminal[..12].to_string()
205 } else if self.logical_terminal.len() < 12 {
206 format!("{:X<12}", self.logical_terminal)
208 } else {
209 self.logical_terminal.clone()
210 };
211
212 let session_number = format!(
214 "{:0>4}",
215 &self.session_number[..self.session_number.len().min(4)]
216 );
217
218 let sequence_number = format!(
220 "{:0>6}",
221 &self.sequence_number[..self.sequence_number.len().min(6)]
222 );
223
224 write!(
225 f,
226 "{}{}{}{}{}",
227 self.application_id, self.service_id, logical_terminal, session_number, sequence_number
228 )
229 }
230}
231
232#[derive(Debug, Clone, PartialEq)]
238pub struct InputApplicationHeader {
239 pub message_type: String,
241 pub destination_address: String,
243 pub receiver_bic: String,
245 pub priority: String,
247 pub delivery_monitoring: Option<String>,
249 pub obsolescence_period: Option<String>,
251}
252
253impl serde::Serialize for InputApplicationHeader {
255 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
256 where
257 S: serde::Serializer,
258 {
259 use serde::ser::SerializeStruct;
260
261 let normalized_destination_address = if self.destination_address.len() > 12 {
263 self.destination_address[..12].to_string()
264 } else if self.destination_address.len() < 12 {
265 format!("{:X<12}", self.destination_address)
266 } else {
267 self.destination_address.clone()
268 };
269
270 let field_count = 4
271 + self.delivery_monitoring.is_some() as usize
272 + self.obsolescence_period.is_some() as usize;
273 let mut state = serializer.serialize_struct("InputApplicationHeader", field_count)?;
274 state.serialize_field("message_type", &self.message_type)?;
275 state.serialize_field("destination_address", &normalized_destination_address)?;
276 state.serialize_field("receiver_bic", &self.receiver_bic)?;
277 state.serialize_field("priority", &self.priority)?;
278 if let Some(ref dm) = self.delivery_monitoring {
279 state.serialize_field("delivery_monitoring", dm)?;
280 }
281 if let Some(ref op) = self.obsolescence_period {
282 state.serialize_field("obsolescence_period", op)?;
283 }
284 state.end()
285 }
286}
287
288impl<'de> serde::Deserialize<'de> for InputApplicationHeader {
289 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
290 where
291 D: serde::Deserializer<'de>,
292 {
293 #[derive(Deserialize)]
294 struct InputApplicationHeaderHelper {
295 message_type: String,
296 destination_address: String,
297 receiver_bic: String,
298 priority: String,
299 delivery_monitoring: Option<String>,
300 obsolescence_period: Option<String>,
301 }
302
303 let helper = InputApplicationHeaderHelper::deserialize(deserializer)?;
304
305 let normalized_destination_address = if helper.destination_address.len() > 12 {
307 helper.destination_address[..12].to_string()
308 } else if helper.destination_address.len() < 12 {
309 format!("{:X<12}", helper.destination_address)
310 } else {
311 helper.destination_address.clone()
312 };
313
314 let receiver_bic = helper.receiver_bic;
317
318 Ok(InputApplicationHeader {
319 message_type: helper.message_type,
320 destination_address: normalized_destination_address,
321 receiver_bic,
322 priority: helper.priority,
323 delivery_monitoring: helper.delivery_monitoring,
324 obsolescence_period: helper.obsolescence_period,
325 })
326 }
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
335pub struct OutputApplicationHeader {
336 pub message_type: String,
338 pub input_time: String,
340 pub mir: MessageInputReference,
342 pub output_date: String,
344 pub output_time: String,
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub priority: Option<String>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
360#[serde(tag = "direction")]
361pub enum ApplicationHeader {
362 #[serde(rename = "I")]
364 Input(InputApplicationHeader),
365
366 #[serde(rename = "O")]
368 Output(OutputApplicationHeader),
369}
370
371impl ApplicationHeader {
372 pub fn parse(block2: &str) -> Result<Self> {
374 if block2.len() < 4 {
375 return Err(ParseError::InvalidBlockStructure {
376 block: "2".to_string(),
377 message: format!(
378 "Block 2 too short: expected at least 4 characters, got {}",
379 block2.len()
380 ),
381 });
382 }
383
384 let direction = &block2[0..1];
385 let message_type = block2[1..4].to_string();
386
387 match direction {
388 "I" => {
389 if block2.len() < 17 {
392 return Err(ParseError::InvalidBlockStructure {
393 block: "2".to_string(),
394 message: format!(
395 "Input Block 2 too short: expected at least 17 characters, got {}",
396 block2.len()
397 ),
398 });
399 }
400
401 let raw_destination_address = block2[4..16].to_string();
402 let priority = block2[16..17].to_string();
403
404 let destination_address = raw_destination_address;
407
408 let receiver_bic = if destination_address.len() == 12 {
412 let last_four = &destination_address[8..12];
416 if last_four == "XXXX" || (last_four.len() == 4 && &last_four[1..] == "XXX") {
417 destination_address[0..8].to_string()
419 } else {
420 let potential_branch = &destination_address[8..11];
422 if potential_branch.chars().all(|c| c.is_ascii_alphanumeric())
423 && potential_branch != "XXX"
424 {
425 destination_address[0..11].to_string()
427 } else {
428 destination_address[0..8].to_string()
430 }
431 }
432 } else if destination_address.len() >= 11 {
433 destination_address[0..11].to_string()
434 } else if destination_address.len() >= 8 {
435 destination_address[0..8].to_string()
436 } else {
437 destination_address.clone()
439 };
440
441 let delivery_monitoring = if block2.len() >= 18 {
443 let monitoring = &block2[17..18];
444 if monitoring
446 .chars()
447 .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit())
448 {
449 Some(monitoring.to_string())
450 } else {
451 None
452 }
453 } else {
454 None
455 };
456
457 let obsolescence_period = if delivery_monitoring.is_some() && block2.len() >= 21 {
459 Some(block2[18..21].to_string())
460 } else {
461 None
462 };
463
464 Ok(ApplicationHeader::Input(InputApplicationHeader {
465 message_type,
466 destination_address,
467 receiver_bic,
468 priority,
469 delivery_monitoring,
470 obsolescence_period,
471 }))
472 }
473 "O" => {
474 if block2.len() < 46 {
481 return Err(ParseError::InvalidBlockStructure {
482 block: "2".to_string(),
483 message: format!(
484 "Output Block 2 too short: expected at least 46 characters, got {}",
485 block2.len()
486 ),
487 });
488 }
489
490 let input_time = block2[4..8].to_string(); let mir_date = block2[8..14].to_string(); let mir_lt_address = block2[14..26].to_string(); let mir_session = block2[26..30].to_string(); let mir_sequence = block2[30..36].to_string(); let output_date = block2[36..42].to_string(); let output_time = block2[42..46].to_string(); let priority = if block2.len() >= 47 {
503 Some(block2[46..47].to_string())
504 } else {
505 None
506 };
507
508 let mir = MessageInputReference {
510 date: mir_date,
511 lt_identifier: mir_lt_address.clone(),
512 branch_code: if mir_lt_address.len() >= 12 {
513 mir_lt_address[9..12].to_string()
514 } else {
515 "XXX".to_string()
516 },
517 session_number: mir_session,
518 sequence_number: mir_sequence,
519 };
520
521 Ok(ApplicationHeader::Output(OutputApplicationHeader {
522 message_type,
523 input_time,
524 mir,
525 output_date,
526 output_time,
527 priority,
528 }))
529 }
530 _ => Err(ParseError::InvalidBlockStructure {
531 block: "2".to_string(),
532 message: format!(
533 "Invalid direction indicator: expected 'I' or 'O', got '{}'",
534 direction
535 ),
536 }),
537 }
538 }
539
540 pub fn message_type(&self) -> &str {
542 match self {
543 ApplicationHeader::Input(header) => &header.message_type,
544 ApplicationHeader::Output(header) => &header.message_type,
545 }
546 }
547
548 pub fn priority(&self) -> Option<&str> {
550 match self {
551 ApplicationHeader::Input(header) => Some(&header.priority),
552 ApplicationHeader::Output(header) => header.priority.as_deref(),
553 }
554 }
555}
556
557impl std::fmt::Display for ApplicationHeader {
558 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
559 match self {
560 ApplicationHeader::Input(header) => {
561 write!(f, "{}", header)
563 }
564 ApplicationHeader::Output(header) => {
565 let mut result = format!(
568 "O{}{}{}{}{}{}{}{}",
569 header.message_type,
570 header.input_time,
571 header.mir.date,
572 header.mir.lt_identifier,
573 header.mir.session_number,
574 header.mir.sequence_number,
575 header.output_date,
576 header.output_time,
577 );
578
579 if let Some(ref priority) = header.priority {
580 result.push_str(priority);
581 }
582
583 write!(f, "{result}")
584 }
585 }
586 }
587}
588
589impl std::fmt::Display for InputApplicationHeader {
590 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
591 let message_type = format!(
598 "{:0>3}",
599 &self.message_type[..self.message_type.len().min(3)]
600 );
601
602 let destination_address = if self.destination_address.len() > 12 {
604 self.destination_address[..12].to_string()
605 } else if self.destination_address.len() < 12 {
606 format!("{:X<12}", self.destination_address)
607 } else {
608 self.destination_address.clone()
609 };
610
611 let mut result = format!("I{}{}{}", message_type, destination_address, self.priority);
612
613 if let Some(ref delivery_monitoring) = self.delivery_monitoring {
614 result.push_str(delivery_monitoring);
615 }
616
617 if let Some(ref obsolescence_period) = self.obsolescence_period {
618 result.push_str(obsolescence_period);
619 }
620
621 write!(f, "{result}")
622 }
623}
624
625impl std::fmt::Display for OutputApplicationHeader {
626 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
627 let mut result = format!(
628 "O{}{}{}{}{}{}{}{}",
629 self.message_type,
630 self.input_time,
631 self.mir.date,
632 self.mir.lt_identifier,
633 self.mir.session_number,
634 self.mir.sequence_number,
635 self.output_date,
636 self.output_time,
637 );
638
639 if let Some(ref priority) = self.priority {
640 result.push_str(priority);
641 }
642
643 write!(f, "{result}")
644 }
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
659pub struct UserHeader {
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub service_identifier: Option<String>,
663
664 #[serde(skip_serializing_if = "Option::is_none")]
666 pub banking_priority: Option<String>,
667
668 #[serde(skip_serializing_if = "Option::is_none")]
670 pub message_user_reference: Option<String>,
671
672 #[serde(skip_serializing_if = "Option::is_none")]
674 pub validation_flag: Option<String>,
675
676 #[serde(skip_serializing_if = "Option::is_none")]
678 pub balance_checkpoint: Option<BalanceCheckpoint>,
679
680 #[serde(skip_serializing_if = "Option::is_none")]
682 pub message_input_reference: Option<MessageInputReference>,
683
684 #[serde(skip_serializing_if = "Option::is_none")]
686 pub related_reference: Option<String>,
687
688 #[serde(skip_serializing_if = "Option::is_none")]
690 #[serde(rename = "service_type_identifier")]
691 pub service_type_identifier: Option<String>,
692
693 #[serde(skip_serializing_if = "Option::is_none")]
695 pub unique_end_to_end_reference: Option<String>,
696
697 #[serde(skip_serializing_if = "Option::is_none")]
699 pub addressee_information: Option<String>,
700
701 #[serde(skip_serializing_if = "Option::is_none")]
703 pub payment_release_information: Option<PaymentReleaseInfo>,
704
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub sanctions_screening_info: Option<SanctionsScreeningInfo>,
708
709 #[serde(skip_serializing_if = "Option::is_none")]
711 pub payment_controls_info: Option<PaymentControlsInfo>,
712}
713
714#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
716pub struct BalanceCheckpoint {
717 pub date: String,
719 pub time: String,
721 #[serde(skip_serializing_if = "Option::is_none")]
723 pub hundredths_of_second: Option<String>,
724}
725
726#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
728pub struct MessageInputReference {
729 pub date: String,
731 pub lt_identifier: String,
733 pub branch_code: String,
735 pub session_number: String,
737 pub sequence_number: String,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
743pub struct PaymentReleaseInfo {
744 pub code: String,
746 #[serde(skip_serializing_if = "Option::is_none")]
748 pub additional_info: Option<String>,
749}
750
751#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
753pub struct SanctionsScreeningInfo {
754 pub code_word: String,
756 #[serde(skip_serializing_if = "Option::is_none")]
758 pub additional_info: Option<String>,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
763pub struct PaymentControlsInfo {
764 pub code_word: String,
766 #[serde(skip_serializing_if = "Option::is_none")]
768 pub additional_info: Option<String>,
769}
770
771impl UserHeader {
772 pub fn parse(block3: &str) -> Result<Self> {
774 let mut user_header = UserHeader::default();
775
776 if block3.contains("{103:")
779 && let Some(start) = block3.find("{103:")
780 && let Some(end) = block3[start..].find('}')
781 {
782 user_header.service_identifier = Some(block3[start + 5..start + end].to_string());
783 }
784
785 if block3.contains("{113:")
786 && let Some(start) = block3.find("{113:")
787 && let Some(end) = block3[start..].find('}')
788 {
789 user_header.banking_priority = Some(block3[start + 5..start + end].to_string());
790 }
791
792 if block3.contains("{108:")
793 && let Some(start) = block3.find("{108:")
794 && let Some(end) = block3[start..].find('}')
795 {
796 user_header.message_user_reference = Some(block3[start + 5..start + end].to_string());
797 }
798
799 if block3.contains("{119:")
800 && let Some(start) = block3.find("{119:")
801 && let Some(end) = block3[start..].find('}')
802 {
803 user_header.validation_flag = Some(block3[start + 5..start + end].to_string());
804 }
805
806 if block3.contains("{423:")
807 && let Some(start) = block3.find("{423:")
808 && let Some(end) = block3[start..].find('}')
809 {
810 let value = &block3[start + 5..start + end];
811 user_header.balance_checkpoint = Self::parse_balance_checkpoint(value);
812 }
813
814 if block3.contains("{106:")
815 && let Some(start) = block3.find("{106:")
816 && let Some(end) = block3[start..].find('}')
817 {
818 let value = &block3[start + 5..start + end];
819 user_header.message_input_reference = Self::parse_message_input_reference(value);
820 }
821
822 if block3.contains("{424:")
823 && let Some(start) = block3.find("{424:")
824 && let Some(end) = block3[start..].find('}')
825 {
826 user_header.related_reference = Some(block3[start + 5..start + end].to_string());
827 }
828
829 if block3.contains("{111:")
830 && let Some(start) = block3.find("{111:")
831 && let Some(end) = block3[start..].find('}')
832 {
833 user_header.service_type_identifier = Some(block3[start + 5..start + end].to_string());
834 }
835
836 if block3.contains("{121:")
837 && let Some(start) = block3.find("{121:")
838 && let Some(end) = block3[start..].find('}')
839 {
840 user_header.unique_end_to_end_reference =
841 Some(block3[start + 5..start + end].to_string());
842 }
843
844 if block3.contains("{115:")
845 && let Some(start) = block3.find("{115:")
846 && let Some(end) = block3[start..].find('}')
847 {
848 user_header.addressee_information = Some(block3[start + 5..start + end].to_string());
849 }
850
851 if block3.contains("{165:")
852 && let Some(start) = block3.find("{165:")
853 && let Some(end) = block3[start..].find('}')
854 {
855 let value = &block3[start + 5..start + end];
856 user_header.payment_release_information = Self::parse_payment_release_info(value);
857 }
858
859 if block3.contains("{433:")
860 && let Some(start) = block3.find("{433:")
861 && let Some(end) = block3[start..].find('}')
862 {
863 let value = &block3[start + 5..start + end];
864 user_header.sanctions_screening_info = Self::parse_sanctions_screening_info(value);
865 }
866
867 if block3.contains("{434:")
868 && let Some(start) = block3.find("{434:")
869 && let Some(end) = block3[start..].find('}')
870 {
871 let value = &block3[start + 5..start + end];
872 user_header.payment_controls_info = Self::parse_payment_controls_info(value);
873 }
874
875 Ok(user_header)
876 }
877
878 fn parse_balance_checkpoint(value: &str) -> Option<BalanceCheckpoint> {
880 if value.len() >= 12 {
881 Some(BalanceCheckpoint {
882 date: value[0..6].to_string(),
883 time: value[6..12].to_string(),
884 hundredths_of_second: if value.len() > 12 {
885 Some(value[12..].to_string())
886 } else {
887 None
888 },
889 })
890 } else {
891 None
892 }
893 }
894
895 fn parse_message_input_reference(value: &str) -> Option<MessageInputReference> {
897 if value.len() >= 28 {
898 Some(MessageInputReference {
899 date: value[0..6].to_string(),
900 lt_identifier: value[6..18].to_string(),
901 branch_code: value[18..21].to_string(),
902 session_number: value[21..25].to_string(),
903 sequence_number: value[25..].to_string(),
904 })
905 } else {
906 None
907 }
908 }
909
910 fn parse_payment_release_info(value: &str) -> Option<PaymentReleaseInfo> {
912 if value.len() >= 3 {
913 let code = value[0..3].to_string();
914 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
915 Some(value[4..].to_string())
916 } else {
917 None
918 };
919 Some(PaymentReleaseInfo {
920 code,
921 additional_info,
922 })
923 } else {
924 None
925 }
926 }
927
928 fn parse_sanctions_screening_info(value: &str) -> Option<SanctionsScreeningInfo> {
930 if value.len() >= 3 {
931 let code_word = value[0..3].to_string();
932 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
933 Some(value[4..].to_string())
934 } else {
935 None
936 };
937 Some(SanctionsScreeningInfo {
938 code_word,
939 additional_info,
940 })
941 } else {
942 None
943 }
944 }
945
946 fn parse_payment_controls_info(value: &str) -> Option<PaymentControlsInfo> {
948 if value.len() >= 3 {
949 let code_word = value[0..3].to_string();
950 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
951 Some(value[4..].to_string())
952 } else {
953 None
954 };
955 Some(PaymentControlsInfo {
956 code_word,
957 additional_info,
958 })
959 } else {
960 None
961 }
962 }
963}
964
965impl std::fmt::Display for UserHeader {
966 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
967 let mut result = String::new();
968
969 if let Some(ref service_id) = self.service_identifier {
970 result.push_str(&format!("{{103:{service_id}}}"));
971 }
972
973 if let Some(ref banking_priority) = self.banking_priority {
974 result.push_str(&format!("{{113:{banking_priority}}}"));
975 }
976
977 if let Some(ref message_user_ref) = self.message_user_reference {
978 result.push_str(&format!("{{108:{message_user_ref}}}"));
979 }
980
981 if let Some(ref validation_flag) = self.validation_flag {
982 result.push_str(&format!("{{119:{validation_flag}}}"));
983 }
984
985 if let Some(ref unique_end_to_end_ref) = self.unique_end_to_end_reference {
986 result.push_str(&format!("{{121:{unique_end_to_end_ref}}}"));
987 }
988
989 if let Some(ref service_type_identifier) = self.service_type_identifier {
990 result.push_str(&format!("{{111:{service_type_identifier}}}"));
991 }
992
993 if let Some(ref payment_controls) = self.payment_controls_info {
994 let mut value = payment_controls.code_word.clone();
995 if let Some(ref additional) = payment_controls.additional_info {
996 value.push('/');
997 value.push_str(additional);
998 }
999 result.push_str(&format!("{{434:{value}}}"));
1000 }
1001
1002 if let Some(ref payment_release) = self.payment_release_information {
1003 let mut value = payment_release.code.clone();
1004 if let Some(ref additional) = payment_release.additional_info {
1005 value.push('/');
1006 value.push_str(additional);
1007 }
1008 result.push_str(&format!("{{165:{value}}}"));
1009 }
1010
1011 if let Some(ref sanctions) = self.sanctions_screening_info {
1012 let mut value = sanctions.code_word.clone();
1013 if let Some(ref additional) = sanctions.additional_info {
1014 value.push('/');
1015 value.push_str(additional);
1016 }
1017 result.push_str(&format!("{{433:{value}}}"));
1018 }
1019
1020 write!(f, "{result}")
1021 }
1022}
1023
1024#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1036pub struct Trailer {
1037 #[serde(skip_serializing_if = "Option::is_none")]
1039 pub checksum: Option<String>,
1040
1041 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub test_and_training: Option<bool>,
1044
1045 #[serde(skip_serializing_if = "Option::is_none")]
1047 pub possible_duplicate_emission: Option<PossibleDuplicateEmission>,
1048
1049 #[serde(skip_serializing_if = "Option::is_none")]
1051 pub delayed_message: Option<bool>,
1052
1053 #[serde(skip_serializing_if = "Option::is_none")]
1055 pub message_reference: Option<MessageReference>,
1056
1057 #[serde(skip_serializing_if = "Option::is_none")]
1059 pub possible_duplicate_message: Option<PossibleDuplicateMessage>,
1060
1061 #[serde(skip_serializing_if = "Option::is_none")]
1063 pub system_originated_message: Option<SystemOriginatedMessage>,
1064
1065 #[serde(skip_serializing_if = "Option::is_none")]
1067 pub mac: Option<String>,
1068}
1069
1070#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1072pub struct PossibleDuplicateEmission {
1073 #[serde(skip_serializing_if = "Option::is_none")]
1075 pub time: Option<String>,
1076 #[serde(skip_serializing_if = "Option::is_none")]
1078 pub message_input_reference: Option<MessageInputReference>,
1079}
1080
1081#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1083pub struct MessageReference {
1084 pub date: String,
1086 pub full_time: String,
1088 pub message_input_reference: MessageInputReference,
1090}
1091
1092#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1094pub struct PossibleDuplicateMessage {
1095 #[serde(skip_serializing_if = "Option::is_none")]
1097 pub time: Option<String>,
1098 #[serde(skip_serializing_if = "Option::is_none")]
1100 pub message_output_reference: Option<MessageOutputReference>,
1101}
1102
1103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1105pub struct MessageOutputReference {
1106 pub date: String,
1108 pub lt_identifier: String,
1110 pub branch_code: String,
1112 pub session_number: String,
1114 pub sequence_number: String,
1116}
1117
1118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1120pub struct SystemOriginatedMessage {
1121 #[serde(skip_serializing_if = "Option::is_none")]
1123 pub time: Option<String>,
1124 #[serde(skip_serializing_if = "Option::is_none")]
1126 pub message_input_reference: Option<MessageInputReference>,
1127}
1128
1129impl Trailer {
1130 pub fn parse(block5: &str) -> Result<Self> {
1132 let mut trailer = Trailer::default();
1133
1134 if block5.contains("{CHK:")
1136 && let Some(start) = block5.find("{CHK:")
1137 && let Some(end) = block5[start..].find('}')
1138 {
1139 trailer.checksum = Some(block5[start + 5..start + end].to_string());
1140 }
1141
1142 if block5.contains("{TNG}") {
1143 trailer.test_and_training = Some(true);
1144 }
1145
1146 if block5.contains("{DLM}") {
1147 trailer.delayed_message = Some(true);
1148 }
1149
1150 if block5.contains("{MAC:")
1151 && let Some(start) = block5.find("{MAC:")
1152 && let Some(end) = block5[start..].find('}')
1153 {
1154 trailer.mac = Some(block5[start + 5..start + end].to_string());
1155 }
1156
1157 Ok(trailer)
1161 }
1162}
1163
1164impl std::fmt::Display for Trailer {
1165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1166 let mut result = String::new();
1167
1168 if let Some(ref checksum) = self.checksum {
1169 result.push_str(&format!("{{CHK:{checksum}}}"));
1170 }
1171
1172 if let Some(true) = self.test_and_training {
1173 result.push_str("{TNG}");
1174 }
1175
1176 if let Some(true) = self.delayed_message {
1177 result.push_str("{DLM}");
1178 }
1179
1180 if let Some(ref possible_duplicate_emission) = self.possible_duplicate_emission {
1181 result.push_str(&format!(
1182 "{{PDE:{}}}",
1183 possible_duplicate_emission.time.as_deref().unwrap_or("")
1184 ));
1185 }
1186
1187 if let Some(ref message_reference) = self.message_reference {
1188 result.push_str(&format!("{{MRF:{}}}", message_reference.date));
1189 }
1190
1191 if let Some(ref mac) = self.mac {
1192 result.push_str(&format!("{{MAC:{mac}}}"));
1193 }
1194
1195 write!(f, "{result}")
1196 }
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202
1203 #[test]
1204 fn test_application_header_input_parsing() {
1205 let block2 = "I103DEUTDEFFAXXXN";
1207 let header = ApplicationHeader::parse(block2).unwrap();
1208
1209 match header {
1210 ApplicationHeader::Input(input) => {
1211 assert_eq!(input.message_type, "103");
1212 assert_eq!(input.destination_address, "DEUTDEFFAXXX");
1213 assert_eq!(input.receiver_bic, "DEUTDEFF");
1214 assert_eq!(input.priority, "N");
1215 assert_eq!(input.delivery_monitoring, None);
1216 assert_eq!(input.obsolescence_period, None);
1217 }
1218 ApplicationHeader::Output(_) => panic!("Expected Input header, got Output"),
1219 }
1220 }
1221
1222 #[test]
1223 fn test_application_header_input_parsing_with_monitoring() {
1224 let block2 = "I103DEUTDEFFAXXXU3003";
1226 let header = ApplicationHeader::parse(block2).unwrap();
1227
1228 match header {
1229 ApplicationHeader::Input(input) => {
1230 assert_eq!(input.message_type, "103");
1231 assert_eq!(input.destination_address, "DEUTDEFFAXXX");
1232 assert_eq!(input.receiver_bic, "DEUTDEFF");
1233 assert_eq!(input.priority, "U");
1234 assert_eq!(input.delivery_monitoring, Some("3".to_string()));
1235 assert_eq!(input.obsolescence_period, Some("003".to_string()));
1236 }
1237 ApplicationHeader::Output(_) => panic!("Expected Input header, got Output"),
1238 }
1239 }
1240
1241 #[test]
1242 fn test_application_header_output_parsing() {
1243 let block2 = "O1031535051028DEUTDEFFAXXX08264556280510281535N";
1245 let header = ApplicationHeader::parse(block2).unwrap();
1246
1247 match header {
1248 ApplicationHeader::Output(output) => {
1249 assert_eq!(output.message_type, "103");
1250 assert_eq!(output.input_time, "1535");
1251 assert_eq!(output.output_date, "051028");
1252 assert_eq!(output.output_time, "1535");
1253 assert_eq!(output.priority, Some("N".to_string()));
1254
1255 assert_eq!(output.mir.date, "051028");
1257 assert_eq!(output.mir.lt_identifier, "DEUTDEFFAXXX");
1258 assert_eq!(output.mir.branch_code, "XXX");
1259 assert_eq!(output.mir.session_number, "0826");
1260 assert_eq!(output.mir.sequence_number, "455628");
1261 }
1262 ApplicationHeader::Input(_) => panic!("Expected Output header, got Input"),
1263 }
1264 }
1265
1266 #[test]
1267 fn test_application_header_output_parsing_different_message_type() {
1268 let block2 = "O2021245051028CHASUS33AXXX08264556280510281245U";
1270 let header = ApplicationHeader::parse(block2).unwrap();
1271
1272 match header {
1273 ApplicationHeader::Output(output) => {
1274 assert_eq!(output.message_type, "202");
1275 assert_eq!(output.mir.lt_identifier, "CHASUS33AXXX");
1276 assert_eq!(output.priority, Some("U".to_string()));
1277 }
1278 ApplicationHeader::Input(_) => panic!("Expected Output header, got Input"),
1279 }
1280 }
1281
1282 #[test]
1283 fn test_application_header_invalid_direction() {
1284 let block2 = "X103DEUTDEFFAXXXN";
1285 let result = ApplicationHeader::parse(block2);
1286
1287 assert!(result.is_err());
1288 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1289 assert!(message.contains("Invalid direction indicator"));
1290 } else {
1291 panic!("Expected InvalidBlockStructure error");
1292 }
1293 }
1294
1295 #[test]
1296 fn test_application_header_input_too_short() {
1297 let block2 = "I103DEUTDEF"; let result = ApplicationHeader::parse(block2);
1299
1300 assert!(result.is_err());
1301 }
1302
1303 #[test]
1304 fn test_application_header_output_too_short() {
1305 let block2 = "O103153505102"; let result = ApplicationHeader::parse(block2);
1307
1308 assert!(result.is_err());
1309 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1310 assert!(message.contains("Output Block 2 too short: expected at least 46 characters"));
1312 } else {
1313 panic!("Expected InvalidBlockStructure error");
1314 }
1315 }
1316
1317 #[test]
1318 fn test_application_header_output_minimum_length_but_still_too_short() {
1319 let block2 = "O10315350510280DE"; let result = ApplicationHeader::parse(block2);
1322
1323 assert!(result.is_err());
1324 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1325 assert!(message.contains("Output Block 2 too short: expected at least 46 characters"));
1326 } else {
1327 panic!("Expected InvalidBlockStructure error");
1328 }
1329 }
1330
1331 #[test]
1332 fn test_basic_header_parsing() {
1333 let block1 = "F01DEUTDEFFAXXX0000123456";
1334 let header = BasicHeader::parse(block1).unwrap();
1335
1336 assert_eq!(header.application_id, "F");
1337 assert_eq!(header.service_id, "01");
1338 assert_eq!(header.logical_terminal, "DEUTDEFFAXXX");
1339 assert_eq!(header.sender_bic, "DEUTDEFF");
1340 assert_eq!(header.session_number, "0000");
1341 assert_eq!(header.sequence_number, "123456");
1342 }
1343
1344 #[test]
1345 fn test_application_header_input_display() {
1346 let header = ApplicationHeader::Input(InputApplicationHeader {
1347 message_type: "103".to_string(),
1348 destination_address: "DEUTDEFFAXXX".to_string(),
1349 receiver_bic: "DEUTDEFF".to_string(),
1350 priority: "U".to_string(),
1351 delivery_monitoring: Some("3".to_string()),
1352 obsolescence_period: Some("003".to_string()),
1353 });
1354
1355 assert_eq!(header.to_string(), "I103DEUTDEFFAXXXU3003");
1356 }
1357
1358 #[test]
1359 fn test_application_header_output_display() {
1360 let mir = MessageInputReference {
1361 date: "051028".to_string(),
1362 lt_identifier: "DEUTDEFFAXXX".to_string(),
1363 branch_code: "XXX".to_string(),
1364 session_number: "0826".to_string(),
1365 sequence_number: "455628".to_string(),
1366 };
1367
1368 let header = ApplicationHeader::Output(OutputApplicationHeader {
1369 message_type: "103".to_string(),
1370 input_time: "1535".to_string(),
1371 mir,
1372 output_date: "051028".to_string(),
1373 output_time: "1535".to_string(),
1374 priority: Some("N".to_string()),
1375 });
1376
1377 assert_eq!(
1378 header.to_string(),
1379 "O1031535051028DEUTDEFFAXXX08264556280510281535N"
1380 );
1381 }
1382
1383 #[test]
1384 fn test_application_header_helper_methods() {
1385 let input_header = ApplicationHeader::Input(InputApplicationHeader {
1386 message_type: "103".to_string(),
1387 destination_address: "DEUTDEFFAXXX".to_string(),
1388 receiver_bic: "DEUTDEFF".to_string(),
1389 priority: "U".to_string(),
1390 delivery_monitoring: None,
1391 obsolescence_period: None,
1392 });
1393
1394 assert_eq!(input_header.message_type(), "103");
1395 assert_eq!(input_header.priority(), Some("U"));
1396
1397 let mir = MessageInputReference {
1398 date: "051028".to_string(),
1399 lt_identifier: "DEUTDEFFAXXX".to_string(),
1400 branch_code: "XXX".to_string(),
1401 session_number: "0826".to_string(),
1402 sequence_number: "455628".to_string(),
1403 };
1404
1405 let output_header = ApplicationHeader::Output(OutputApplicationHeader {
1406 message_type: "202".to_string(),
1407 input_time: "1535".to_string(),
1408 mir,
1409 output_date: "051028".to_string(),
1410 output_time: "1535".to_string(),
1411 priority: Some("N".to_string()),
1412 });
1413
1414 assert_eq!(output_header.message_type(), "202");
1415 assert_eq!(output_header.priority(), Some("N"));
1416 }
1417}