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