swift_mt_message/headers/
mod.rs

1use crate::errors::{ParseError, Result};
2use crate::fields::common::BIC;
3use serde::{Deserialize, Serialize};
4use std::str::FromStr;
5
6/// Basic Header (Block 1) - Application and service identifier
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct BasicHeader {
9    /// Application identifier (F = FIN application)
10    pub application_id: String,
11
12    /// Service identifier (01 = FIN)
13    pub service_id: String,
14
15    /// Logical Terminal (LT) address (12 characters)
16    pub logical_terminal: String,
17    pub sender_bic: BIC,
18
19    /// Session number (4 digits)
20    pub session_number: String,
21
22    /// Sequence number (6 digits)
23    pub sequence_number: String,
24}
25
26impl BasicHeader {
27    /// Parse basic header from block 1 string
28    pub fn parse(block1: &str) -> Result<Self> {
29        if block1.len() < 21 {
30            return Err(ParseError::InvalidBlockStructure {
31                message: format!(
32                    "Block 1 too short: expected at least 21 characters, got {}",
33                    block1.len()
34                ),
35            });
36        }
37
38        let application_id = block1[0..1].to_string();
39        let service_id = block1[1..3].to_string();
40        let logical_terminal = block1[3..15].to_string();
41        let session_number = block1[15..19].to_string();
42        let sequence_number = block1[19..].to_string();
43
44        // Extract BIC from logical terminal (first 8 characters for standard BIC)
45        let bic_str = &logical_terminal[0..8];
46        let sender_bic = BIC::from_str(bic_str).map_err(|e| ParseError::InvalidBlockStructure {
47            message: format!(
48                "Failed to parse BIC from logical terminal '{}': {}",
49                bic_str, e
50            ),
51        })?;
52
53        Ok(BasicHeader {
54            application_id,
55            service_id,
56            logical_terminal,
57            sender_bic,
58            session_number,
59            sequence_number,
60        })
61    }
62}
63
64impl std::fmt::Display for BasicHeader {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(
67            f,
68            "{}{}{}{}{}",
69            self.application_id,
70            self.service_id,
71            self.logical_terminal,
72            self.session_number,
73            self.sequence_number
74        )
75    }
76}
77
78/// Application Header (Block 2) - Message information
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80pub struct ApplicationHeader {
81    /// Direction (I = Input, O = Output)
82    pub direction: String,
83
84    /// Message type (e.g., "103", "202")
85    pub message_type: String,
86
87    /// Destination address (12 characters)
88    pub destination_address: String,
89    pub receiver_bic: BIC,
90
91    /// Priority (U = Urgent, N = Normal, S = System)
92    pub priority: String,
93
94    /// Delivery monitoring (1 = Non-delivery notification, 3 = Delivery notification)
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub delivery_monitoring: Option<String>,
97
98    /// Obsolescence period (3 digits, only for certain message types)
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub obsolescence_period: Option<String>,
101}
102
103impl ApplicationHeader {
104    /// Parse application header from block 2 string
105    pub fn parse(block2: &str) -> Result<Self> {
106        if block2.len() < 17 {
107            return Err(ParseError::InvalidBlockStructure {
108                message: format!(
109                    "Block 2 too short: expected at least 18 characters, got {}",
110                    block2.len()
111                ),
112            });
113        }
114
115        let direction = block2[0..1].to_string();
116        let message_type = block2[1..4].to_string();
117        let destination_address = block2[4..16].to_string();
118        let priority = block2[16..17].to_string();
119
120        let delivery_monitoring = if block2.len() >= 18 {
121            Some(block2[17..18].to_string())
122        } else {
123            None
124        };
125
126        let obsolescence_period = if block2.len() >= 21 {
127            Some(block2[18..21].to_string())
128        } else {
129            None
130        };
131
132        let receiver_bic = BIC::from_str(&destination_address[0..8]).map_err(|e| {
133            ParseError::InvalidBlockStructure {
134                message: format!(
135                    "Failed to parse BIC from destination address '{}': {}",
136                    destination_address, e
137                ),
138            }
139        })?;
140
141        Ok(ApplicationHeader {
142            direction,
143            message_type,
144            destination_address,
145            receiver_bic,
146            priority,
147            delivery_monitoring,
148            obsolescence_period,
149        })
150    }
151}
152
153impl std::fmt::Display for ApplicationHeader {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        let mut result = format!(
156            "{}{}{}{}",
157            self.direction, self.message_type, self.destination_address, self.priority
158        );
159
160        if let Some(ref delivery_monitoring) = self.delivery_monitoring {
161            result.push_str(delivery_monitoring);
162        }
163
164        if let Some(ref obsolescence_period) = self.obsolescence_period {
165            result.push_str(obsolescence_period);
166        }
167
168        write!(f, "{}", result)
169    }
170}
171
172/// User Header (Block 3) structure based on SWIFT MT standards
173#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
174pub struct UserHeader {
175    /// Tag 103 - Service Identifier (3!a) - Mandatory for FINcopy Service
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub service_identifier: Option<String>,
178
179    /// Tag 113 - Banking Priority (4!x) - Optional
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub banking_priority: Option<String>,
182
183    /// Tag 108 - Message User Reference (16!x) - Optional
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub message_user_reference: Option<String>,
186
187    /// Tag 119 - Validation Flag (8c) - Optional (STP, REMIT, RFDD, COV)
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub validation_flag: Option<String>,
190
191    /// Tag 423 - Balance checkpoint date and time (YYMMDDHHMMSS[ss]) - Optional (MIRS only)
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub balance_checkpoint: Option<BalanceCheckpoint>,
194
195    /// Tag 106 - Message Input Reference MIR (28c) - Optional (MIRS only)
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub message_input_reference: Option<MessageInputReference>,
198
199    /// Tag 424 - Related reference (16x) - Optional (MIRS only)
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub related_reference: Option<String>,
202
203    /// Tag 111 - Service type identifier (3!n) - Optional
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub service_type_identifier: Option<String>,
206
207    /// Tag 121 - Unique end-to-end transaction reference (UUID format) - Mandatory for GPI
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub unique_end_to_end_reference: Option<String>,
210
211    /// Tag 115 - Addressee Information (32x) - Optional (FINCopy only)
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub addressee_information: Option<String>,
214
215    /// Tag 165 - Payment release information receiver (3!c/34x) - Optional (FINInform only)
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub payment_release_information: Option<PaymentReleaseInfo>,
218
219    /// Tag 433 - Sanctions screening information (3!a/[20x]) - Optional
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub sanctions_screening_info: Option<SanctionsScreeningInfo>,
222
223    /// Tag 434 - Payment controls information (3!a/[20x]) - Optional
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub payment_controls_info: Option<PaymentControlsInfo>,
226}
227
228/// Balance checkpoint structure for Tag 423
229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub struct BalanceCheckpoint {
231    pub date: String, // YYMMDD
232    pub time: String, // HHMMSS
233
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub hundredths_of_second: Option<String>, // ss (optional)
236}
237
238/// Message Input Reference structure for Tag 106
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
240pub struct MessageInputReference {
241    pub date: String,            // YYMMDD
242    pub lt_identifier: String,   // 12 characters
243    pub branch_code: String,     // 3!c
244    pub session_number: String,  // 4!n
245    pub sequence_number: String, // 6!n
246}
247
248/// Payment release information structure for Tag 165
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
250pub struct PaymentReleaseInfo {
251    pub code: String, // 3!c
252
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub additional_info: Option<String>, // 34x (optional)
255}
256
257/// Sanctions screening information structure for Tag 433
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
259pub struct SanctionsScreeningInfo {
260    pub code_word: String, // 3!a (AOK, FPO, NOK)
261
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub additional_info: Option<String>, // 20x (optional)
264}
265
266/// Payment controls information structure for Tag 434
267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
268pub struct PaymentControlsInfo {
269    pub code_word: String, // 3!a
270
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub additional_info: Option<String>, // 20x (optional)
273}
274
275impl UserHeader {
276    /// Parse user header from block 3 string using structured parsing
277    pub fn parse(block3: &str) -> Result<Self> {
278        let mut user_header = UserHeader::default();
279
280        // Parse nested tags in format {tag:value}
281        // Simple parsing for now - more sophisticated regex parsing can be added later
282        if block3.contains("{103:") {
283            if let Some(start) = block3.find("{103:") {
284                if let Some(end) = block3[start..].find('}') {
285                    user_header.service_identifier =
286                        Some(block3[start + 5..start + end].to_string());
287                }
288            }
289        }
290
291        if block3.contains("{113:") {
292            if let Some(start) = block3.find("{113:") {
293                if let Some(end) = block3[start..].find('}') {
294                    user_header.banking_priority = Some(block3[start + 5..start + end].to_string());
295                }
296            }
297        }
298
299        if block3.contains("{108:") {
300            if let Some(start) = block3.find("{108:") {
301                if let Some(end) = block3[start..].find('}') {
302                    user_header.message_user_reference =
303                        Some(block3[start + 5..start + end].to_string());
304                }
305            }
306        }
307
308        if block3.contains("{119:") {
309            if let Some(start) = block3.find("{119:") {
310                if let Some(end) = block3[start..].find('}') {
311                    user_header.validation_flag = Some(block3[start + 5..start + end].to_string());
312                }
313            }
314        }
315
316        if block3.contains("{423:") {
317            if let Some(start) = block3.find("{423:") {
318                if let Some(end) = block3[start..].find('}') {
319                    let value = &block3[start + 5..start + end];
320                    user_header.balance_checkpoint = Self::parse_balance_checkpoint(value);
321                }
322            }
323        }
324
325        if block3.contains("{106:") {
326            if let Some(start) = block3.find("{106:") {
327                if let Some(end) = block3[start..].find('}') {
328                    let value = &block3[start + 5..start + end];
329                    user_header.message_input_reference =
330                        Self::parse_message_input_reference(value);
331                }
332            }
333        }
334
335        if block3.contains("{424:") {
336            if let Some(start) = block3.find("{424:") {
337                if let Some(end) = block3[start..].find('}') {
338                    user_header.related_reference =
339                        Some(block3[start + 5..start + end].to_string());
340                }
341            }
342        }
343
344        if block3.contains("{111:") {
345            if let Some(start) = block3.find("{111:") {
346                if let Some(end) = block3[start..].find('}') {
347                    user_header.service_type_identifier =
348                        Some(block3[start + 5..start + end].to_string());
349                }
350            }
351        }
352
353        if block3.contains("{121:") {
354            if let Some(start) = block3.find("{121:") {
355                if let Some(end) = block3[start..].find('}') {
356                    user_header.unique_end_to_end_reference =
357                        Some(block3[start + 5..start + end].to_string());
358                }
359            }
360        }
361
362        if block3.contains("{115:") {
363            if let Some(start) = block3.find("{115:") {
364                if let Some(end) = block3[start..].find('}') {
365                    user_header.addressee_information =
366                        Some(block3[start + 5..start + end].to_string());
367                }
368            }
369        }
370
371        if block3.contains("{165:") {
372            if let Some(start) = block3.find("{165:") {
373                if let Some(end) = block3[start..].find('}') {
374                    let value = &block3[start + 5..start + end];
375                    user_header.payment_release_information =
376                        Self::parse_payment_release_info(value);
377                }
378            }
379        }
380
381        if block3.contains("{433:") {
382            if let Some(start) = block3.find("{433:") {
383                if let Some(end) = block3[start..].find('}') {
384                    let value = &block3[start + 5..start + end];
385                    user_header.sanctions_screening_info =
386                        Self::parse_sanctions_screening_info(value);
387                }
388            }
389        }
390
391        if block3.contains("{434:") {
392            if let Some(start) = block3.find("{434:") {
393                if let Some(end) = block3[start..].find('}') {
394                    let value = &block3[start + 5..start + end];
395                    user_header.payment_controls_info = Self::parse_payment_controls_info(value);
396                }
397            }
398        }
399
400        Ok(user_header)
401    }
402
403    /// Parse balance checkpoint from tag value
404    fn parse_balance_checkpoint(value: &str) -> Option<BalanceCheckpoint> {
405        if value.len() >= 12 {
406            Some(BalanceCheckpoint {
407                date: value[0..6].to_string(),
408                time: value[6..12].to_string(),
409                hundredths_of_second: if value.len() > 12 {
410                    Some(value[12..].to_string())
411                } else {
412                    None
413                },
414            })
415        } else {
416            None
417        }
418    }
419
420    /// Parse message input reference from tag value
421    fn parse_message_input_reference(value: &str) -> Option<MessageInputReference> {
422        if value.len() >= 28 {
423            Some(MessageInputReference {
424                date: value[0..6].to_string(),
425                lt_identifier: value[6..18].to_string(),
426                branch_code: value[18..21].to_string(),
427                session_number: value[21..25].to_string(),
428                sequence_number: value[25..].to_string(),
429            })
430        } else {
431            None
432        }
433    }
434
435    /// Parse payment release info from tag value
436    fn parse_payment_release_info(value: &str) -> Option<PaymentReleaseInfo> {
437        if value.len() >= 3 {
438            let code = value[0..3].to_string();
439            let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
440                Some(value[4..].to_string())
441            } else {
442                None
443            };
444            Some(PaymentReleaseInfo {
445                code,
446                additional_info,
447            })
448        } else {
449            None
450        }
451    }
452
453    /// Parse sanctions screening info from tag value
454    fn parse_sanctions_screening_info(value: &str) -> Option<SanctionsScreeningInfo> {
455        if value.len() >= 3 {
456            let code_word = value[0..3].to_string();
457            let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
458                Some(value[4..].to_string())
459            } else {
460                None
461            };
462            Some(SanctionsScreeningInfo {
463                code_word,
464                additional_info,
465            })
466        } else {
467            None
468        }
469    }
470
471    /// Parse payment controls info from tag value
472    fn parse_payment_controls_info(value: &str) -> Option<PaymentControlsInfo> {
473        if value.len() >= 3 {
474            let code_word = value[0..3].to_string();
475            let additional_info = if value.len() > 4 && value.chars().nth(3) == Some('/') {
476                Some(value[4..].to_string())
477            } else {
478                None
479            };
480            Some(PaymentControlsInfo {
481                code_word,
482                additional_info,
483            })
484        } else {
485            None
486        }
487    }
488}
489
490impl std::fmt::Display for UserHeader {
491    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492        let mut result = String::new();
493
494        if let Some(ref service_id) = self.service_identifier {
495            result.push_str(&format!("{{103:{}}}", service_id));
496        }
497
498        if let Some(ref banking_priority) = self.banking_priority {
499            result.push_str(&format!("{{113:{}}}", banking_priority));
500        }
501
502        if let Some(ref message_user_ref) = self.message_user_reference {
503            result.push_str(&format!("{{108:{}}}", message_user_ref));
504        }
505
506        if let Some(ref validation_flag) = self.validation_flag {
507            result.push_str(&format!("{{119:{}}}", validation_flag));
508        }
509
510        if let Some(ref unique_end_to_end_ref) = self.unique_end_to_end_reference {
511            result.push_str(&format!("{{121:{}}}", unique_end_to_end_ref));
512        }
513
514        if let Some(ref service_type_id) = self.service_type_identifier {
515            result.push_str(&format!("{{111:{}}}", service_type_id));
516        }
517
518        if let Some(ref payment_controls) = self.payment_controls_info {
519            let mut value = payment_controls.code_word.clone();
520            if let Some(ref additional) = payment_controls.additional_info {
521                value.push('/');
522                value.push_str(additional);
523            }
524            result.push_str(&format!("{{434:{}}}", value));
525        }
526
527        if let Some(ref payment_release) = self.payment_release_information {
528            let mut value = payment_release.code.clone();
529            if let Some(ref additional) = payment_release.additional_info {
530                value.push('/');
531                value.push_str(additional);
532            }
533            result.push_str(&format!("{{165:{}}}", value));
534        }
535
536        if let Some(ref sanctions) = self.sanctions_screening_info {
537            let mut value = sanctions.code_word.clone();
538            if let Some(ref additional) = sanctions.additional_info {
539                value.push('/');
540                value.push_str(additional);
541            }
542            result.push_str(&format!("{{433:{}}}", value));
543        }
544
545        write!(f, "{}", result)
546    }
547}
548
549/// Trailer (Block 5) structure based on SWIFT MT standards
550#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
551pub struct Trailer {
552    /// CHK - Checksum (12!h) - Mandatory
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub checksum: Option<String>,
555
556    /// TNG - Test & Training Message - Optional (empty tag)
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub test_and_training: Option<bool>,
559
560    /// PDE - Possible Duplicate Emission - Optional
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub possible_duplicate_emission: Option<PossibleDuplicateEmission>,
563
564    /// DLM - Delayed Message - Optional (empty tag)
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub delayed_message: Option<bool>,
567
568    /// MRF - Message Reference - Optional
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub message_reference: Option<MessageReference>,
571
572    /// PDM - Possible Duplicate Message - Optional
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub possible_duplicate_message: Option<PossibleDuplicateMessage>,
575
576    /// SYS - System Originated Message - Optional
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub system_originated_message: Option<SystemOriginatedMessage>,
579
580    /// MAC - Message Authentication Code - Optional
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub mac: Option<String>,
583}
584
585/// Possible Duplicate Emission structure for PDE tag
586#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
587pub struct PossibleDuplicateEmission {
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub time: Option<String>, // HHMM (optional)
590
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub message_input_reference: Option<MessageInputReference>, // MIR (optional)
593}
594
595/// Message Reference structure for MRF tag
596#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
597pub struct MessageReference {
598    pub date: String,                                   // YYMMDD
599    pub full_time: String,                              // HHMM
600    pub message_input_reference: MessageInputReference, // MIR
601}
602
603/// Possible Duplicate Message structure for PDM tag
604#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
605pub struct PossibleDuplicateMessage {
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub time: Option<String>, // HHMM (optional)
608
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub message_output_reference: Option<MessageOutputReference>, // MOR (optional)
611}
612
613/// Message Output Reference structure (similar to MIR but for output)
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
615pub struct MessageOutputReference {
616    pub date: String,            // YYMMDD
617    pub lt_identifier: String,   // 12 characters
618    pub branch_code: String,     // 3!c
619    pub session_number: String,  // 4!n
620    pub sequence_number: String, // 6!n
621}
622
623/// System Originated Message structure for SYS tag
624#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
625pub struct SystemOriginatedMessage {
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub time: Option<String>, // HHMM (optional)
628
629    #[serde(skip_serializing_if = "Option::is_none")]
630    pub message_input_reference: Option<MessageInputReference>, // MIR (optional)
631}
632
633impl Trailer {
634    /// Parse trailer from block 5 string using structured parsing
635    pub fn parse(block5: &str) -> Result<Self> {
636        let mut trailer = Trailer::default();
637
638        // Extract common tags if present
639        if block5.contains("{CHK:") {
640            if let Some(start) = block5.find("{CHK:") {
641                if let Some(end) = block5[start..].find('}') {
642                    trailer.checksum = Some(block5[start + 5..start + end].to_string());
643                }
644            }
645        }
646
647        if block5.contains("{TNG}") {
648            trailer.test_and_training = Some(true);
649        }
650
651        if block5.contains("{DLM}") {
652            trailer.delayed_message = Some(true);
653        }
654
655        if block5.contains("{MAC:") {
656            if let Some(start) = block5.find("{MAC:") {
657                if let Some(end) = block5[start..].find('}') {
658                    trailer.mac = Some(block5[start + 5..start + end].to_string());
659                }
660            }
661        }
662
663        // More complex parsing for structured tags can be added here
664        // For now, implementing basic tag extraction
665
666        Ok(trailer)
667    }
668}
669
670impl std::fmt::Display for Trailer {
671    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
672        let mut result = String::new();
673
674        if let Some(ref checksum) = self.checksum {
675            result.push_str(&format!("{{CHK:{}}}", checksum));
676        }
677
678        if let Some(true) = self.test_and_training {
679            result.push_str("{TNG}");
680        }
681
682        if let Some(true) = self.delayed_message {
683            result.push_str("{DLM}");
684        }
685
686        if let Some(ref possible_duplicate_emission) = self.possible_duplicate_emission {
687            result.push_str(&format!(
688                "{{PDE:{}}}",
689                possible_duplicate_emission.time.as_deref().unwrap_or("")
690            ));
691        }
692
693        if let Some(ref message_reference) = self.message_reference {
694            result.push_str(&format!("{{MRF:{}}}", message_reference.date));
695        }
696
697        if let Some(ref mac) = self.mac {
698            result.push_str(&format!("{{MAC:{}}}", mac));
699        }
700
701        write!(f, "{}", result)
702    }
703}
704
705#[cfg(test)]
706mod tests {
707    use super::*;
708
709    #[test]
710    fn test_basic_header_parse() {
711        let block1 = "F01BANKDEFFAXXX0123456789";
712        let header = BasicHeader::parse(block1).unwrap();
713
714        assert_eq!(header.application_id, "F");
715        assert_eq!(header.service_id, "01");
716        assert_eq!(header.logical_terminal, "BANKDEFFAXXX");
717        assert_eq!(header.session_number, "0123");
718        assert_eq!(header.sequence_number, "456789");
719    }
720
721    #[test]
722    fn test_application_header_parse() {
723        let block2 = "I103BANKDEFFAXXXU3003";
724        let header = ApplicationHeader::parse(block2).unwrap();
725
726        assert_eq!(header.direction, "I");
727        assert_eq!(header.message_type, "103");
728        assert_eq!(header.destination_address, "BANKDEFFAXXX");
729        assert_eq!(header.priority, "U");
730        assert_eq!(header.delivery_monitoring, Some("3".to_string()));
731        assert_eq!(header.obsolescence_period, Some("003".to_string()));
732    }
733}