1use crate::{SwiftField, ValidationResult};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108pub struct Field57B {
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 Field57B {
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: "57B".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: "57B".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: "57B".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: "57B".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: "57B".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: "57B".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: "57B".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: "57B".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: "57B".to_string(),
196 message: "Account number contains invalid characters".to_string(),
197 });
198 }
199 }
200
201 Ok(Field57B {
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 "Account With Institution (Party ID: {})",
227 self.party_identifier
228 )
229 }
230}
231
232impl SwiftField for Field57B {
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: "57B".to_string(),
238 message: "Field content cannot be empty".to_string(),
239 });
240 }
241
242 let content = if let Some(stripped) = content.strip_prefix(":57B:") {
243 stripped
244 } else if let Some(stripped) = content.strip_prefix("57B:") {
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: "57B".to_string(),
284 message: "Party identifier is required".to_string(),
285 });
286 }
287
288 Field57B::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!(":57B:{}", 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 Field57B {
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_field57b_creation_party_only() {
353 let field = Field57B::new(None, None, "ACCOUNTWITHPARTYID123").unwrap();
354 assert_eq!(field.party_identifier(), "ACCOUNTWITHPARTYID123");
355 assert!(field.account_number().is_none());
356 assert!(field.account_line_indicator().is_none());
357 }
358
359 #[test]
360 fn test_field57b_creation_with_account() {
361 let field = Field57B::new(
362 None,
363 Some("ACCT987654321".to_string()),
364 "ACCOUNTWITHPARTYID123",
365 )
366 .unwrap();
367 assert_eq!(field.party_identifier(), "ACCOUNTWITHPARTYID123");
368 assert_eq!(field.account_number(), Some("ACCT987654321"));
369 assert!(field.account_line_indicator().is_none());
370 }
371
372 #[test]
373 fn test_field57b_creation_with_account_line_indicator() {
374 let field = Field57B::new(
375 Some("D".to_string()),
376 Some("ACCT987654321".to_string()),
377 "ACCOUNTWITHPARTYID123",
378 )
379 .unwrap();
380 assert_eq!(field.party_identifier(), "ACCOUNTWITHPARTYID123");
381 assert_eq!(field.account_number(), Some("ACCT987654321"));
382 assert_eq!(field.account_line_indicator(), Some("D"));
383 }
384
385 #[test]
386 fn test_field57b_parse_party_only() {
387 let field = Field57B::parse("ACCOUNTWITHPARTYID123").unwrap();
388 assert_eq!(field.party_identifier(), "ACCOUNTWITHPARTYID123");
389 assert!(field.account_number().is_none());
390 }
391
392 #[test]
393 fn test_field57b_parse_with_account() {
394 let field = Field57B::parse("/ACCT987654321\nACCOUNTWITHPARTYID123").unwrap();
395 assert_eq!(field.party_identifier(), "ACCOUNTWITHPARTYID123");
396 assert_eq!(field.account_number(), Some("ACCT987654321"));
397 }
398
399 #[test]
400 fn test_field57b_parse_with_tag() {
401 let field = Field57B::parse(":57B:ACCOUNTWITHPARTYID123").unwrap();
402 assert_eq!(field.party_identifier(), "ACCOUNTWITHPARTYID123");
403 }
404
405 #[test]
406 fn test_field57b_to_swift_string() {
407 let field = Field57B::new(None, None, "ACCOUNTWITHPARTYID123").unwrap();
408 assert_eq!(field.to_swift_string(), ":57B:ACCOUNTWITHPARTYID123");
409
410 let field = Field57B::new(
411 None,
412 Some("ACCT987654321".to_string()),
413 "ACCOUNTWITHPARTYID123",
414 )
415 .unwrap();
416 assert_eq!(
417 field.to_swift_string(),
418 ":57B:/ACCT987654321\nACCOUNTWITHPARTYID123"
419 );
420 }
421
422 #[test]
423 fn test_field57b_display() {
424 let field = Field57B::new(None, None, "ACCOUNTWITHPARTYID123").unwrap();
425 assert_eq!(format!("{}", field), "Party: ACCOUNTWITHPARTYID123");
426
427 let field = Field57B::new(
428 None,
429 Some("ACCT987654321".to_string()),
430 "ACCOUNTWITHPARTYID123",
431 )
432 .unwrap();
433 assert_eq!(
434 format!("{}", field),
435 "Account: ACCT987654321, Party: ACCOUNTWITHPARTYID123"
436 );
437 }
438
439 #[test]
440 fn test_field57b_description() {
441 let field = Field57B::new(None, None, "ACCOUNTWITHPARTYID123").unwrap();
442 assert_eq!(
443 field.description(),
444 "Account With Institution (Party ID: ACCOUNTWITHPARTYID123)"
445 );
446 }
447
448 #[test]
449 fn test_field57b_validation_empty_party() {
450 let result = Field57B::new(None, None, "");
451 assert!(result.is_err());
452 assert!(result.unwrap_err().to_string().contains("cannot be empty"));
453 }
454
455 #[test]
456 fn test_field57b_validation_party_too_long() {
457 let party = "A".repeat(36); let result = Field57B::new(None, None, party);
459 assert!(result.is_err());
460 assert!(
461 result
462 .unwrap_err()
463 .to_string()
464 .contains("cannot exceed 35 characters")
465 );
466 }
467
468 #[test]
469 fn test_field57b_validation_invalid_characters() {
470 let result = Field57B::new(None, None, "PARTY\x00ID"); assert!(result.is_err());
472 assert!(
473 result
474 .unwrap_err()
475 .to_string()
476 .contains("invalid characters")
477 );
478 }
479
480 #[test]
481 fn test_field57b_validation_account_too_long() {
482 let account = "A".repeat(35); let result = Field57B::new(None, Some(account), "ACCOUNTWITHPARTYID123");
484 assert!(result.is_err());
485 assert!(
486 result
487 .unwrap_err()
488 .to_string()
489 .contains("cannot exceed 34 characters")
490 );
491 }
492
493 #[test]
494 fn test_field57b_validate() {
495 let field = Field57B::new(None, None, "ACCOUNTWITHPARTYID123").unwrap();
496 let validation = field.validate();
497 assert!(validation.is_valid);
498 assert!(validation.errors.is_empty());
499 }
500}