swift_mt_message/fields/common/
multiline_text_field.rs1use crate::{Result, SwiftField, ValidationError, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct GenericMultiLineTextField<const MAX_LINES: usize, const MAX_CHARS: usize> {
9 pub lines: Vec<String>,
11}
12
13impl<const MAX_LINES: usize, const MAX_CHARS: usize>
14 GenericMultiLineTextField<MAX_LINES, MAX_CHARS>
15{
16 fn remove_field_tag_prefix(value: &str) -> &str {
19 use std::sync::OnceLock;
21 static FIELD_TAG_REGEX: OnceLock<regex::Regex> = OnceLock::new();
22
23 let regex = FIELD_TAG_REGEX.get_or_init(|| {
24 regex::Regex::new(r"^:?([0-9]{1,3}[A-Z]{0,2}):").unwrap()
27 });
28
29 if let Some(captures) = regex.find(value) {
30 &value[captures.end()..]
31 } else {
32 value
33 }
34 }
35}
36
37impl<const MAX_LINES: usize, const MAX_CHARS: usize> SwiftField
38 for GenericMultiLineTextField<MAX_LINES, MAX_CHARS>
39{
40 fn parse(value: &str) -> Result<Self> {
41 let content = value.trim();
42
43 let content = Self::remove_field_tag_prefix(content);
45
46 let lines = content.lines().map(|line| line.to_string()).collect();
47 Ok(Self { lines })
48 }
49
50 fn to_swift_string(&self) -> String {
51 self.lines.join("\n")
52 }
53
54 fn validate(&self) -> ValidationResult {
55 let mut errors = Vec::new();
56 let warnings = Vec::new();
57
58 if self.lines.len() > MAX_LINES {
59 errors.push(ValidationError::ValueValidation {
60 field_tag: "multiline".to_string(),
61 message: format!("Too many lines: {} (max {})", self.lines.len(), MAX_LINES),
62 });
63 }
64
65 for (i, line) in self.lines.iter().enumerate() {
66 if line.len() > MAX_CHARS {
67 errors.push(ValidationError::ValueValidation {
68 field_tag: "multiline".to_string(),
69 message: format!(
70 "Line {} too long: {} chars (max {})",
71 i + 1,
72 line.len(),
73 MAX_CHARS
74 ),
75 });
76 }
77 }
78
79 ValidationResult {
80 is_valid: errors.is_empty(),
81 errors,
82 warnings,
83 }
84 }
85
86 fn format_spec() -> &'static str {
87 "lines"
88 }
89}
90
91pub type GenericMultiLine3x35 = GenericMultiLineTextField<3, 35>;
93pub type GenericMultiLine4x35 = GenericMultiLineTextField<4, 35>;
94pub type GenericMultiLine6x35 = GenericMultiLineTextField<6, 35>;
95pub type GenericMultiLine6x65 = GenericMultiLineTextField<6, 65>;
96pub type GenericMultiLine20x35 = GenericMultiLineTextField<20, 35>;
97pub type GenericMultiLine35x50 = GenericMultiLineTextField<35, 50>;
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_generic_field_tag_removal() {
105 let test_cases = vec![
107 (":50K:/1234567890", "/1234567890"),
108 ("50K:/1234567890", "/1234567890"),
109 (":20:FT123456789", "FT123456789"),
110 ("20:FT123456789", "FT123456789"),
111 (":32A:241231USD1000000,", "241231USD1000000,"),
112 ("32A:241231USD1000000,", "241231USD1000000,"),
113 (":71F:USD50,00", "USD50,00"),
114 ("71F:USD50,00", "USD50,00"),
115 (":13C:/123045+0/+0100/-0500", "/123045+0/+0100/-0500"),
116 ("13C:/123045+0/+0100/-0500", "/123045+0/+0100/-0500"),
117 ("plain_text", "plain_text"), ("", ""), (":", ":"), ("::", "::"), ];
123
124 for (input, expected) in test_cases {
125 let result = GenericMultiLineTextField::<4, 35>::remove_field_tag_prefix(input);
126 assert_eq!(result, expected, "Failed for input: '{input}'");
127 }
128
129 println!("✅ Generic field tag removal works for all patterns!");
130 }
131
132 #[test]
133 fn test_multiline_field_parsing() {
134 let input = "50K:/1234567890\nACME CORPORATION\n123 MAIN STREET\nNEW YORK NY 10001 US";
136
137 let result = GenericMultiLineTextField::<4, 35>::parse(input).unwrap();
138
139 assert_eq!(result.lines.len(), 4);
140 assert_eq!(result.lines[0], "/1234567890");
141 assert_eq!(result.lines[1], "ACME CORPORATION");
142 assert_eq!(result.lines[2], "123 MAIN STREET");
143 assert_eq!(result.lines[3], "NEW YORK NY 10001 US");
144
145 println!("✅ Multiline field parsing with field tag removal works correctly!");
146 }
147}