1use crate::errors::{ParseError, Result};
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, PartialEq)]
38#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
39pub struct BasicHeader {
40 pub application_id: String,
42 pub service_id: String,
44 pub logical_terminal: String,
46 pub sender_bic: String,
48 pub session_number: String,
50 pub sequence_number: String,
52}
53
54impl serde::Serialize for BasicHeader {
56 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
57 where
58 S: serde::Serializer,
59 {
60 use serde::ser::SerializeStruct;
61
62 let normalized_logical_terminal = if self.logical_terminal.len() > 12 {
64 self.logical_terminal[..12].to_string()
65 } else if self.logical_terminal.len() < 12 {
66 format!("{:X<12}", self.logical_terminal)
67 } else {
68 self.logical_terminal.clone()
69 };
70
71 let mut state = serializer.serialize_struct("BasicHeader", 5)?;
72 state.serialize_field("application_id", &self.application_id)?;
73 state.serialize_field("service_id", &self.service_id)?;
74 state.serialize_field("logical_terminal", &normalized_logical_terminal)?;
75 state.serialize_field("sender_bic", &self.sender_bic)?;
76 state.serialize_field("session_number", &self.session_number)?;
77 state.serialize_field("sequence_number", &self.sequence_number)?;
78 state.end()
79 }
80}
81
82impl<'de> serde::Deserialize<'de> for BasicHeader {
83 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
84 where
85 D: serde::Deserializer<'de>,
86 {
87 #[derive(Deserialize)]
88 struct BasicHeaderHelper {
89 application_id: String,
90 service_id: String,
91 logical_terminal: String,
92 sender_bic: String,
93 session_number: String,
94 sequence_number: String,
95 }
96
97 let helper = BasicHeaderHelper::deserialize(deserializer)?;
98
99 let normalized_logical_terminal = if helper.logical_terminal.len() > 12 {
101 helper.logical_terminal[..12].to_string()
102 } else if helper.logical_terminal.len() < 12 {
103 format!("{:X<12}", helper.logical_terminal)
104 } else {
105 helper.logical_terminal.clone()
106 };
107
108 let sender_bic = helper.sender_bic;
111
112 Ok(BasicHeader {
113 application_id: helper.application_id,
114 service_id: helper.service_id,
115 logical_terminal: normalized_logical_terminal,
116 sender_bic,
117 session_number: helper.session_number,
118 sequence_number: helper.sequence_number,
119 })
120 }
121}
122
123impl BasicHeader {
124 pub fn parse(block1: &str) -> Result<Self> {
126 if block1.len() != 25 {
129 return Err(ParseError::InvalidBlockStructure {
130 block: "1".to_string(),
131 message: format!(
132 "Block 1 must be exactly 25 characters, got {}",
133 block1.len()
134 ),
135 });
136 }
137
138 let application_id = block1[0..1].to_string();
139 let service_id = block1[1..3].to_string();
140 let raw_logical_terminal = block1[3..15].to_string();
141 let session_number = block1[15..19].to_string();
142 let sequence_number = block1[19..25].to_string();
143
144 let logical_terminal = raw_logical_terminal;
147
148 let sender_bic = if logical_terminal.len() == 12 {
156 let last_four = &logical_terminal[8..12];
160 if last_four == "XXXX" || (last_four.len() == 4 && &last_four[1..] == "XXX") {
161 logical_terminal[0..8].to_string()
163 } else {
164 let potential_branch = &logical_terminal[8..11];
166 if potential_branch.chars().all(|c| c.is_ascii_alphanumeric())
167 && potential_branch != "XXX"
168 {
169 logical_terminal[0..11].to_string()
171 } else {
172 logical_terminal[0..8].to_string()
174 }
175 }
176 } else if logical_terminal.len() >= 11 {
177 logical_terminal[0..11].to_string()
178 } else if logical_terminal.len() >= 8 {
179 logical_terminal[0..8].to_string()
180 } else {
181 logical_terminal.clone()
183 };
184
185 Ok(BasicHeader {
186 application_id,
187 service_id,
188 logical_terminal,
189 sender_bic,
190 session_number,
191 sequence_number,
192 })
193 }
194}
195
196impl std::fmt::Display for BasicHeader {
197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198 let logical_terminal = if self.logical_terminal.len() > 12 {
205 self.logical_terminal[..12].to_string()
206 } else if self.logical_terminal.len() < 12 {
207 format!("{:X<12}", self.logical_terminal)
209 } else {
210 self.logical_terminal.clone()
211 };
212
213 let session_number = format!(
215 "{:0>4}",
216 &self.session_number[..self.session_number.len().min(4)]
217 );
218
219 let sequence_number = format!(
221 "{:0>6}",
222 &self.sequence_number[..self.sequence_number.len().min(6)]
223 );
224
225 write!(
226 f,
227 "{}{}{}{}{}",
228 self.application_id, self.service_id, logical_terminal, session_number, sequence_number
229 )
230 }
231}
232
233#[derive(Debug, Clone, PartialEq)]
239#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
240pub struct InputApplicationHeader {
241 pub message_type: String,
243 pub destination_address: String,
245 pub receiver_bic: String,
247 pub priority: String,
249 pub delivery_monitoring: Option<String>,
251 pub obsolescence_period: Option<String>,
253}
254
255impl serde::Serialize for InputApplicationHeader {
257 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
258 where
259 S: serde::Serializer,
260 {
261 use serde::ser::SerializeStruct;
262
263 let normalized_destination_address = if self.destination_address.len() > 12 {
265 self.destination_address[..12].to_string()
266 } else if self.destination_address.len() < 12 {
267 format!("{:X<12}", self.destination_address)
268 } else {
269 self.destination_address.clone()
270 };
271
272 let field_count = 4
273 + self.delivery_monitoring.is_some() as usize
274 + self.obsolescence_period.is_some() as usize;
275 let mut state = serializer.serialize_struct("InputApplicationHeader", field_count)?;
276 state.serialize_field("message_type", &self.message_type)?;
277 state.serialize_field("destination_address", &normalized_destination_address)?;
278 state.serialize_field("receiver_bic", &self.receiver_bic)?;
279 state.serialize_field("priority", &self.priority)?;
280 if let Some(ref dm) = self.delivery_monitoring {
281 state.serialize_field("delivery_monitoring", dm)?;
282 }
283 if let Some(ref op) = self.obsolescence_period {
284 state.serialize_field("obsolescence_period", op)?;
285 }
286 state.end()
287 }
288}
289
290impl<'de> serde::Deserialize<'de> for InputApplicationHeader {
291 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
292 where
293 D: serde::Deserializer<'de>,
294 {
295 #[derive(Deserialize)]
296 struct InputApplicationHeaderHelper {
297 message_type: String,
298 destination_address: String,
299 receiver_bic: String,
300 priority: String,
301 delivery_monitoring: Option<String>,
302 obsolescence_period: Option<String>,
303 }
304
305 let helper = InputApplicationHeaderHelper::deserialize(deserializer)?;
306
307 let normalized_destination_address = if helper.destination_address.len() > 12 {
309 helper.destination_address[..12].to_string()
310 } else if helper.destination_address.len() < 12 {
311 format!("{:X<12}", helper.destination_address)
312 } else {
313 helper.destination_address.clone()
314 };
315
316 let receiver_bic = helper.receiver_bic;
319
320 Ok(InputApplicationHeader {
321 message_type: helper.message_type,
322 destination_address: normalized_destination_address,
323 receiver_bic,
324 priority: helper.priority,
325 delivery_monitoring: helper.delivery_monitoring,
326 obsolescence_period: helper.obsolescence_period,
327 })
328 }
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
337#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
338pub struct OutputApplicationHeader {
339 pub message_type: String,
341 pub input_time: String,
343 pub mir: MessageInputReference,
345 pub output_date: String,
347 pub output_time: String,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub priority: Option<String>,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
363#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
364#[serde(tag = "direction")]
365pub enum ApplicationHeader {
366 #[serde(rename = "I")]
368 Input(InputApplicationHeader),
369
370 #[serde(rename = "O")]
372 Output(OutputApplicationHeader),
373}
374
375impl ApplicationHeader {
376 pub fn parse(block2: &str) -> Result<Self> {
378 if block2.len() < 4 {
379 return Err(ParseError::InvalidBlockStructure {
380 block: "2".to_string(),
381 message: format!(
382 "Block 2 too short: expected at least 4 characters, got {}",
383 block2.len()
384 ),
385 });
386 }
387
388 let direction = &block2[0..1];
389 let message_type = block2[1..4].to_string();
390
391 match direction {
392 "I" => {
393 if block2.len() < 17 {
396 return Err(ParseError::InvalidBlockStructure {
397 block: "2".to_string(),
398 message: format!(
399 "Input Block 2 too short: expected at least 17 characters, got {}",
400 block2.len()
401 ),
402 });
403 }
404
405 let raw_destination_address = block2[4..16].to_string();
406 let priority = block2[16..17].to_string();
407
408 let destination_address = raw_destination_address;
411
412 let receiver_bic = if destination_address.len() == 12 {
416 let last_four = &destination_address[8..12];
420 if last_four == "XXXX" || (last_four.len() == 4 && &last_four[1..] == "XXX") {
421 destination_address[0..8].to_string()
423 } else {
424 let potential_branch = &destination_address[8..11];
426 if potential_branch.chars().all(|c| c.is_ascii_alphanumeric())
427 && potential_branch != "XXX"
428 {
429 destination_address[0..11].to_string()
431 } else {
432 destination_address[0..8].to_string()
434 }
435 }
436 } else if destination_address.len() >= 11 {
437 destination_address[0..11].to_string()
438 } else if destination_address.len() >= 8 {
439 destination_address[0..8].to_string()
440 } else {
441 destination_address.clone()
443 };
444
445 let delivery_monitoring = if block2.len() >= 18 {
447 let monitoring = &block2[17..18];
448 if monitoring
450 .chars()
451 .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit())
452 {
453 Some(monitoring.to_string())
454 } else {
455 None
456 }
457 } else {
458 None
459 };
460
461 let obsolescence_period = if delivery_monitoring.is_some() && block2.len() >= 21 {
463 Some(block2[18..21].to_string())
464 } else {
465 None
466 };
467
468 Ok(ApplicationHeader::Input(InputApplicationHeader {
469 message_type,
470 destination_address,
471 receiver_bic,
472 priority,
473 delivery_monitoring,
474 obsolescence_period,
475 }))
476 }
477 "O" => {
478 if block2.len() < 46 {
485 return Err(ParseError::InvalidBlockStructure {
486 block: "2".to_string(),
487 message: format!(
488 "Output Block 2 too short: expected at least 46 characters, got {}",
489 block2.len()
490 ),
491 });
492 }
493
494 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 {
507 Some(block2[46..47].to_string())
508 } else {
509 None
510 };
511
512 let mir = MessageInputReference {
514 date: mir_date,
515 lt_identifier: mir_lt_address.clone(),
516 branch_code: if mir_lt_address.len() >= 12 {
517 mir_lt_address[9..12].to_string()
518 } else {
519 "XXX".to_string()
520 },
521 session_number: mir_session,
522 sequence_number: mir_sequence,
523 };
524
525 Ok(ApplicationHeader::Output(OutputApplicationHeader {
526 message_type,
527 input_time,
528 mir,
529 output_date,
530 output_time,
531 priority,
532 }))
533 }
534 _ => Err(ParseError::InvalidBlockStructure {
535 block: "2".to_string(),
536 message: format!(
537 "Invalid direction indicator: expected 'I' or 'O', got '{}'",
538 direction
539 ),
540 }),
541 }
542 }
543
544 pub fn message_type(&self) -> &str {
546 match self {
547 ApplicationHeader::Input(header) => &header.message_type,
548 ApplicationHeader::Output(header) => &header.message_type,
549 }
550 }
551
552 pub fn priority(&self) -> Option<&str> {
554 match self {
555 ApplicationHeader::Input(header) => Some(&header.priority),
556 ApplicationHeader::Output(header) => header.priority.as_deref(),
557 }
558 }
559}
560
561impl std::fmt::Display for ApplicationHeader {
562 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563 match self {
564 ApplicationHeader::Input(header) => {
565 write!(f, "{}", header)
567 }
568 ApplicationHeader::Output(header) => {
569 let mut result = format!(
572 "O{}{}{}{}{}{}{}{}",
573 header.message_type,
574 header.input_time,
575 header.mir.date,
576 header.mir.lt_identifier,
577 header.mir.session_number,
578 header.mir.sequence_number,
579 header.output_date,
580 header.output_time,
581 );
582
583 if let Some(ref priority) = header.priority {
584 result.push_str(priority);
585 }
586
587 write!(f, "{result}")
588 }
589 }
590 }
591}
592
593impl std::fmt::Display for InputApplicationHeader {
594 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
595 let message_type = format!(
602 "{:0>3}",
603 &self.message_type[..self.message_type.len().min(3)]
604 );
605
606 let destination_address = if self.destination_address.len() > 12 {
608 self.destination_address[..12].to_string()
609 } else if self.destination_address.len() < 12 {
610 format!("{:X<12}", self.destination_address)
611 } else {
612 self.destination_address.clone()
613 };
614
615 let mut result = format!("I{}{}{}", message_type, destination_address, self.priority);
616
617 if let Some(ref delivery_monitoring) = self.delivery_monitoring {
618 result.push_str(delivery_monitoring);
619 }
620
621 if let Some(ref obsolescence_period) = self.obsolescence_period {
622 result.push_str(obsolescence_period);
623 }
624
625 write!(f, "{result}")
626 }
627}
628
629impl std::fmt::Display for OutputApplicationHeader {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 let mut result = format!(
632 "O{}{}{}{}{}{}{}{}",
633 self.message_type,
634 self.input_time,
635 self.mir.date,
636 self.mir.lt_identifier,
637 self.mir.session_number,
638 self.mir.sequence_number,
639 self.output_date,
640 self.output_time,
641 );
642
643 if let Some(ref priority) = self.priority {
644 result.push_str(priority);
645 }
646
647 write!(f, "{result}")
648 }
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
663#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
664pub struct UserHeader {
665 #[serde(skip_serializing_if = "Option::is_none")]
667 pub service_identifier: Option<String>,
668
669 #[serde(skip_serializing_if = "Option::is_none")]
671 pub banking_priority: Option<String>,
672
673 #[serde(skip_serializing_if = "Option::is_none")]
675 pub message_user_reference: Option<String>,
676
677 #[serde(skip_serializing_if = "Option::is_none")]
679 pub validation_flag: Option<String>,
680
681 #[serde(skip_serializing_if = "Option::is_none")]
683 pub balance_checkpoint: Option<BalanceCheckpoint>,
684
685 #[serde(skip_serializing_if = "Option::is_none")]
687 pub message_input_reference: Option<MessageInputReference>,
688
689 #[serde(skip_serializing_if = "Option::is_none")]
691 pub related_reference: Option<String>,
692
693 #[serde(skip_serializing_if = "Option::is_none")]
695 #[serde(rename = "service_type_identifier")]
696 pub service_type_identifier: Option<String>,
697
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub unique_end_to_end_reference: Option<String>,
701
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub addressee_information: Option<String>,
705
706 #[serde(skip_serializing_if = "Option::is_none")]
708 pub payment_release_information: Option<PaymentReleaseInfo>,
709
710 #[serde(skip_serializing_if = "Option::is_none")]
712 pub sanctions_screening_info: Option<SanctionsScreeningInfo>,
713
714 #[serde(skip_serializing_if = "Option::is_none")]
716 pub payment_controls_info: Option<PaymentControlsInfo>,
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
721#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
722pub struct BalanceCheckpoint {
723 pub date: String,
725 pub time: String,
727 #[serde(skip_serializing_if = "Option::is_none")]
729 pub hundredths_of_second: Option<String>,
730}
731
732#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
734#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
735pub struct MessageInputReference {
736 pub date: String,
738 pub lt_identifier: String,
740 pub branch_code: String,
742 pub session_number: String,
744 pub sequence_number: String,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
750#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
751pub struct PaymentReleaseInfo {
752 pub code: String,
754 #[serde(skip_serializing_if = "Option::is_none")]
756 pub additional_info: Option<String>,
757}
758
759#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
761#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
762pub struct SanctionsScreeningInfo {
763 pub code_word: String,
765 #[serde(skip_serializing_if = "Option::is_none")]
767 pub additional_info: Option<String>,
768}
769
770#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
772#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
773pub struct PaymentControlsInfo {
774 pub code_word: String,
776 #[serde(skip_serializing_if = "Option::is_none")]
778 pub additional_info: Option<String>,
779}
780
781impl UserHeader {
782 pub fn parse(block3: &str) -> Result<Self> {
784 let mut user_header = UserHeader::default();
785
786 if block3.contains("{103:")
789 && let Some(start) = block3.find("{103:")
790 && let Some(end) = block3[start..].find('}')
791 {
792 user_header.service_identifier = Some(block3[start + 5..start + end].to_string());
793 }
794
795 if block3.contains("{113:")
796 && let Some(start) = block3.find("{113:")
797 && let Some(end) = block3[start..].find('}')
798 {
799 user_header.banking_priority = Some(block3[start + 5..start + end].to_string());
800 }
801
802 if block3.contains("{108:")
803 && let Some(start) = block3.find("{108:")
804 && let Some(end) = block3[start..].find('}')
805 {
806 user_header.message_user_reference = Some(block3[start + 5..start + end].to_string());
807 }
808
809 if block3.contains("{119:")
810 && let Some(start) = block3.find("{119:")
811 && let Some(end) = block3[start..].find('}')
812 {
813 user_header.validation_flag = Some(block3[start + 5..start + end].to_string());
814 }
815
816 if block3.contains("{423:")
817 && let Some(start) = block3.find("{423:")
818 && let Some(end) = block3[start..].find('}')
819 {
820 let value = &block3[start + 5..start + end];
821 user_header.balance_checkpoint = Self::parse_balance_checkpoint(value);
822 }
823
824 if block3.contains("{106:")
825 && let Some(start) = block3.find("{106:")
826 && let Some(end) = block3[start..].find('}')
827 {
828 let value = &block3[start + 5..start + end];
829 user_header.message_input_reference = Self::parse_message_input_reference(value);
830 }
831
832 if block3.contains("{424:")
833 && let Some(start) = block3.find("{424:")
834 && let Some(end) = block3[start..].find('}')
835 {
836 user_header.related_reference = Some(block3[start + 5..start + end].to_string());
837 }
838
839 if block3.contains("{111:")
840 && let Some(start) = block3.find("{111:")
841 && let Some(end) = block3[start..].find('}')
842 {
843 user_header.service_type_identifier = Some(block3[start + 5..start + end].to_string());
844 }
845
846 if block3.contains("{121:")
847 && let Some(start) = block3.find("{121:")
848 && let Some(end) = block3[start..].find('}')
849 {
850 user_header.unique_end_to_end_reference =
851 Some(block3[start + 5..start + end].to_string());
852 }
853
854 if block3.contains("{115:")
855 && let Some(start) = block3.find("{115:")
856 && let Some(end) = block3[start..].find('}')
857 {
858 user_header.addressee_information = Some(block3[start + 5..start + end].to_string());
859 }
860
861 if block3.contains("{165:")
862 && let Some(start) = block3.find("{165:")
863 && let Some(end) = block3[start..].find('}')
864 {
865 let value = &block3[start + 5..start + end];
866 user_header.payment_release_information = Self::parse_payment_release_info(value);
867 }
868
869 if block3.contains("{433:")
870 && let Some(start) = block3.find("{433:")
871 && let Some(end) = block3[start..].find('}')
872 {
873 let value = &block3[start + 5..start + end];
874 user_header.sanctions_screening_info = Self::parse_sanctions_screening_info(value);
875 }
876
877 if block3.contains("{434:")
878 && let Some(start) = block3.find("{434:")
879 && let Some(end) = block3[start..].find('}')
880 {
881 let value = &block3[start + 5..start + end];
882 user_header.payment_controls_info = Self::parse_payment_controls_info(value);
883 }
884
885 Ok(user_header)
886 }
887
888 fn parse_balance_checkpoint(value: &str) -> Option<BalanceCheckpoint> {
890 if value.len() >= 12 {
891 Some(BalanceCheckpoint {
892 date: value[0..6].to_string(),
893 time: value[6..12].to_string(),
894 hundredths_of_second: if value.len() > 12 {
895 Some(value[12..].to_string())
896 } else {
897 None
898 },
899 })
900 } else {
901 None
902 }
903 }
904
905 fn parse_message_input_reference(value: &str) -> Option<MessageInputReference> {
907 if value.len() >= 28 {
908 Some(MessageInputReference {
909 date: value[0..6].to_string(),
910 lt_identifier: value[6..18].to_string(),
911 branch_code: value[18..21].to_string(),
912 session_number: value[21..25].to_string(),
913 sequence_number: value[25..].to_string(),
914 })
915 } else {
916 None
917 }
918 }
919
920 fn parse_payment_release_info(value: &str) -> Option<PaymentReleaseInfo> {
922 if value.len() >= 3 {
923 let code = value[0..3].to_string();
924 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
925 Some(value[4..].to_string())
926 } else {
927 None
928 };
929 Some(PaymentReleaseInfo {
930 code,
931 additional_info,
932 })
933 } else {
934 None
935 }
936 }
937
938 fn parse_sanctions_screening_info(value: &str) -> Option<SanctionsScreeningInfo> {
940 if value.len() >= 3 {
941 let code_word = value[0..3].to_string();
942 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
943 Some(value[4..].to_string())
944 } else {
945 None
946 };
947 Some(SanctionsScreeningInfo {
948 code_word,
949 additional_info,
950 })
951 } else {
952 None
953 }
954 }
955
956 fn parse_payment_controls_info(value: &str) -> Option<PaymentControlsInfo> {
958 if value.len() >= 3 {
959 let code_word = value[0..3].to_string();
960 let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
961 Some(value[4..].to_string())
962 } else {
963 None
964 };
965 Some(PaymentControlsInfo {
966 code_word,
967 additional_info,
968 })
969 } else {
970 None
971 }
972 }
973}
974
975impl std::fmt::Display for UserHeader {
976 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
977 let mut result = String::new();
978
979 if let Some(ref service_id) = self.service_identifier {
980 result.push_str(&format!("{{103:{service_id}}}"));
981 }
982
983 if let Some(ref banking_priority) = self.banking_priority {
984 result.push_str(&format!("{{113:{banking_priority}}}"));
985 }
986
987 if let Some(ref message_user_ref) = self.message_user_reference {
988 result.push_str(&format!("{{108:{message_user_ref}}}"));
989 }
990
991 if let Some(ref validation_flag) = self.validation_flag {
992 result.push_str(&format!("{{119:{validation_flag}}}"));
993 }
994
995 if let Some(ref unique_end_to_end_ref) = self.unique_end_to_end_reference {
996 result.push_str(&format!("{{121:{unique_end_to_end_ref}}}"));
997 }
998
999 if let Some(ref service_type_identifier) = self.service_type_identifier {
1000 result.push_str(&format!("{{111:{service_type_identifier}}}"));
1001 }
1002
1003 if let Some(ref payment_controls) = self.payment_controls_info {
1004 let mut value = payment_controls.code_word.clone();
1005 if let Some(ref additional) = payment_controls.additional_info {
1006 value.push('/');
1007 value.push_str(additional);
1008 }
1009 result.push_str(&format!("{{434:{value}}}"));
1010 }
1011
1012 if let Some(ref payment_release) = self.payment_release_information {
1013 let mut value = payment_release.code.clone();
1014 if let Some(ref additional) = payment_release.additional_info {
1015 value.push('/');
1016 value.push_str(additional);
1017 }
1018 result.push_str(&format!("{{165:{value}}}"));
1019 }
1020
1021 if let Some(ref sanctions) = self.sanctions_screening_info {
1022 let mut value = sanctions.code_word.clone();
1023 if let Some(ref additional) = sanctions.additional_info {
1024 value.push('/');
1025 value.push_str(additional);
1026 }
1027 result.push_str(&format!("{{433:{value}}}"));
1028 }
1029
1030 write!(f, "{result}")
1031 }
1032}
1033
1034#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
1046#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1047pub struct Trailer {
1048 #[serde(skip_serializing_if = "Option::is_none")]
1050 pub checksum: Option<String>,
1051
1052 #[serde(skip_serializing_if = "Option::is_none")]
1054 pub test_and_training: Option<bool>,
1055
1056 #[serde(skip_serializing_if = "Option::is_none")]
1058 pub possible_duplicate_emission: Option<PossibleDuplicateEmission>,
1059
1060 #[serde(skip_serializing_if = "Option::is_none")]
1062 pub delayed_message: Option<bool>,
1063
1064 #[serde(skip_serializing_if = "Option::is_none")]
1066 pub message_reference: Option<MessageReference>,
1067
1068 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub possible_duplicate_message: Option<PossibleDuplicateMessage>,
1071
1072 #[serde(skip_serializing_if = "Option::is_none")]
1074 pub system_originated_message: Option<SystemOriginatedMessage>,
1075
1076 #[serde(skip_serializing_if = "Option::is_none")]
1078 pub mac: Option<String>,
1079}
1080
1081#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1083#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1084pub struct PossibleDuplicateEmission {
1085 #[serde(skip_serializing_if = "Option::is_none")]
1087 pub time: Option<String>,
1088 #[serde(skip_serializing_if = "Option::is_none")]
1090 pub message_input_reference: Option<MessageInputReference>,
1091}
1092
1093#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1095#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1096pub struct MessageReference {
1097 pub date: String,
1099 pub full_time: String,
1101 pub message_input_reference: MessageInputReference,
1103}
1104
1105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1107#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1108pub struct PossibleDuplicateMessage {
1109 #[serde(skip_serializing_if = "Option::is_none")]
1111 pub time: Option<String>,
1112 #[serde(skip_serializing_if = "Option::is_none")]
1114 pub message_output_reference: Option<MessageOutputReference>,
1115}
1116
1117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1119#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1120pub struct MessageOutputReference {
1121 pub date: String,
1123 pub lt_identifier: String,
1125 pub branch_code: String,
1127 pub session_number: String,
1129 pub sequence_number: String,
1131}
1132
1133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1135#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1136pub struct SystemOriginatedMessage {
1137 #[serde(skip_serializing_if = "Option::is_none")]
1139 pub time: Option<String>,
1140 #[serde(skip_serializing_if = "Option::is_none")]
1142 pub message_input_reference: Option<MessageInputReference>,
1143}
1144
1145impl Trailer {
1146 pub fn parse(block5: &str) -> Result<Self> {
1148 let mut trailer = Trailer::default();
1149
1150 if block5.contains("{CHK:")
1152 && let Some(start) = block5.find("{CHK:")
1153 && let Some(end) = block5[start..].find('}')
1154 {
1155 trailer.checksum = Some(block5[start + 5..start + end].to_string());
1156 }
1157
1158 if block5.contains("{TNG}") {
1159 trailer.test_and_training = Some(true);
1160 }
1161
1162 if block5.contains("{DLM}") {
1163 trailer.delayed_message = Some(true);
1164 }
1165
1166 if block5.contains("{MAC:")
1167 && let Some(start) = block5.find("{MAC:")
1168 && let Some(end) = block5[start..].find('}')
1169 {
1170 trailer.mac = Some(block5[start + 5..start + end].to_string());
1171 }
1172
1173 Ok(trailer)
1177 }
1178}
1179
1180impl std::fmt::Display for Trailer {
1181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1182 let mut result = String::new();
1183
1184 if let Some(ref checksum) = self.checksum {
1185 result.push_str(&format!("{{CHK:{checksum}}}"));
1186 }
1187
1188 if let Some(true) = self.test_and_training {
1189 result.push_str("{TNG}");
1190 }
1191
1192 if let Some(true) = self.delayed_message {
1193 result.push_str("{DLM}");
1194 }
1195
1196 if let Some(ref possible_duplicate_emission) = self.possible_duplicate_emission {
1197 result.push_str(&format!(
1198 "{{PDE:{}}}",
1199 possible_duplicate_emission.time.as_deref().unwrap_or("")
1200 ));
1201 }
1202
1203 if let Some(ref message_reference) = self.message_reference {
1204 result.push_str(&format!("{{MRF:{}}}", message_reference.date));
1205 }
1206
1207 if let Some(ref mac) = self.mac {
1208 result.push_str(&format!("{{MAC:{mac}}}"));
1209 }
1210
1211 write!(f, "{result}")
1212 }
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217 use super::*;
1218
1219 #[test]
1220 fn test_application_header_input_parsing() {
1221 let block2 = "I103DEUTDEFFAXXXN";
1223 let header = ApplicationHeader::parse(block2).unwrap();
1224
1225 match header {
1226 ApplicationHeader::Input(input) => {
1227 assert_eq!(input.message_type, "103");
1228 assert_eq!(input.destination_address, "DEUTDEFFAXXX");
1229 assert_eq!(input.receiver_bic, "DEUTDEFF");
1230 assert_eq!(input.priority, "N");
1231 assert_eq!(input.delivery_monitoring, None);
1232 assert_eq!(input.obsolescence_period, None);
1233 }
1234 ApplicationHeader::Output(_) => panic!("Expected Input header, got Output"),
1235 }
1236 }
1237
1238 #[test]
1239 fn test_application_header_input_parsing_with_monitoring() {
1240 let block2 = "I103DEUTDEFFAXXXU3003";
1242 let header = ApplicationHeader::parse(block2).unwrap();
1243
1244 match header {
1245 ApplicationHeader::Input(input) => {
1246 assert_eq!(input.message_type, "103");
1247 assert_eq!(input.destination_address, "DEUTDEFFAXXX");
1248 assert_eq!(input.receiver_bic, "DEUTDEFF");
1249 assert_eq!(input.priority, "U");
1250 assert_eq!(input.delivery_monitoring, Some("3".to_string()));
1251 assert_eq!(input.obsolescence_period, Some("003".to_string()));
1252 }
1253 ApplicationHeader::Output(_) => panic!("Expected Input header, got Output"),
1254 }
1255 }
1256
1257 #[test]
1258 fn test_application_header_output_parsing() {
1259 let block2 = "O1031535051028DEUTDEFFAXXX08264556280510281535N";
1261 let header = ApplicationHeader::parse(block2).unwrap();
1262
1263 match header {
1264 ApplicationHeader::Output(output) => {
1265 assert_eq!(output.message_type, "103");
1266 assert_eq!(output.input_time, "1535");
1267 assert_eq!(output.output_date, "051028");
1268 assert_eq!(output.output_time, "1535");
1269 assert_eq!(output.priority, Some("N".to_string()));
1270
1271 assert_eq!(output.mir.date, "051028");
1273 assert_eq!(output.mir.lt_identifier, "DEUTDEFFAXXX");
1274 assert_eq!(output.mir.branch_code, "XXX");
1275 assert_eq!(output.mir.session_number, "0826");
1276 assert_eq!(output.mir.sequence_number, "455628");
1277 }
1278 ApplicationHeader::Input(_) => panic!("Expected Output header, got Input"),
1279 }
1280 }
1281
1282 #[test]
1283 fn test_application_header_output_parsing_different_message_type() {
1284 let block2 = "O2021245051028CHASUS33AXXX08264556280510281245U";
1286 let header = ApplicationHeader::parse(block2).unwrap();
1287
1288 match header {
1289 ApplicationHeader::Output(output) => {
1290 assert_eq!(output.message_type, "202");
1291 assert_eq!(output.mir.lt_identifier, "CHASUS33AXXX");
1292 assert_eq!(output.priority, Some("U".to_string()));
1293 }
1294 ApplicationHeader::Input(_) => panic!("Expected Output header, got Input"),
1295 }
1296 }
1297
1298 #[test]
1299 fn test_application_header_invalid_direction() {
1300 let block2 = "X103DEUTDEFFAXXXN";
1301 let result = ApplicationHeader::parse(block2);
1302
1303 assert!(result.is_err());
1304 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1305 assert!(message.contains("Invalid direction indicator"));
1306 } else {
1307 panic!("Expected InvalidBlockStructure error");
1308 }
1309 }
1310
1311 #[test]
1312 fn test_application_header_input_too_short() {
1313 let block2 = "I103DEUTDEF"; let result = ApplicationHeader::parse(block2);
1315
1316 assert!(result.is_err());
1317 }
1318
1319 #[test]
1320 fn test_application_header_output_too_short() {
1321 let block2 = "O103153505102"; let result = ApplicationHeader::parse(block2);
1323
1324 assert!(result.is_err());
1325 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1326 assert!(message.contains("Output Block 2 too short: expected at least 46 characters"));
1328 } else {
1329 panic!("Expected InvalidBlockStructure error");
1330 }
1331 }
1332
1333 #[test]
1334 fn test_application_header_output_minimum_length_but_still_too_short() {
1335 let block2 = "O10315350510280DE"; let result = ApplicationHeader::parse(block2);
1338
1339 assert!(result.is_err());
1340 if let Err(ParseError::InvalidBlockStructure { message, .. }) = result {
1341 assert!(message.contains("Output Block 2 too short: expected at least 46 characters"));
1342 } else {
1343 panic!("Expected InvalidBlockStructure error");
1344 }
1345 }
1346
1347 #[test]
1348 fn test_basic_header_parsing() {
1349 let block1 = "F01DEUTDEFFAXXX0000123456";
1350 let header = BasicHeader::parse(block1).unwrap();
1351
1352 assert_eq!(header.application_id, "F");
1353 assert_eq!(header.service_id, "01");
1354 assert_eq!(header.logical_terminal, "DEUTDEFFAXXX");
1355 assert_eq!(header.sender_bic, "DEUTDEFF");
1356 assert_eq!(header.session_number, "0000");
1357 assert_eq!(header.sequence_number, "123456");
1358 }
1359
1360 #[test]
1361 fn test_application_header_input_display() {
1362 let header = ApplicationHeader::Input(InputApplicationHeader {
1363 message_type: "103".to_string(),
1364 destination_address: "DEUTDEFFAXXX".to_string(),
1365 receiver_bic: "DEUTDEFF".to_string(),
1366 priority: "U".to_string(),
1367 delivery_monitoring: Some("3".to_string()),
1368 obsolescence_period: Some("003".to_string()),
1369 });
1370
1371 assert_eq!(header.to_string(), "I103DEUTDEFFAXXXU3003");
1372 }
1373
1374 #[test]
1375 fn test_application_header_output_display() {
1376 let mir = MessageInputReference {
1377 date: "051028".to_string(),
1378 lt_identifier: "DEUTDEFFAXXX".to_string(),
1379 branch_code: "XXX".to_string(),
1380 session_number: "0826".to_string(),
1381 sequence_number: "455628".to_string(),
1382 };
1383
1384 let header = ApplicationHeader::Output(OutputApplicationHeader {
1385 message_type: "103".to_string(),
1386 input_time: "1535".to_string(),
1387 mir,
1388 output_date: "051028".to_string(),
1389 output_time: "1535".to_string(),
1390 priority: Some("N".to_string()),
1391 });
1392
1393 assert_eq!(
1394 header.to_string(),
1395 "O1031535051028DEUTDEFFAXXX08264556280510281535N"
1396 );
1397 }
1398
1399 #[test]
1400 fn test_application_header_helper_methods() {
1401 let input_header = ApplicationHeader::Input(InputApplicationHeader {
1402 message_type: "103".to_string(),
1403 destination_address: "DEUTDEFFAXXX".to_string(),
1404 receiver_bic: "DEUTDEFF".to_string(),
1405 priority: "U".to_string(),
1406 delivery_monitoring: None,
1407 obsolescence_period: None,
1408 });
1409
1410 assert_eq!(input_header.message_type(), "103");
1411 assert_eq!(input_header.priority(), Some("U"));
1412
1413 let mir = MessageInputReference {
1414 date: "051028".to_string(),
1415 lt_identifier: "DEUTDEFFAXXX".to_string(),
1416 branch_code: "XXX".to_string(),
1417 session_number: "0826".to_string(),
1418 sequence_number: "455628".to_string(),
1419 };
1420
1421 let output_header = ApplicationHeader::Output(OutputApplicationHeader {
1422 message_type: "202".to_string(),
1423 input_time: "1535".to_string(),
1424 mir,
1425 output_date: "051028".to_string(),
1426 output_time: "1535".to_string(),
1427 priority: Some("N".to_string()),
1428 });
1429
1430 assert_eq!(output_header.message_type(), "202");
1431 assert_eq!(output_header.priority(), Some("N"));
1432 }
1433}