swift_mt_message/fields/
field25.rs1use super::swift_utils::{parse_bic, parse_max_length, parse_swift_chars};
2use crate::errors::ParseError;
3use crate::traits::SwiftField;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Field25NoOption {
18 pub authorisation: String,
20}
21
22impl SwiftField for Field25NoOption {
23 fn parse(input: &str) -> crate::Result<Self>
24 where
25 Self: Sized,
26 {
27 let input_stripped = input.strip_prefix('/').unwrap_or(input);
29
30 let authorisation = parse_max_length(input_stripped, 35, "Field 25 authorisation")?;
32 parse_swift_chars(&authorisation, "Field 25 authorisation")?;
33
34 Ok(Field25NoOption { authorisation })
35 }
36
37 fn to_swift_string(&self) -> String {
38 if self.authorisation.starts_with('/') {
40 format!(":25:{}", self.authorisation)
41 } else {
42 format!(":25:/{}", self.authorisation)
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct Field25A {
54 pub account: String,
56}
57
58impl SwiftField for Field25A {
59 fn parse(input: &str) -> crate::Result<Self>
60 where
61 Self: Sized,
62 {
63 if !input.starts_with('/') {
65 return Err(ParseError::InvalidFormat {
66 message: "Field 25A must start with '/'".to_string(),
67 });
68 }
69
70 let account_part = &input[1..];
72 if account_part.is_empty() {
73 return Err(ParseError::InvalidFormat {
74 message: "Field 25A account cannot be empty after '/'".to_string(),
75 });
76 }
77
78 if account_part.len() > 34 {
79 return Err(ParseError::InvalidFormat {
80 message: format!(
81 "Field 25A account must not exceed 34 characters, found {}",
82 account_part.len()
83 ),
84 });
85 }
86
87 parse_swift_chars(account_part, "Field 25A account")?;
88
89 Ok(Field25A {
90 account: account_part.to_string(), })
92 }
93
94 fn to_swift_string(&self) -> String {
95 if self.account.starts_with('/') {
97 format!(":25A:{}", self.account)
98 } else {
99 format!(":25A:/{}", self.account)
100 }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub struct Field25P {
111 pub account: String,
113
114 pub bic: String,
116}
117
118impl SwiftField for Field25P {
119 fn parse(input: &str) -> crate::Result<Self>
120 where
121 Self: Sized,
122 {
123 let lines: Vec<&str> = input.split('\n').collect();
125
126 if lines.is_empty() {
127 return Err(ParseError::InvalidFormat {
128 message: "Field 25P cannot be empty".to_string(),
129 });
130 }
131
132 let account = parse_max_length(lines[0], 35, "Field 25P account")?;
134 parse_swift_chars(&account, "Field 25P account")?;
135
136 let bic = if lines.len() > 1 {
138 parse_bic(lines[1])?
139 } else {
140 if input.len() > 8 {
142 let potential_bic_11 = &input[input.len().saturating_sub(11)..];
143 let potential_bic_8 = &input[input.len().saturating_sub(8)..];
144
145 if potential_bic_11.len() == 11
147 && let Ok(bic) = parse_bic(potential_bic_11)
148 {
149 let account_part = &input[..input.len() - 11];
151 return Ok(Field25P {
152 account: parse_max_length(account_part, 35, "Field 25P account")?,
153 bic,
154 });
155 }
156
157 if potential_bic_8.len() == 8
159 && let Ok(bic) = parse_bic(potential_bic_8)
160 {
161 let account_part = &input[..input.len() - 8];
163 return Ok(Field25P {
164 account: parse_max_length(account_part, 35, "Field 25P account")?,
165 bic,
166 });
167 }
168 }
169
170 return Err(ParseError::InvalidFormat {
171 message: "Field 25P requires a BIC code".to_string(),
172 });
173 };
174
175 Ok(Field25P { account, bic })
176 }
177
178 fn to_swift_string(&self) -> String {
179 format!(":25P:{}\n{}", self.account, self.bic)
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187#[serde(untagged)]
188pub enum Field25AccountIdentification {
189 NoOption(Field25NoOption),
191 P(Field25P),
193}
194
195impl SwiftField for Field25AccountIdentification {
196 fn parse(input: &str) -> crate::Result<Self>
197 where
198 Self: Sized,
199 {
200 if input.contains('\n')
203 || (input.len() > 8 && parse_bic(&input[input.len().saturating_sub(11)..]).is_ok())
204 || (input.len() > 8 && parse_bic(&input[input.len().saturating_sub(8)..]).is_ok())
205 {
206 Ok(Field25AccountIdentification::P(Field25P::parse(input)?))
207 } else {
208 Ok(Field25AccountIdentification::NoOption(
210 Field25NoOption::parse(input)?,
211 ))
212 }
213 }
214
215 fn to_swift_string(&self) -> String {
216 match self {
217 Field25AccountIdentification::NoOption(field) => field.to_swift_string(),
218 Field25AccountIdentification::P(field) => field.to_swift_string(),
219 }
220 }
221}
222
223pub type Field25 = Field25NoOption;
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_field25_no_option() {
232 let field = Field25NoOption::parse("AUTH123456789").unwrap();
234 assert_eq!(field.authorisation, "AUTH123456789");
235 assert_eq!(field.to_swift_string(), ":25:/AUTH123456789"); let field = Field25NoOption::parse("/1234567890").unwrap();
239 assert_eq!(field.authorisation, "1234567890"); assert_eq!(field.to_swift_string(), ":25:/1234567890"); let long_auth = "A".repeat(35);
244 let field = Field25NoOption::parse(&long_auth).unwrap();
245 assert_eq!(field.authorisation, long_auth);
246
247 let too_long = "A".repeat(36);
249 assert!(Field25NoOption::parse(&too_long).is_err());
250 }
251
252 #[test]
253 fn test_field25a() {
254 let field = Field25A::parse("/GB82WEST12345698765432").unwrap();
255 assert_eq!(field.account, "GB82WEST12345698765432"); assert_eq!(field.to_swift_string(), ":25A:/GB82WEST12345698765432"); let field = Field25A::parse("/1234567890").unwrap();
259 assert_eq!(field.account, "1234567890"); assert!(Field25A::parse("1234567890").is_err());
263
264 assert!(Field25A::parse("/").is_err());
266
267 let too_long = format!("/{}", "A".repeat(35));
269 assert!(Field25A::parse(&too_long).is_err());
270 }
271
272 #[test]
273 fn test_field25p() {
274 let field = Field25P::parse("CHF1234567890\nUBSWCHZH80A").unwrap();
276 assert_eq!(field.account, "CHF1234567890");
277 assert_eq!(field.bic, "UBSWCHZH80A");
278 assert_eq!(field.to_swift_string(), ":25P:CHF1234567890\nUBSWCHZH80A");
279
280 let field = Field25P::parse("ACCOUNT123DEUTDEFF").unwrap();
282 assert_eq!(field.account, "ACCOUNT123");
283 assert_eq!(field.bic, "DEUTDEFF");
284
285 let field = Field25P::parse("ACC456DEUTDEFFXXX").unwrap();
287 assert_eq!(field.account, "ACC456");
288 assert_eq!(field.bic, "DEUTDEFFXXX");
289 }
290
291 #[test]
292 fn test_field25_account_identification() {
293 let field = Field25AccountIdentification::parse("AUTH999").unwrap();
295 match field {
296 Field25AccountIdentification::NoOption(f) => assert_eq!(f.authorisation, "AUTH999"),
297 _ => panic!("Expected NoOption variant"),
298 }
299
300 let field = Field25AccountIdentification::parse("MYACCOUNT\nDEUTDEFF").unwrap();
302 match field {
303 Field25AccountIdentification::P(f) => {
304 assert_eq!(f.account, "MYACCOUNT");
305 assert_eq!(f.bic, "DEUTDEFF");
306 }
307 _ => panic!("Expected P variant"),
308 }
309 }
310}