swift_mt_message/fields/
field56.rs

1use super::field_utils::{parse_name_and_address, parse_party_identifier};
2use super::swift_utils::{parse_bic, parse_swift_chars};
3use crate::errors::ParseError;
4use crate::traits::SwiftField;
5use serde::{Deserialize, Serialize};
6
7/// **Field 56A: Intermediary (BIC with Party Identifier)**
8///
9/// Specifies intermediary bank for routing to beneficiary's bank.
10///
11/// **Format:** [/1!a][/34x] + BIC (8 or 11 chars)
12/// **Payment Method Codes:** //FW (Fedwire), //RT (RTGS), //AU, //IN
13///
14/// **Example:**
15/// ```text
16/// :56A://FW021000018
17/// CHASUS33XXX
18/// ```
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct Field56A {
21    /// Optional party identifier (max 34 chars, may include //FW, //RT, //AU, //IN codes)
22    pub party_identifier: Option<String>,
23
24    /// BIC code (8 or 11 chars)
25    pub bic: String,
26}
27
28/// **Field 56C: Intermediary (Party Identifier Only)**
29///
30/// Simplified intermediary reference with party identifier only.
31/// Format: /34x (mandatory slash prefix)
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct Field56C {
34    /// Party identifier (1-34 chars, domestic routing codes)
35    pub party_identifier: String,
36}
37
38/// **Field 56D: Intermediary (Party Identifier with Name and Address)**
39///
40/// Detailed intermediary identification with name and address.
41/// Format: [/1!a][/34x] + 4*35x
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct Field56D {
44    /// Optional party identifier (max 34 chars, routing codes)
45    pub party_identifier: Option<String>,
46
47    /// Name and address (max 4 lines, 35 chars each)
48    pub name_and_address: Vec<String>,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub enum Field56Intermediary {
53    #[serde(rename = "56A")]
54    A(Field56A),
55    #[serde(rename = "56C")]
56    C(Field56C),
57    #[serde(rename = "56D")]
58    D(Field56D),
59}
60
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
62pub enum Field56IntermediaryAD {
63    #[serde(rename = "56A")]
64    A(Field56A),
65    #[serde(rename = "56D")]
66    D(Field56D),
67}
68
69impl SwiftField for Field56A {
70    fn parse(input: &str) -> crate::Result<Self>
71    where
72        Self: Sized,
73    {
74        let lines: Vec<&str> = input.lines().collect();
75
76        if lines.is_empty() {
77            return Err(ParseError::InvalidFormat {
78                message: "Field 56A cannot be empty".to_string(),
79            });
80        }
81
82        let mut party_identifier = None;
83        let mut bic_line_idx = 0;
84
85        // Check for optional party identifier on first line
86        if let Some(party_id) = parse_party_identifier(lines[0])? {
87            party_identifier = Some(party_id);
88            bic_line_idx = 1;
89        }
90
91        // Ensure we have a BIC line
92        if bic_line_idx >= lines.len() {
93            return Err(ParseError::InvalidFormat {
94                message: "Field 56A missing BIC code".to_string(),
95            });
96        }
97
98        let bic = parse_bic(lines[bic_line_idx])?;
99
100        Ok(Field56A {
101            party_identifier,
102            bic,
103        })
104    }
105
106    fn to_swift_string(&self) -> String {
107        let mut result = Vec::new();
108
109        if let Some(ref id) = self.party_identifier {
110            result.push(format!("/{}", id));
111        }
112
113        result.push(self.bic.clone());
114        format!(":56A:{}", result.join("\n"))
115    }
116}
117
118impl SwiftField for Field56C {
119    fn parse(input: &str) -> crate::Result<Self>
120    where
121        Self: Sized,
122    {
123        if !input.starts_with('/') {
124            return Err(ParseError::InvalidFormat {
125                message: "Field 56C must start with '/'".to_string(),
126            });
127        }
128
129        let identifier = &input[1..];
130
131        if identifier.is_empty() || identifier.len() > 34 {
132            return Err(ParseError::InvalidFormat {
133                message: "Field 56C party identifier must be 1-34 characters".to_string(),
134            });
135        }
136
137        parse_swift_chars(identifier, "Field 56C party identifier")?;
138
139        Ok(Field56C {
140            party_identifier: identifier.to_string(),
141        })
142    }
143
144    fn to_swift_string(&self) -> String {
145        format!(":56C:/{}", self.party_identifier)
146    }
147}
148
149impl SwiftField for Field56D {
150    fn parse(input: &str) -> crate::Result<Self>
151    where
152        Self: Sized,
153    {
154        let lines: Vec<&str> = input.lines().collect();
155
156        if lines.is_empty() {
157            return Err(ParseError::InvalidFormat {
158                message: "Field 56D must have at least one line".to_string(),
159            });
160        }
161
162        let mut party_identifier = None;
163        let mut start_idx = 0;
164
165        // Check for party identifier on first line
166        if let Some(party_id) = parse_party_identifier(lines[0])? {
167            party_identifier = Some(party_id);
168            start_idx = 1;
169        }
170
171        // Parse remaining lines as name and address
172        let name_and_address = parse_name_and_address(&lines, start_idx, "Field56D")?;
173
174        Ok(Field56D {
175            party_identifier,
176            name_and_address,
177        })
178    }
179
180    fn to_swift_string(&self) -> String {
181        let mut result = Vec::new();
182
183        if let Some(ref id) = self.party_identifier {
184            result.push(format!("/{}", id));
185        }
186
187        for line in &self.name_and_address {
188            result.push(line.clone());
189        }
190
191        format!(":56D:{}", result.join("\n"))
192    }
193}
194
195impl SwiftField for Field56Intermediary {
196    fn parse(input: &str) -> crate::Result<Self>
197    where
198        Self: Sized,
199    {
200        // Try Option A (BIC-based) first
201        if let Ok(field) = Field56A::parse(input) {
202            return Ok(Field56Intermediary::A(field));
203        }
204
205        // Try Option C (party identifier only) - must be single line with /
206        if input.starts_with('/')
207            && !input.contains('\n')
208            && let Ok(field) = Field56C::parse(input)
209        {
210            return Ok(Field56Intermediary::C(field));
211        }
212
213        // Try Option D (party identifier with name/address)
214        if let Ok(field) = Field56D::parse(input) {
215            return Ok(Field56Intermediary::D(field));
216        }
217
218        Err(ParseError::InvalidFormat {
219            message: "Field 56 Intermediary could not be parsed as option A, C or D".to_string(),
220        })
221    }
222
223    fn parse_with_variant(
224        value: &str,
225        variant: Option<&str>,
226        _field_tag: Option<&str>,
227    ) -> crate::Result<Self>
228    where
229        Self: Sized,
230    {
231        match variant {
232            Some("A") => {
233                let field = Field56A::parse(value)?;
234                Ok(Field56Intermediary::A(field))
235            }
236            Some("C") => {
237                let field = Field56C::parse(value)?;
238                Ok(Field56Intermediary::C(field))
239            }
240            Some("D") => {
241                let field = Field56D::parse(value)?;
242                Ok(Field56Intermediary::D(field))
243            }
244            _ => {
245                // No variant specified, fall back to default parse behavior
246                Self::parse(value)
247            }
248        }
249    }
250
251    fn to_swift_string(&self) -> String {
252        match self {
253            Field56Intermediary::A(field) => field.to_swift_string(),
254            Field56Intermediary::C(field) => field.to_swift_string(),
255            Field56Intermediary::D(field) => field.to_swift_string(),
256        }
257    }
258
259    fn get_variant_tag(&self) -> Option<&'static str> {
260        match self {
261            Field56Intermediary::A(_) => Some("A"),
262            Field56Intermediary::C(_) => Some("C"),
263            Field56Intermediary::D(_) => Some("D"),
264        }
265    }
266}
267
268impl SwiftField for Field56IntermediaryAD {
269    fn parse(input: &str) -> crate::Result<Self>
270    where
271        Self: Sized,
272    {
273        // Try Option A (BIC-based) first
274        if let Ok(field) = Field56A::parse(input) {
275            return Ok(Field56IntermediaryAD::A(field));
276        }
277
278        // Try Option D (party identifier with name/address)
279        if let Ok(field) = Field56D::parse(input) {
280            return Ok(Field56IntermediaryAD::D(field));
281        }
282
283        Err(ParseError::InvalidFormat {
284            message: "Field 56 Intermediary AD could not be parsed as option A or D".to_string(),
285        })
286    }
287
288    fn parse_with_variant(
289        value: &str,
290        variant: Option<&str>,
291        _field_tag: Option<&str>,
292    ) -> crate::Result<Self>
293    where
294        Self: Sized,
295    {
296        match variant {
297            Some("A") => {
298                let field = Field56A::parse(value)?;
299                Ok(Field56IntermediaryAD::A(field))
300            }
301            Some("D") => {
302                let field = Field56D::parse(value)?;
303                Ok(Field56IntermediaryAD::D(field))
304            }
305            _ => {
306                // No variant specified, fall back to default parse behavior
307                Self::parse(value)
308            }
309        }
310    }
311
312    fn to_swift_string(&self) -> String {
313        match self {
314            Field56IntermediaryAD::A(field) => field.to_swift_string(),
315            Field56IntermediaryAD::D(field) => field.to_swift_string(),
316        }
317    }
318}
319
320// Type aliases for backward compatibility and simplicity
321pub type Field56 = Field56Intermediary;
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_field56a() {
329        // With party identifier
330        let field = Field56A::parse("/C/US123456\nDEUTDEFF").unwrap();
331        assert_eq!(field.party_identifier, Some("C/US123456".to_string()));
332        assert_eq!(field.bic, "DEUTDEFF");
333
334        // With special code //FW
335        let field = Field56A::parse("//FW021000018\nCHASUS33XXX").unwrap();
336        assert_eq!(field.party_identifier, Some("/FW021000018".to_string()));
337        assert_eq!(field.bic, "CHASUS33XXX");
338
339        // Without party identifier
340        let field = Field56A::parse("DEUTDEFFXXX").unwrap();
341        assert_eq!(field.party_identifier, None);
342        assert_eq!(field.bic, "DEUTDEFFXXX");
343    }
344
345    #[test]
346    fn test_field56c() {
347        let field = Field56C::parse("/USCLEARING123").unwrap();
348        assert_eq!(field.party_identifier, "USCLEARING123");
349        assert_eq!(field.to_swift_string(), ":56C:/USCLEARING123");
350    }
351
352    #[test]
353    fn test_field56d() {
354        // With party identifier
355        let field = Field56D::parse("/D/DE123456\nDEUTSCHE BANK\nFRANKFURT").unwrap();
356        assert_eq!(field.party_identifier, Some("D/DE123456".to_string()));
357        assert_eq!(field.name_and_address.len(), 2);
358        assert_eq!(field.name_and_address[0], "DEUTSCHE BANK");
359
360        // Without party identifier
361        let field = Field56D::parse("ACME BANK\nNEW YORK").unwrap();
362        assert_eq!(field.party_identifier, None);
363        assert_eq!(field.name_and_address.len(), 2);
364    }
365
366    #[test]
367    fn test_field56_invalid() {
368        // Invalid BIC
369        assert!(Field56A::parse("INVALID").is_err());
370
371        // Missing slash in 56C
372        assert!(Field56C::parse("NOSLASH").is_err());
373
374        // Too many lines in 56D
375        assert!(Field56D::parse("LINE1\nLINE2\nLINE3\nLINE4\nLINE5").is_err());
376    }
377}