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 fn sample() -> Self {
91 let line_count = if MAX_LINES > 4 { 4 } else { MAX_LINES };
92 let lines = crate::sample::generate_name_and_address(line_count);
93 Self { lines }
94 }
95
96 fn sample_with_config(_config: &crate::sample::FieldConfig) -> Self {
97 Self::sample()
98 }
99}
100
101pub type GenericMultiLine3x35 = GenericMultiLineTextField<3, 35>;
103pub type GenericMultiLine4x35 = GenericMultiLineTextField<4, 35>;
104pub type GenericMultiLine6x35 = GenericMultiLineTextField<6, 35>;
105pub type GenericMultiLine6x65 = GenericMultiLineTextField<6, 65>;
106pub type GenericMultiLine20x35 = GenericMultiLineTextField<20, 35>;
107pub type GenericMultiLine35x50 = GenericMultiLineTextField<35, 50>;
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_generic_field_tag_removal() {
115 let test_cases = vec![
117 (":50K:/1234567890", "/1234567890"),
118 ("50K:/1234567890", "/1234567890"),
119 (":20:FT123456789", "FT123456789"),
120 ("20:FT123456789", "FT123456789"),
121 (":32A:241231USD1000000,", "241231USD1000000,"),
122 ("32A:241231USD1000000,", "241231USD1000000,"),
123 (":71F:USD50,00", "USD50,00"),
124 ("71F:USD50,00", "USD50,00"),
125 (":13C:/123045+0/+0100/-0500", "/123045+0/+0100/-0500"),
126 ("13C:/123045+0/+0100/-0500", "/123045+0/+0100/-0500"),
127 ("plain_text", "plain_text"), ("", ""), (":", ":"), ("::", "::"), ];
133
134 for (input, expected) in test_cases {
135 let result = GenericMultiLineTextField::<4, 35>::remove_field_tag_prefix(input);
136 assert_eq!(result, expected, "Failed for input: '{input}'");
137 }
138
139 println!("✅ Generic field tag removal works for all patterns!");
140 }
141
142 #[test]
143 fn test_multiline_field_parsing() {
144 let input = "50K:/1234567890\nACME CORPORATION\n123 MAIN STREET\nNEW YORK NY 10001 US";
146
147 let result = GenericMultiLineTextField::<4, 35>::parse(input).unwrap();
148
149 assert_eq!(result.lines.len(), 4);
150 assert_eq!(result.lines[0], "/1234567890");
151 assert_eq!(result.lines[1], "ACME CORPORATION");
152 assert_eq!(result.lines[2], "123 MAIN STREET");
153 assert_eq!(result.lines[3], "NEW YORK NY 10001 US");
154
155 println!("✅ Multiline field parsing with field tag removal works correctly!");
156 }
157}