swift_mt_message/parser/
utils.rs

1use super::MessageParser;
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4
5/// Extract Block 4 content from SWIFT message input.
6/// If input starts with "{", attempts to extract Block 4.
7/// Otherwise, assumes input is already Block 4 content.
8pub fn extract_block4(input: &str) -> Result<String, ParseError> {
9    if input.starts_with("{") {
10        super::SwiftParser::extract_block(input, 4)?.ok_or_else(|| ParseError::InvalidFormat {
11            message: "Block 4 not found".to_string(),
12        })
13    } else {
14        Ok(input.to_string())
15    }
16}
17
18/// Append a mandatory field to the result string with CRLF.
19pub fn append_field<T: SwiftField>(result: &mut String, field: &T) {
20    result.push_str(&field.to_swift_string());
21    result.push_str("\r\n");
22}
23
24/// Append an optional field to the result string with CRLF if present.
25pub fn append_optional_field<T: SwiftField>(result: &mut String, field: &Option<T>) {
26    if let Some(f) = field {
27        result.push_str(&f.to_swift_string());
28        result.push_str("\r\n");
29    }
30}
31
32/// Append a vector of fields to the result string, each with CRLF.
33pub fn append_vec_field<T: SwiftField>(result: &mut String, fields: &Option<Vec<T>>) {
34    if let Some(vec) = fields {
35        for field in vec {
36            result.push_str(&field.to_swift_string());
37            result.push_str("\r\n");
38        }
39    }
40}
41
42/// Parse repeated fields and return as Option<Vec<T>>.
43/// Returns None if no fields found, Some(vec) otherwise.
44pub fn parse_repeated_field<T: crate::traits::SwiftField>(
45    parser: &mut MessageParser,
46    tag: &str,
47) -> Result<Option<Vec<T>>, ParseError> {
48    let mut fields = Vec::new();
49    while let Ok(field) = parser.parse_field::<T>(tag) {
50        fields.push(field);
51    }
52    Ok(if fields.is_empty() {
53        None
54    } else {
55        Some(fields)
56    })
57}
58
59/// Verify that all content in the parser has been consumed.
60/// Returns error if unparsed content remains.
61pub fn verify_parser_complete(parser: &MessageParser) -> Result<(), ParseError> {
62    if !parser.is_complete() {
63        return Err(ParseError::InvalidFormat {
64            message: format!(
65                "Unparsed content remaining in message: {}",
66                parser.remaining()
67            ),
68        });
69    }
70    Ok(())
71}
72
73/// Remove trailing CRLF from result string if present.
74pub fn remove_trailing_crlf(result: &mut String) {
75    if result.ends_with("\r\n") {
76        result.truncate(result.len() - 2);
77    }
78}
79
80/// Finalize MT string by removing trailing CRLF and optionally adding terminator.
81pub fn finalize_mt_string(mut result: String, add_terminator: bool) -> String {
82    remove_trailing_crlf(&mut result);
83    if add_terminator {
84        result.push('-');
85    }
86    result
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_extract_block4_with_blocks() {
95        let input = "{1:F01BANKFRPPAXXX0000000000}{4:\r\n:20:TEST123\r\n-}";
96        let result = extract_block4(input);
97        assert!(result.is_ok());
98        assert!(result.unwrap().contains(":20:TEST123"));
99    }
100
101    #[test]
102    fn test_extract_block4_plain() {
103        let input = ":20:TEST123\r\n";
104        let result = extract_block4(input);
105        assert!(result.is_ok());
106        assert_eq!(result.unwrap(), input);
107    }
108
109    #[test]
110    fn test_extract_block4_missing() {
111        let input = "{1:F01BANKFRPPAXXX0000000000}";
112        let result = extract_block4(input);
113        assert!(result.is_err());
114    }
115
116    #[test]
117    fn test_remove_trailing_crlf() {
118        let mut s = String::from("test\r\n");
119        remove_trailing_crlf(&mut s);
120        assert_eq!(s, "test");
121
122        let mut s2 = String::from("test");
123        remove_trailing_crlf(&mut s2);
124        assert_eq!(s2, "test");
125    }
126
127    #[test]
128    fn test_finalize_mt_string() {
129        let result = finalize_mt_string(String::from("test\r\n"), true);
130        assert_eq!(result, "test-");
131
132        let result2 = finalize_mt_string(String::from("test\r\n"), false);
133        assert_eq!(result2, "test");
134    }
135}