swift_mt_message/fields/common/
bic_field.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct GenericBicField {
9 pub bic: BIC,
11 pub account: Option<String>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct BIC {
17 pub raw: String,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub bank_code: Option<String>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub country_code: Option<String>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub location_code: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub branch_code: Option<String>,
30}
31
32impl std::fmt::Display for BIC {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 write!(f, "{}", self.raw)
35 }
36}
37
38impl std::str::FromStr for BIC {
39 type Err = String;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 if s.len() < 8 {
43 return Err("BIC must be at least 8 characters".to_string());
44 }
45
46 if s.len() > 11 {
47 let truncated = &s[..11];
49 return BIC::from_str(truncated);
50 }
51
52 Ok(BIC {
53 raw: s.to_string(),
54 bank_code: if s.len() > 4 {
55 Some(s[0..4].to_string())
56 } else {
57 None
58 },
59 country_code: if s.len() > 6 {
60 Some(s[4..6].to_string())
61 } else {
62 None
63 },
64 location_code: if s.len() > 8 {
65 Some(s[6..8].to_string())
66 } else {
67 None
68 },
69 branch_code: if s.len() == 11 {
70 Some(s[8..11].to_string())
71 } else {
72 None
73 },
74 })
75 }
76}
77
78impl crate::SwiftField for GenericBicField {
80 fn parse(value: &str) -> crate::Result<Self> {
81 let content = value.trim();
82
83 let content = Self::remove_field_tag_prefix(content);
85
86 if let Some(slash_pos) = content.find('/') {
88 let bic_str = &content[..slash_pos];
90 let account_str = &content[slash_pos + 1..];
91
92 let bic =
93 bic_str
94 .parse()
95 .map_err(|e: String| crate::ParseError::InvalidFieldFormat {
96 field_tag: "GENERICBICFIELD".to_string(),
97 message: format!("Failed to parse BIC: {e}"),
98 })?;
99
100 let account = if account_str.is_empty() {
101 None
102 } else {
103 Some(account_str.to_string())
104 };
105
106 Ok(GenericBicField { bic, account })
107 } else {
108 let bic =
110 content
111 .parse()
112 .map_err(|e: String| crate::ParseError::InvalidFieldFormat {
113 field_tag: "GENERICBICFIELD".to_string(),
114 message: format!("Failed to parse BIC: {e}"),
115 })?;
116
117 Ok(GenericBicField { bic, account: None })
118 }
119 }
120
121 fn to_swift_string(&self) -> String {
122 if let Some(ref account) = self.account {
123 format!("{}/{}", self.bic, account)
124 } else {
125 self.bic.to_string()
126 }
127 }
128
129 fn validate(&self) -> crate::ValidationResult {
130 let mut errors = Vec::new();
131 let mut warnings = Vec::new();
132
133 if self.bic.raw.len() != 8 && self.bic.raw.len() != 11 {
135 errors.push(crate::ValidationError::FormatValidation {
136 field_tag: "GENERICBICFIELD".to_string(),
137 message: format!("BIC must be 8 or 11 characters, got {}", self.bic.raw.len()),
138 });
139 }
140
141 if let Some(ref account) = self.account {
143 if account.len() > 35 {
144 errors.push(crate::ValidationError::LengthValidation {
145 field_tag: "GENERICBICFIELD".to_string(),
146 expected: "≤35".to_string(),
147 actual: account.len(),
148 });
149 }
150 if account.is_empty() {
151 warnings.push("Empty account number provided".to_string());
152 }
153 }
154
155 crate::ValidationResult {
156 is_valid: errors.is_empty(),
157 errors,
158 warnings,
159 }
160 }
161
162 fn format_spec() -> &'static str {
163 "4!a2!a2!c[3!c][/35x]"
164 }
165}
166
167impl GenericBicField {
168 fn remove_field_tag_prefix(value: &str) -> &str {
171 use std::sync::OnceLock;
172 static FIELD_TAG_REGEX: OnceLock<regex::Regex> = OnceLock::new();
173
174 let regex = FIELD_TAG_REGEX.get_or_init(|| {
175 regex::Regex::new(r"^:?([0-9]{1,3}[A-Z]{0,2}):").unwrap()
178 });
179
180 if let Some(captures) = regex.find(value) {
181 &value[captures.end()..]
182 } else {
183 value
184 }
185 }
186}
187
188impl std::fmt::Display for GenericBicField {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 if let Some(ref account) = self.account {
191 write!(f, "{}/{}", self.bic, account)
192 } else {
193 write!(f, "{}", self.bic)
194 }
195 }
196}