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}