swift_mt_message/fields/
field51.rs

1use super::field_utils::parse_party_identifier;
2use super::swift_utils::parse_bic;
3use crate::errors::ParseError;
4use crate::traits::SwiftField;
5use serde::{Deserialize, Serialize};
6
7/// **Field 51A: Sending Institution**
8///
9/// Identifies the message sender in FileAct messages and specialized contexts.
10///
11/// **Format:** `[/1!a][/34x]` + BIC (8 or 11 chars)
12///
13/// **Example:**
14/// ```text
15/// :51A:DEUTDEFFXXX
16/// :51A:/D/12345678
17/// CHASUS33XXX
18/// ```
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct Field51A {
21    /// Optional party identifier (max 34 chars, clearing/account ref)
22    pub party_identifier: Option<String>,
23
24    /// BIC code (8 or 11 chars)
25    pub bic: String,
26}
27
28impl SwiftField for Field51A {
29    fn parse(input: &str) -> crate::Result<Self>
30    where
31        Self: Sized,
32    {
33        let mut remaining = input;
34        let mut party_identifier = None;
35
36        // Check for optional party identifier on first line
37        if let Some(newline_pos) = input.find('\n') {
38            let first_line = &input[..newline_pos];
39            if let Some(id) = parse_party_identifier(first_line)? {
40                party_identifier = Some(format!("/{}", id));
41                remaining = &input[newline_pos + 1..];
42            }
43        }
44
45        // If no party identifier found, check if entire input starts with '/'
46        if party_identifier.is_none() && input.starts_with('/') {
47            // This might be a party identifier without BIC
48            if input.len() <= 36 && !input.contains('\n') {
49                return Err(ParseError::InvalidFormat {
50                    message: "Field 51A requires BIC code after party identifier".to_string(),
51                });
52            }
53        }
54
55        // Parse BIC code
56        let bic = parse_bic(remaining)?;
57
58        Ok(Field51A {
59            party_identifier,
60            bic,
61        })
62    }
63
64    fn to_swift_string(&self) -> String {
65        let mut result = String::new();
66        if let Some(ref party_id) = self.party_identifier {
67            result.push_str(party_id);
68            result.push('\n');
69        }
70        result.push_str(&self.bic);
71        format!(":51A:{}", result)
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_field51a_valid() {
81        // Without party identifier
82        let field = Field51A::parse("DEUTDEFFXXX").unwrap();
83        assert_eq!(field.bic, "DEUTDEFFXXX");
84        assert_eq!(field.party_identifier, None);
85        assert_eq!(field.to_swift_string(), ":51A:DEUTDEFFXXX");
86
87        // With party identifier
88        let field = Field51A::parse("/D/12345678\nCHASUS33XXX").unwrap();
89        assert_eq!(field.bic, "CHASUS33XXX");
90        assert_eq!(field.party_identifier, Some("/D/12345678".to_string()));
91        assert_eq!(field.to_swift_string(), ":51A:/D/12345678\nCHASUS33XXX");
92
93        // 8-character BIC
94        let field = Field51A::parse("MIDLGB22").unwrap();
95        assert_eq!(field.bic, "MIDLGB22");
96        assert_eq!(field.party_identifier, None);
97    }
98
99    #[test]
100    fn test_field51a_invalid() {
101        // Invalid BIC
102        assert!(Field51A::parse("INVALID").is_err());
103
104        // Party identifier without BIC
105        assert!(Field51A::parse("/D/12345678").is_err());
106
107        // Invalid characters in BIC
108        assert!(Field51A::parse("DEUT@EFF").is_err());
109    }
110}