1use crate::{SwiftField, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108pub struct Field54B {
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub account_line_indicator: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub account_number: Option<String>,
115 pub party_identifier: String,
117}
118
119impl Field54B {
120 pub fn new(
122 account_line_indicator: Option<String>,
123 account_number: Option<String>,
124 party_identifier: impl Into<String>,
125 ) -> Result<Self, crate::ParseError> {
126 let party_identifier = party_identifier.into().trim().to_string();
127
128 if party_identifier.is_empty() {
130 return Err(crate::ParseError::InvalidFieldFormat {
131 field_tag: "54B".to_string(),
132 message: "Party identifier cannot be empty".to_string(),
133 });
134 }
135
136 if party_identifier.len() > 35 {
137 return Err(crate::ParseError::InvalidFieldFormat {
138 field_tag: "54B".to_string(),
139 message: "Party identifier cannot exceed 35 characters".to_string(),
140 });
141 }
142
143 if !party_identifier
144 .chars()
145 .all(|c| c.is_ascii() && !c.is_control())
146 {
147 return Err(crate::ParseError::InvalidFieldFormat {
148 field_tag: "54B".to_string(),
149 message: "Party identifier contains invalid characters".to_string(),
150 });
151 }
152
153 if let Some(ref indicator) = account_line_indicator {
155 if indicator.is_empty() {
156 return Err(crate::ParseError::InvalidFieldFormat {
157 field_tag: "54B".to_string(),
158 message: "Account line indicator cannot be empty if specified".to_string(),
159 });
160 }
161
162 if indicator.len() != 1 {
163 return Err(crate::ParseError::InvalidFieldFormat {
164 field_tag: "54B".to_string(),
165 message: "Account line indicator must be exactly 1 character".to_string(),
166 });
167 }
168
169 if !indicator.chars().all(|c| c.is_ascii() && !c.is_control()) {
170 return Err(crate::ParseError::InvalidFieldFormat {
171 field_tag: "54B".to_string(),
172 message: "Account line indicator contains invalid characters".to_string(),
173 });
174 }
175 }
176
177 if let Some(ref account) = account_number {
179 if account.is_empty() {
180 return Err(crate::ParseError::InvalidFieldFormat {
181 field_tag: "54B".to_string(),
182 message: "Account number cannot be empty if specified".to_string(),
183 });
184 }
185
186 if account.len() > 34 {
187 return Err(crate::ParseError::InvalidFieldFormat {
188 field_tag: "54B".to_string(),
189 message: "Account number cannot exceed 34 characters".to_string(),
190 });
191 }
192
193 if !account.chars().all(|c| c.is_ascii() && !c.is_control()) {
194 return Err(crate::ParseError::InvalidFieldFormat {
195 field_tag: "54B".to_string(),
196 message: "Account number contains invalid characters".to_string(),
197 });
198 }
199 }
200
201 Ok(Field54B {
202 account_line_indicator,
203 account_number,
204 party_identifier,
205 })
206 }
207
208 pub fn account_line_indicator(&self) -> Option<&str> {
210 self.account_line_indicator.as_deref()
211 }
212
213 pub fn account_number(&self) -> Option<&str> {
215 self.account_number.as_deref()
216 }
217
218 pub fn party_identifier(&self) -> &str {
220 &self.party_identifier
221 }
222
223 pub fn description(&self) -> String {
225 format!(
226 "Receiver's Correspondent (Party ID: {})",
227 self.party_identifier
228 )
229 }
230}
231
232impl SwiftField for Field54B {
233 fn parse(content: &str) -> crate::Result<Self> {
234 let content = content.trim();
235 if content.is_empty() {
236 return Err(crate::ParseError::InvalidFieldFormat {
237 field_tag: "54B".to_string(),
238 message: "Field content cannot be empty".to_string(),
239 });
240 }
241
242 let content = if let Some(stripped) = content.strip_prefix(":54B:") {
243 stripped
244 } else if let Some(stripped) = content.strip_prefix("54B:") {
245 stripped
246 } else {
247 content
248 };
249
250 let mut account_line_indicator = None;
251 let mut account_number = None;
252 let mut party_identifier_content = content;
253
254 if content.starts_with('/') {
256 let lines: Vec<&str> = content.lines().collect();
257 if !lines.is_empty() {
258 let first_line = lines[0];
259
260 if first_line.len() == 2 && first_line.starts_with('/') {
261 account_line_indicator = Some(first_line[1..].to_string());
263 party_identifier_content = if lines.len() > 1 { lines[1] } else { "" };
264 } else if first_line.len() > 2 && first_line.starts_with('/') {
265 let parts: Vec<&str> = first_line[1..].split('/').collect();
267 if parts.len() == 2 {
268 account_line_indicator = Some(parts[0].to_string());
270 account_number = Some(parts[1].to_string());
271 } else {
272 account_number = Some(parts[0].to_string());
274 }
275 party_identifier_content = if lines.len() > 1 { lines[1] } else { "" };
276 }
277 }
278 }
279
280 let party_identifier = party_identifier_content.trim().to_string();
281 if party_identifier.is_empty() {
282 return Err(crate::ParseError::InvalidFieldFormat {
283 field_tag: "54B".to_string(),
284 message: "Party identifier is required".to_string(),
285 });
286 }
287
288 Field54B::new(account_line_indicator, account_number, party_identifier)
289 }
290
291 fn to_swift_string(&self) -> String {
292 let mut result = String::new();
293
294 if let Some(ref indicator) = self.account_line_indicator {
295 result.push('/');
296 result.push_str(indicator);
297 }
298
299 if let Some(ref account) = self.account_number {
300 result.push('/');
301 result.push_str(account);
302 }
303
304 if !result.is_empty() {
305 result.push('\n');
306 }
307 result.push_str(&self.party_identifier);
308
309 format!(":54B:{}", result)
310 }
311
312 fn validate(&self) -> ValidationResult {
313 ValidationResult {
315 is_valid: true,
316 errors: Vec::new(),
317 warnings: Vec::new(),
318 }
319 }
320
321 fn format_spec() -> &'static str {
322 "[/1!a][/34x]35x"
323 }
324}
325
326impl std::fmt::Display for Field54B {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 match (&self.account_line_indicator, &self.account_number) {
329 (Some(indicator), Some(account)) => write!(
330 f,
331 "Indicator: {}, Account: {}, Party: {}",
332 indicator, account, self.party_identifier
333 ),
334 (None, Some(account)) => {
335 write!(f, "Account: {}, Party: {}", account, self.party_identifier)
336 }
337 (Some(indicator), None) => write!(
338 f,
339 "Indicator: {}, Party: {}",
340 indicator, self.party_identifier
341 ),
342 (None, None) => write!(f, "Party: {}", self.party_identifier),
343 }
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_field54b_creation_party_only() {
353 let field = Field54B::new(None, None, "RCVRPARTYID123").unwrap();
354 assert_eq!(field.party_identifier(), "RCVRPARTYID123");
355 assert!(field.account_number().is_none());
356 assert!(field.account_line_indicator().is_none());
357 }
358
359 #[test]
360 fn test_field54b_creation_with_account() {
361 let field = Field54B::new(None, Some("9876543210".to_string()), "RCVRPARTYID123").unwrap();
362 assert_eq!(field.party_identifier(), "RCVRPARTYID123");
363 assert_eq!(field.account_number(), Some("9876543210"));
364 assert!(field.account_line_indicator().is_none());
365 }
366
367 #[test]
368 fn test_field54b_creation_with_account_line_indicator() {
369 let field = Field54B::new(
370 Some("B".to_string()),
371 Some("9876543210".to_string()),
372 "RCVRPARTYID123",
373 )
374 .unwrap();
375 assert_eq!(field.party_identifier(), "RCVRPARTYID123");
376 assert_eq!(field.account_number(), Some("9876543210"));
377 assert_eq!(field.account_line_indicator(), Some("B"));
378 }
379
380 #[test]
381 fn test_field54b_parse_party_only() {
382 let field = Field54B::parse("RCVRPARTYID123").unwrap();
383 assert_eq!(field.party_identifier(), "RCVRPARTYID123");
384 assert!(field.account_number().is_none());
385 }
386
387 #[test]
388 fn test_field54b_parse_with_account() {
389 let field = Field54B::parse("/9876543210\nRCVRPARTYID123").unwrap();
390 assert_eq!(field.party_identifier(), "RCVRPARTYID123");
391 assert_eq!(field.account_number(), Some("9876543210"));
392 }
393
394 #[test]
395 fn test_field54b_parse_with_tag() {
396 let field = Field54B::parse(":54B:RCVRPARTYID123").unwrap();
397 assert_eq!(field.party_identifier(), "RCVRPARTYID123");
398 }
399
400 #[test]
401 fn test_field54b_to_swift_string() {
402 let field = Field54B::new(None, None, "RCVRPARTYID123").unwrap();
403 assert_eq!(field.to_swift_string(), ":54B:RCVRPARTYID123");
404
405 let field = Field54B::new(None, Some("9876543210".to_string()), "RCVRPARTYID123").unwrap();
406 assert_eq!(field.to_swift_string(), ":54B:/9876543210\nRCVRPARTYID123");
407 }
408
409 #[test]
410 fn test_field54b_display() {
411 let field = Field54B::new(None, None, "RCVRPARTYID123").unwrap();
412 assert_eq!(format!("{}", field), "Party: RCVRPARTYID123");
413
414 let field = Field54B::new(None, Some("9876543210".to_string()), "RCVRPARTYID123").unwrap();
415 assert_eq!(
416 format!("{}", field),
417 "Account: 9876543210, Party: RCVRPARTYID123"
418 );
419 }
420
421 #[test]
422 fn test_field54b_description() {
423 let field = Field54B::new(None, None, "RCVRPARTYID123").unwrap();
424 assert_eq!(
425 field.description(),
426 "Receiver's Correspondent (Party ID: RCVRPARTYID123)"
427 );
428 }
429
430 #[test]
431 fn test_field54b_validation_empty_party() {
432 let result = Field54B::new(None, None, "");
433 assert!(result.is_err());
434 assert!(result.unwrap_err().to_string().contains("cannot be empty"));
435 }
436
437 #[test]
438 fn test_field54b_validation_party_too_long() {
439 let party = "A".repeat(36); let result = Field54B::new(None, None, party);
441 assert!(result.is_err());
442 assert!(
443 result
444 .unwrap_err()
445 .to_string()
446 .contains("cannot exceed 35 characters")
447 );
448 }
449
450 #[test]
451 fn test_field54b_validation_invalid_characters() {
452 let result = Field54B::new(None, None, "PARTY\x00ID"); assert!(result.is_err());
454 assert!(
455 result
456 .unwrap_err()
457 .to_string()
458 .contains("invalid characters")
459 );
460 }
461
462 #[test]
463 fn test_field54b_validation_account_too_long() {
464 let account = "A".repeat(35); let result = Field54B::new(None, Some(account), "RCVRPARTYID123");
466 assert!(result.is_err());
467 assert!(
468 result
469 .unwrap_err()
470 .to_string()
471 .contains("cannot exceed 34 characters")
472 );
473 }
474
475 #[test]
476 fn test_field54b_validate() {
477 let field = Field54B::new(None, None, "RCVRPARTYID123").unwrap();
478 let validation = field.validate();
479 assert!(validation.is_valid);
480 assert!(validation.errors.is_empty());
481 }
482}