swift_mt_message/fields/
field59.rs

1use crate::common::BIC;
2use crate::{SwiftField, ValidationResult, errors::ParseError};
3use serde::de::{self, MapAccess, Visitor};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::fmt;
6
7/// # Field 59: Beneficiary Customer
8///
9/// ## Overview
10/// Field 59 identifies the beneficiary customer in SWIFT payment messages, representing the
11/// ultimate recipient of the payment funds. This field supports multiple identification
12/// options (59A, 59F, and 59 without letter) to accommodate different beneficiary types
13/// and identification requirements. The beneficiary customer is the final destination for
14/// payment funds and must be clearly identified for regulatory compliance and payment delivery.
15///
16/// ## Format Specification
17/// ### Field 59A (BIC Option)
18/// **Format**: `[/account]BIC`
19/// - **account**: Optional account number (up to 34 characters)
20/// - **BIC**: Bank Identifier Code (8 or 11 characters)
21///
22/// ### Field 59F (Party Identifier Option)
23/// **Format**: `party_identifier + 4*(1!n/33x)`
24/// - **party_identifier**: Up to 35 characters
25/// - **name_and_address**: Up to 4 structured lines (format: n/text)
26///
27/// ### Field 59 (Basic Option)
28/// **Format**: `4*35x`
29/// - **beneficiary_customer**: Up to 4 lines of 35 characters each
30///
31/// ## Structure
32/// ```text
33/// Option A (BIC):
34/// /DE89370400440532013000
35/// DEUTDEFFXXX
36///
37/// Option F (Party ID):
38/// PARTYID123456789
39/// 1/JOHN DOE
40/// 2/123 MAIN STREET
41/// 3/NEW YORK NY 10001
42/// 4/UNITED STATES
43///
44/// Basic Option:
45/// MUELLER GMBH
46/// HAUPTSTRASSE 1
47/// 60311 FRANKFURT
48/// GERMANY
49/// ```
50///
51/// ## Field Components
52/// - **Account Number**: Beneficiary's account identifier (Option A)
53/// - **BIC Code**: Bank Identifier Code (Option A)
54/// - **Party Identifier**: Structured party identification (Option F)
55/// - **Name and Address**: Beneficiary details (Options F and Basic)
56/// - **Line Numbers**: Structured addressing (Option F: 1-4)
57///
58/// ## Usage Context
59/// Field 59 variants are used in:
60/// - **MT103**: Single Customer Credit Transfer
61/// - **MT200**: Financial Institution Transfer
62/// - **MT202**: General Financial Institution Transfer
63/// - **MT202COV**: Cover for customer credit transfer
64/// - **MT205**: Financial Institution Transfer for its own account
65///
66/// ### Business Applications
67/// - **Payment delivery**: Identifying final payment recipient
68/// - **Regulatory compliance**: Meeting beneficiary identification requirements
69/// - **AML/KYC compliance**: Supporting anti-money laundering checks
70/// - **Payment transparency**: Providing clear beneficiary details
71/// - **Cross-border payments**: International beneficiary identification
72/// - **Sanctions screening**: Enabling compliance checks
73///
74/// ## Examples
75/// ```text
76/// :59A:/DE89370400440532013000
77/// DEUTDEFFXXX
78/// └─── German bank customer with IBAN and BIC
79///
80/// :59F:PARTYID123456789
81/// 1/JOHN DOE
82/// 2/123 MAIN STREET
83/// 3/NEW YORK NY 10001
84/// 4/UNITED STATES
85/// └─── Structured party identification with address
86///
87/// :59:MUELLER GMBH
88/// HAUPTSTRASSE 1
89/// 60311 FRANKFURT
90/// GERMANY
91/// └─── Basic beneficiary identification
92/// ```
93///
94/// ## Validation Rules
95/// ### Option A (BIC)
96/// 1. **BIC format**: Must be valid 8 or 11 character BIC
97/// 2. **Account number**: Maximum 34 characters (optional)
98/// 3. **Character validation**: SWIFT character set compliance
99///
100/// ### Option F (Party Identifier)
101/// 1. **Party identifier**: Maximum 35 characters, required
102/// 2. **Name/address lines**: Maximum 4 lines, format n/text
103/// 3. **Line content**: Maximum 33 characters per line
104/// 4. **Line numbers**: Must be 1-4
105///
106/// ### Basic Option
107/// 1. **Line count**: Maximum 4 lines
108/// 2. **Line length**: Maximum 35 characters per line
109/// 3. **Content**: Must contain meaningful beneficiary information
110///
111/// ## Network Validated Rules (SWIFT Standards)
112/// - BIC must be valid if used (Error: T10)
113/// - Account number cannot exceed 34 characters (Error: T14)
114/// - Party identifier cannot exceed 35 characters (Error: T50)
115/// - Name/address lines cannot exceed specified limits (Error: T26)
116/// - Must use SWIFT character set only (Error: T61)
117/// - Beneficiary must be identifiable (Error: T51)
118/// - Only one 59 option per message (Error: C59)
119///
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
122pub struct Field59A {
123    /// Account number (optional)
124    pub account: Option<String>,
125    /// BIC (Bank Identifier Code)
126    #[serde(flatten)]
127    pub bic: BIC,
128}
129
130impl Field59A {
131    /// Create a new Field59A with validation
132    pub fn new(account: Option<String>, bic: impl Into<String>) -> Result<Self, ParseError> {
133        let bic_str = bic.into().trim().to_string();
134
135        // Parse and validate BIC using the common structure
136        let parsed_bic = BIC::parse(&bic_str, Some("59A"))?;
137
138        // Validate account if present
139        if let Some(ref acc) = account {
140            if acc.is_empty() {
141                return Err(ParseError::InvalidFieldFormat {
142                    field_tag: "59A".to_string(),
143                    message: "Account cannot be empty if specified".to_string(),
144                });
145            }
146
147            if acc.len() > 34 {
148                return Err(ParseError::InvalidFieldFormat {
149                    field_tag: "59A".to_string(),
150                    message: "Account number too long (max 34 characters)".to_string(),
151                });
152            }
153        }
154
155        Ok(Field59A {
156            account,
157            bic: parsed_bic,
158        })
159    }
160
161    /// Get the account number
162    pub fn account(&self) -> Option<&str> {
163        self.account.as_deref()
164    }
165
166    /// Get the BIC code
167    pub fn bic(&self) -> &str {
168        self.bic.value()
169    }
170}
171
172impl std::fmt::Display for Field59A {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        match &self.account {
175            Some(account) => write!(f, "Account: {}, BIC: {}", account, self.bic.value()),
176            None => write!(f, "BIC: {}", self.bic.value()),
177        }
178    }
179}
180
181/// Field 59F: Beneficiary Customer (Option F)
182///
183/// Format: 35x (party identifier) + 4*(1!n/33x) (name and address)
184///
185/// This field specifies the beneficiary customer using a party identifier and structured name/address.
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
187pub struct Field59F {
188    /// Party identifier (up to 35 characters)
189    pub party_identifier: String,
190    /// Name and address lines (up to 4 lines in format 1/Name, 2/Address, etc.)
191    pub name_and_address: Vec<String>,
192}
193
194impl Field59F {
195    /// Create a new Field59F with validation
196    pub fn new(
197        party_identifier: impl Into<String>,
198        name_and_address: Vec<String>,
199    ) -> Result<Self, ParseError> {
200        let party_identifier = party_identifier.into().trim().to_string();
201
202        if party_identifier.is_empty() {
203            return Err(ParseError::InvalidFieldFormat {
204                field_tag: "59F".to_string(),
205                message: "Party identifier cannot be empty".to_string(),
206            });
207        }
208
209        if party_identifier.len() > 35 {
210            return Err(ParseError::InvalidFieldFormat {
211                field_tag: "59F".to_string(),
212                message: "Party identifier cannot exceed 35 characters".to_string(),
213            });
214        }
215
216        if !party_identifier
217            .chars()
218            .all(|c| c.is_ascii() && !c.is_control())
219        {
220            return Err(ParseError::InvalidFieldFormat {
221                field_tag: "59F".to_string(),
222                message: "Party identifier contains invalid characters".to_string(),
223            });
224        }
225
226        if name_and_address.is_empty() {
227            return Err(ParseError::InvalidFieldFormat {
228                field_tag: "59F".to_string(),
229                message: "Name and address cannot be empty".to_string(),
230            });
231        }
232
233        if name_and_address.len() > 4 {
234            return Err(ParseError::InvalidFieldFormat {
235                field_tag: "59F".to_string(),
236                message: "Cannot exceed 4 name and address lines".to_string(),
237            });
238        }
239
240        for (i, line) in name_and_address.iter().enumerate() {
241            let trimmed = line.trim();
242            if trimmed.is_empty() {
243                return Err(ParseError::InvalidFieldFormat {
244                    field_tag: "59F".to_string(),
245                    message: format!("Name and address line {} cannot be empty", i + 1),
246                });
247            }
248
249            // Validate format: should be n/text where n is 1-4
250            if !trimmed.starts_with(char::is_numeric) || !trimmed.contains('/') {
251                return Err(ParseError::InvalidFieldFormat {
252                    field_tag: "59F".to_string(),
253                    message: format!("Name and address line {} must be in format 'n/text'", i + 1),
254                });
255            }
256
257            let parts: Vec<&str> = trimmed.splitn(2, '/').collect();
258            if parts.len() != 2 {
259                return Err(ParseError::InvalidFieldFormat {
260                    field_tag: "59F".to_string(),
261                    message: format!(
262                        "Name and address line {} must contain exactly one '/'",
263                        i + 1
264                    ),
265                });
266            }
267
268            let line_number: u8 = parts[0]
269                .parse()
270                .map_err(|_| ParseError::InvalidFieldFormat {
271                    field_tag: "59F".to_string(),
272                    message: format!(
273                        "Name and address line {} must start with a number 1-4",
274                        i + 1
275                    ),
276                })?;
277
278            if !(1..=4).contains(&line_number) {
279                return Err(ParseError::InvalidFieldFormat {
280                    field_tag: "59F".to_string(),
281                    message: format!(
282                        "Name and address line {} number must be between 1 and 4",
283                        i + 1
284                    ),
285                });
286            }
287
288            if parts[1].is_empty() {
289                return Err(ParseError::InvalidFieldFormat {
290                    field_tag: "59F".to_string(),
291                    message: format!("Name and address line {} content cannot be empty", i + 1),
292                });
293            }
294
295            if parts[1].len() > 33 {
296                return Err(ParseError::InvalidFieldFormat {
297                    field_tag: "59F".to_string(),
298                    message: format!(
299                        "Name and address line {} content cannot exceed 33 characters",
300                        i + 1
301                    ),
302                });
303            }
304
305            if !parts[1].chars().all(|c| c.is_ascii() && !c.is_control()) {
306                return Err(ParseError::InvalidFieldFormat {
307                    field_tag: "59F".to_string(),
308                    message: format!(
309                        "Name and address line {} contains invalid characters",
310                        i + 1
311                    ),
312                });
313            }
314        }
315
316        let trimmed_lines: Vec<String> = name_and_address
317            .iter()
318            .map(|line| line.trim().to_string())
319            .collect();
320
321        Ok(Field59F {
322            party_identifier,
323            name_and_address: trimmed_lines,
324        })
325    }
326
327    /// Get the party identifier
328    pub fn party_identifier(&self) -> &str {
329        &self.party_identifier
330    }
331
332    /// Get the name and address lines
333    pub fn name_and_address(&self) -> &[String] {
334        &self.name_and_address
335    }
336
337    /// Get a specific name/address line by line number (1-4)
338    pub fn name_address_line(&self, line_number: u8) -> Option<&str> {
339        self.name_and_address
340            .iter()
341            .find(|line| line.starts_with(&format!("{}/", line_number)))
342            .map(|line| line.split_once('/').map(|x| x.1).unwrap_or(""))
343    }
344
345    /// Get human-readable description
346    pub fn description(&self) -> String {
347        format!(
348            "Beneficiary Customer (Party ID: {}, Name/Address: {})",
349            self.party_identifier,
350            self.name_and_address.join(", ")
351        )
352    }
353}
354
355impl std::fmt::Display for Field59F {
356    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357        write!(
358            f,
359            "Party: {}, Name/Address: {}",
360            self.party_identifier,
361            self.name_and_address.join(" / ")
362        )
363    }
364}
365
366impl SwiftField for Field59F {
367    fn parse(content: &str) -> Result<Self, ParseError> {
368        let content = if let Some(stripped) = content.strip_prefix(":59F:") {
369            stripped
370        } else if let Some(stripped) = content.strip_prefix("59F:") {
371            stripped
372        } else {
373            content
374        };
375
376        let lines: Vec<&str> = content.lines().collect();
377        if lines.is_empty() {
378            return Err(ParseError::InvalidFieldFormat {
379                field_tag: "59F".to_string(),
380                message: "No content provided".to_string(),
381            });
382        }
383
384        let party_identifier = lines[0].to_string();
385        let name_and_address = lines[1..].iter().map(|s| s.to_string()).collect();
386
387        Field59F::new(party_identifier, name_and_address)
388    }
389
390    fn to_swift_string(&self) -> String {
391        let mut content = self.party_identifier.clone();
392        for line in &self.name_and_address {
393            content.push('\n');
394            content.push_str(line);
395        }
396        format!(":59F:{}", content)
397    }
398
399    fn validate(&self) -> ValidationResult {
400        // Validation is done in constructor
401        ValidationResult {
402            is_valid: true,
403            errors: Vec::new(),
404            warnings: Vec::new(),
405        }
406    }
407
408    fn format_spec() -> &'static str {
409        "party_identifier_and_name_address"
410    }
411}
412
413/// Field 59: Beneficiary Customer (Basic)
414///
415/// Format: 4*35x (up to 4 lines of 35 characters each)
416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
417pub struct Field59Basic {
418    /// Beneficiary customer information (up to 4 lines)
419    pub beneficiary_customer: Vec<String>,
420}
421
422impl Field59Basic {
423    /// Create a new Field59Basic with validation
424    pub fn new(beneficiary_customer: Vec<String>) -> Result<Self, ParseError> {
425        if beneficiary_customer.is_empty() {
426            return Err(ParseError::InvalidFieldFormat {
427                field_tag: "59".to_string(),
428                message: "Beneficiary customer information cannot be empty".to_string(),
429            });
430        }
431
432        if beneficiary_customer.len() > 4 {
433            return Err(ParseError::InvalidFieldFormat {
434                field_tag: "59".to_string(),
435                message: "Too many lines (max 4)".to_string(),
436            });
437        }
438
439        for (i, line) in beneficiary_customer.iter().enumerate() {
440            if line.len() > 35 {
441                return Err(ParseError::InvalidFieldFormat {
442                    field_tag: "59".to_string(),
443                    message: format!("Line {} too long (max 35 characters)", i + 1),
444                });
445            }
446        }
447
448        Ok(Field59Basic {
449            beneficiary_customer,
450        })
451    }
452
453    /// Get the beneficiary customer lines
454    pub fn beneficiary_customer(&self) -> &[String] {
455        &self.beneficiary_customer
456    }
457}
458
459/// Field 59: Beneficiary Customer (with options A, F, and no letter option)
460#[derive(Debug, Clone, PartialEq, Eq)]
461pub enum Field59 {
462    A(Field59A),
463    F(Field59F),
464    NoOption(Field59Basic),
465}
466
467impl Field59 {
468    /// Parse Field59 with a specific tag (59A, 59F, or 59)
469    pub fn parse_with_tag(tag: &str, content: &str) -> Result<Self, ParseError> {
470        match tag {
471            "59A" => Ok(Field59::A(Field59A::parse(content)?)),
472            "59F" => Ok(Field59::F(Field59F::parse(content)?)),
473            "59" => Ok(Field59::NoOption(Field59Basic::parse(content)?)),
474            _ => Err(ParseError::InvalidFieldFormat {
475                field_tag: "59".to_string(),
476                message: format!("Unknown Field59 option: {}", tag),
477            }),
478        }
479    }
480
481    /// Get the tag for this field variant
482    pub fn tag(&self) -> &'static str {
483        match self {
484            Field59::A(_) => "59A",
485            Field59::F(_) => "59F",
486            Field59::NoOption(_) => "59",
487        }
488    }
489}
490
491impl std::fmt::Display for Field59 {
492    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493        match self {
494            Field59::A(field) => write!(f, "59A: {}", field),
495            Field59::F(field) => write!(f, "59F: {}", field),
496            Field59::NoOption(field) => {
497                write!(f, "59: {}", field.beneficiary_customer.join(", "))
498            }
499        }
500    }
501}
502
503// Custom serialization for Field59 to flatten the JSON structure
504impl Serialize for Field59 {
505    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
506    where
507        S: Serializer,
508    {
509        match self {
510            Field59::A(field) => field.serialize(serializer),
511            Field59::F(field) => field.serialize(serializer),
512            Field59::NoOption(field) => field.serialize(serializer),
513        }
514    }
515}
516
517// Custom deserialization for Field59
518impl<'de> Deserialize<'de> for Field59 {
519    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
520    where
521        D: Deserializer<'de>,
522    {
523        struct Field59Visitor;
524
525        impl<'de> Visitor<'de> for Field59Visitor {
526            type Value = Field59;
527
528            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
529                formatter.write_str("a Field59 variant")
530            }
531
532            fn visit_map<V>(self, mut map: V) -> Result<Field59, V::Error>
533            where
534                V: MapAccess<'de>,
535            {
536                let mut fields = std::collections::HashMap::new();
537
538                while let Some((key, value)) = map.next_entry::<String, serde_json::Value>()? {
539                    fields.insert(key, value);
540                }
541
542                // Try to determine the variant based on the fields present
543                if fields.contains_key("bic") {
544                    // Field59A variant
545                    let account = fields
546                        .get("account")
547                        .and_then(|v| v.as_str())
548                        .map(|s| s.to_string());
549                    let bic = fields
550                        .get("bic")
551                        .and_then(|v| v.as_str())
552                        .ok_or_else(|| de::Error::missing_field("bic"))?
553                        .to_string();
554
555                    Field59A::new(account, bic)
556                        .map(Field59::A)
557                        .map_err(|e| de::Error::custom(format!("Field59A validation error: {}", e)))
558                } else if fields.contains_key("party_identifier")
559                    && fields.contains_key("name_and_address")
560                {
561                    // Field59F variant
562                    let party_identifier = fields
563                        .get("party_identifier")
564                        .and_then(|v| v.as_str())
565                        .ok_or_else(|| de::Error::missing_field("party_identifier"))?
566                        .to_string();
567                    let name_and_address = fields
568                        .get("name_and_address")
569                        .and_then(|v| v.as_array())
570                        .ok_or_else(|| de::Error::missing_field("name_and_address"))?
571                        .iter()
572                        .map(|v| v.as_str().unwrap_or("").to_string())
573                        .collect();
574
575                    Field59F::new(party_identifier, name_and_address)
576                        .map(Field59::F)
577                        .map_err(|e| de::Error::custom(format!("Field59F validation error: {}", e)))
578                } else if fields.contains_key("beneficiary_customer") {
579                    // Field59Basic variant
580                    let beneficiary_customer = fields
581                        .get("beneficiary_customer")
582                        .and_then(|v| v.as_array())
583                        .ok_or_else(|| de::Error::missing_field("beneficiary_customer"))?
584                        .iter()
585                        .map(|v| v.as_str().unwrap_or("").to_string())
586                        .collect();
587
588                    Field59Basic::new(beneficiary_customer)
589                        .map(Field59::NoOption)
590                        .map_err(|e| {
591                            de::Error::custom(format!("Field59Basic validation error: {}", e))
592                        })
593                } else {
594                    Err(de::Error::custom("Unable to determine Field59 variant"))
595                }
596            }
597        }
598
599        deserializer.deserialize_map(Field59Visitor)
600    }
601}
602
603impl SwiftField for Field59A {
604    fn parse(content: &str) -> Result<Self, ParseError> {
605        let content = if let Some(stripped) = content.strip_prefix(":59A:") {
606            stripped
607        } else if let Some(stripped) = content.strip_prefix("59A:") {
608            stripped
609        } else {
610            content
611        };
612
613        let mut lines = content.lines();
614        let first_line = lines.next().unwrap_or_default();
615
616        let (account, bic_line) = if let Some(stripped) = first_line.strip_prefix('/') {
617            (Some(stripped.to_string()), lines.next().unwrap_or_default())
618        } else {
619            (None, first_line)
620        };
621
622        Field59A::new(account, bic_line)
623    }
624
625    fn to_swift_string(&self) -> String {
626        match &self.account {
627            Some(account) => format!(":59A:/{}\n{}", account, self.bic.value()),
628            None => format!(":59A:{}", self.bic.value()),
629        }
630    }
631
632    fn validate(&self) -> ValidationResult {
633        use crate::errors::ValidationError;
634
635        let mut errors = Vec::new();
636
637        // Validate BIC format using the common BIC validation
638        let bic_validation = self.bic.validate();
639        if !bic_validation.is_valid {
640            errors.extend(bic_validation.errors);
641        }
642
643        // Validate account length if present
644        if let Some(ref account) = self.account {
645            if account.len() > 34 {
646                errors.push(ValidationError::FormatValidation {
647                    field_tag: "59A".to_string(),
648                    message: "Account number too long (max 34 characters)".to_string(),
649                });
650            }
651        }
652
653        ValidationResult {
654            is_valid: errors.is_empty(),
655            errors,
656            warnings: vec![],
657        }
658    }
659
660    fn format_spec() -> &'static str {
661        "[/account]bic"
662    }
663}
664
665impl SwiftField for Field59Basic {
666    fn parse(content: &str) -> Result<Self, ParseError> {
667        let content = if let Some(stripped) = content.strip_prefix(":59:") {
668            stripped
669        } else if let Some(stripped) = content.strip_prefix("59:") {
670            stripped
671        } else {
672            content
673        };
674
675        let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
676
677        Field59Basic::new(lines)
678    }
679
680    fn to_swift_string(&self) -> String {
681        format!(":59:{}", self.beneficiary_customer.join("\n"))
682    }
683
684    fn validate(&self) -> ValidationResult {
685        use crate::errors::ValidationError;
686
687        let mut errors = Vec::new();
688
689        if self.beneficiary_customer.is_empty() {
690            errors.push(ValidationError::FormatValidation {
691                field_tag: "59".to_string(),
692                message: "Beneficiary customer information cannot be empty".to_string(),
693            });
694        }
695
696        if self.beneficiary_customer.len() > 4 {
697            errors.push(ValidationError::FormatValidation {
698                field_tag: "59".to_string(),
699                message: "Too many lines (max 4)".to_string(),
700            });
701        }
702
703        for (i, line) in self.beneficiary_customer.iter().enumerate() {
704            if line.len() > 35 {
705                errors.push(ValidationError::FormatValidation {
706                    field_tag: "59".to_string(),
707                    message: format!("Line {} too long (max 35 characters)", i + 1),
708                });
709            }
710        }
711
712        ValidationResult {
713            is_valid: errors.is_empty(),
714            errors,
715            warnings: vec![],
716        }
717    }
718
719    fn format_spec() -> &'static str {
720        "4*35x"
721    }
722}
723
724impl SwiftField for Field59 {
725    fn parse(input: &str) -> Result<Self, ParseError> {
726        // Try to determine the variant from the input
727        if input.starts_with(":59A:") || input.starts_with("59A:") {
728            Ok(Field59::A(Field59A::parse(input)?))
729        } else if input.starts_with(":59F:") || input.starts_with("59F:") {
730            Ok(Field59::F(Field59F::parse(input)?))
731        } else if input.starts_with(":59:") || input.starts_with("59:") {
732            Ok(Field59::NoOption(Field59Basic::parse(input)?))
733        } else {
734            // Default to NoOption if no clear indicator
735            Ok(Field59::NoOption(Field59Basic::parse(input)?))
736        }
737    }
738
739    fn to_swift_string(&self) -> String {
740        match self {
741            Field59::A(field) => field.to_swift_string(),
742            Field59::F(field) => field.to_swift_string(),
743            Field59::NoOption(field) => field.to_swift_string(),
744        }
745    }
746
747    fn validate(&self) -> ValidationResult {
748        match self {
749            Field59::A(field) => field.validate(),
750            Field59::F(field) => field.validate(),
751            Field59::NoOption(field) => field.validate(),
752        }
753    }
754
755    fn format_spec() -> &'static str {
756        "beneficiary_customer"
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763
764    #[test]
765    fn test_field59a_creation() {
766        let field = Field59A::new(Some("123456789".to_string()), "DEUTDEFFXXX").unwrap();
767        assert_eq!(field.account(), Some("123456789"));
768        assert_eq!(field.bic(), "DEUTDEFFXXX");
769        assert_eq!(field.to_swift_string(), ":59A:/123456789\nDEUTDEFFXXX");
770    }
771
772    #[test]
773    fn test_field59a_without_account() {
774        let field = Field59A::new(None, "DEUTDEFFXXX").unwrap();
775        assert_eq!(field.account(), None);
776        assert_eq!(field.bic(), "DEUTDEFFXXX");
777        assert_eq!(field.to_swift_string(), ":59A:DEUTDEFFXXX");
778    }
779
780    #[test]
781    fn test_field59a_parse() {
782        let field = Field59A::parse("/123456789\nDEUTDEFFXXX").unwrap();
783        assert_eq!(field.account(), Some("123456789"));
784        assert_eq!(field.bic(), "DEUTDEFFXXX");
785
786        let field = Field59A::parse("DEUTDEFFXXX").unwrap();
787        assert_eq!(field.account(), None);
788        assert_eq!(field.bic(), "DEUTDEFFXXX");
789    }
790
791    #[test]
792    fn test_field59a_invalid_bic() {
793        let result = Field59A::new(None, "INVALID"); // Too short
794        assert!(result.is_err());
795
796        let result = Field59A::new(None, "TOOLONGBICCODE"); // Too long
797        assert!(result.is_err());
798    }
799
800    #[test]
801    fn test_field59_basic_creation() {
802        let field = Field59Basic::new(vec![
803            "MUELLER GMBH".to_string(),
804            "HAUPTSTRASSE 1".to_string(),
805        ])
806        .unwrap();
807        assert_eq!(
808            field.beneficiary_customer(),
809            &["MUELLER GMBH", "HAUPTSTRASSE 1"]
810        );
811    }
812
813    #[test]
814    fn test_field59_basic_parse() {
815        let field = Field59Basic::parse("MUELLER GMBH\nHAUPTSTRASSE 1").unwrap();
816        assert_eq!(
817            field.beneficiary_customer(),
818            &["MUELLER GMBH", "HAUPTSTRASSE 1"]
819        );
820    }
821
822    #[test]
823    fn test_field59_basic_too_many_lines() {
824        let result = Field59Basic::new(vec![
825            "LINE1".to_string(),
826            "LINE2".to_string(),
827            "LINE3".to_string(),
828            "LINE4".to_string(),
829            "LINE5".to_string(), // Too many
830        ]);
831        assert!(result.is_err());
832    }
833
834    #[test]
835    fn test_field59_enum_parse() {
836        let field = Field59::parse(":59A:DEUTDEFFXXX").unwrap();
837        assert!(matches!(field, Field59::A(_)));
838
839        let field = Field59::parse(":59:MUELLER GMBH\nHAUPTSTRASSE 1").unwrap();
840        assert!(matches!(field, Field59::NoOption(_)));
841    }
842
843    #[test]
844    fn test_field59_parse_with_tag() {
845        let field = Field59::parse_with_tag("59A", "DEUTDEFFXXX").unwrap();
846        assert!(matches!(field, Field59::A(_)));
847
848        let field = Field59::parse_with_tag("59", "MUELLER GMBH\nHAUPTSTRASSE 1").unwrap();
849        assert!(matches!(field, Field59::NoOption(_)));
850    }
851
852    #[test]
853    fn test_field59_tag() {
854        let field = Field59::A(Field59A::new(None, "DEUTDEFFXXX").unwrap());
855        assert_eq!(field.tag(), "59A");
856
857        let field = Field59::NoOption(Field59Basic::new(vec!["MUELLER GMBH".to_string()]).unwrap());
858        assert_eq!(field.tag(), "59");
859    }
860
861    #[test]
862    fn test_field59_validation() {
863        let field = Field59A::new(None, "DEUTDEFF").unwrap();
864        let result = field.validate();
865        assert!(result.is_valid);
866
867        let field = Field59Basic::new(vec!["MUELLER GMBH".to_string()]).unwrap();
868        let result = field.validate();
869        assert!(result.is_valid);
870    }
871
872    #[test]
873    fn test_field59_display() {
874        let field = Field59A::new(Some("123456".to_string()), "DEUTDEFF").unwrap();
875        assert_eq!(format!("{}", field), "Account: 123456, BIC: DEUTDEFF");
876
877        let field = Field59A::new(None, "DEUTDEFF").unwrap();
878        assert_eq!(format!("{}", field), "BIC: DEUTDEFF");
879
880        let field =
881            Field59Basic::new(vec!["MUELLER GMBH".to_string(), "BERLIN".to_string()]).unwrap();
882        let enum_field = Field59::NoOption(field);
883        assert_eq!(format!("{}", enum_field), "59: MUELLER GMBH, BERLIN");
884    }
885
886    #[test]
887    fn test_field59_json_serialization_flattened() {
888        // Test Field59A
889        let field59a = Field59::A(
890            Field59A::new(Some("DE89370400440532013000".to_string()), "DEUTDEFF").unwrap(),
891        );
892
893        let json = serde_json::to_string(&field59a).unwrap();
894        println!("Field59A JSON: {}", json);
895
896        // Should be flattened - no "A" wrapper
897        assert!(json.contains("\"account\""));
898        assert!(json.contains("\"bic\""));
899        assert!(!json.contains("\"A\""));
900
901        // Test Field59Basic (NoOption)
902        let field59_basic = Field59::NoOption(
903            Field59Basic::new(vec![
904                "MUELLER GMBH".to_string(),
905                "HAUPTSTRASSE 1".to_string(),
906            ])
907            .unwrap(),
908        );
909
910        let json = serde_json::to_string(&field59_basic).unwrap();
911        println!("Field59Basic JSON: {}", json);
912
913        // Should be flattened - no "NoOption" wrapper
914        assert!(json.contains("\"beneficiary_customer\""));
915        assert!(!json.contains("\"NoOption\""));
916    }
917
918    #[test]
919    fn test_field59_json_deserialization() {
920        // Test deserializing Field59A
921        let json = r#"{"account":"DE89370400440532013000","bic":"DEUTDEFF"}"#;
922        let field: Field59 = serde_json::from_str(json).unwrap();
923        assert!(matches!(field, Field59::A(_)));
924
925        // Test deserializing Field59F
926        let json = r#"{"party_identifier":"PARTYID123","name_and_address":["1/JOHN DOE","2/123 MAIN ST"]}"#;
927        let field: Field59 = serde_json::from_str(json).unwrap();
928        assert!(matches!(field, Field59::F(_)));
929
930        // Test deserializing Field59Basic
931        let json = r#"{"beneficiary_customer":["MUELLER GMBH","HAUPTSTRASSE 1"]}"#;
932        let field: Field59 = serde_json::from_str(json).unwrap();
933        assert!(matches!(field, Field59::NoOption(_)));
934    }
935
936    #[test]
937    fn test_field59f_creation() {
938        let field = Field59F::new(
939            "PARTYID123",
940            vec!["1/JOHN DOE".to_string(), "2/123 MAIN ST".to_string()],
941        )
942        .unwrap();
943        assert_eq!(field.party_identifier(), "PARTYID123");
944        assert_eq!(field.name_and_address(), &["1/JOHN DOE", "2/123 MAIN ST"]);
945        assert_eq!(field.name_address_line(1), Some("JOHN DOE"));
946        assert_eq!(field.name_address_line(2), Some("123 MAIN ST"));
947        assert_eq!(field.name_address_line(3), None);
948    }
949
950    #[test]
951    fn test_field59f_parse() {
952        let field = Field59F::parse("PARTYID123\n1/JOHN DOE\n2/123 MAIN ST").unwrap();
953        assert_eq!(field.party_identifier(), "PARTYID123");
954        assert_eq!(field.name_and_address(), &["1/JOHN DOE", "2/123 MAIN ST"]);
955    }
956
957    #[test]
958    fn test_field59f_to_swift_string() {
959        let field = Field59F::new(
960            "PARTYID123",
961            vec!["1/JOHN DOE".to_string(), "2/123 MAIN ST".to_string()],
962        )
963        .unwrap();
964        assert_eq!(
965            field.to_swift_string(),
966            ":59F:PARTYID123\n1/JOHN DOE\n2/123 MAIN ST"
967        );
968    }
969
970    #[test]
971    fn test_field59f_validation_errors() {
972        // Empty party identifier
973        let result = Field59F::new("", vec!["1/JOHN DOE".to_string()]);
974        assert!(result.is_err());
975
976        // Party identifier too long
977        let result = Field59F::new("A".repeat(36), vec!["1/JOHN DOE".to_string()]);
978        assert!(result.is_err());
979
980        // Empty name and address
981        let result = Field59F::new("PARTYID123", vec![]);
982        assert!(result.is_err());
983
984        // Too many name and address lines
985        let result = Field59F::new(
986            "PARTYID123",
987            vec![
988                "1/JOHN DOE".to_string(),
989                "2/123 MAIN ST".to_string(),
990                "3/CITY".to_string(),
991                "4/COUNTRY".to_string(),
992                "5/EXTRA".to_string(), // Too many
993            ],
994        );
995        assert!(result.is_err());
996
997        // Invalid name and address format
998        let result = Field59F::new("PARTYID123", vec!["INVALID FORMAT".to_string()]);
999        assert!(result.is_err());
1000
1001        // Invalid line number
1002        let result = Field59F::new("PARTYID123", vec!["5/INVALID LINE NUMBER".to_string()]);
1003        assert!(result.is_err());
1004
1005        // Name and address content too long
1006        let result = Field59F::new(
1007            "PARTYID123",
1008            vec![format!("1/{}", "A".repeat(34))], // 34 chars is too long (max 33)
1009        );
1010        assert!(result.is_err());
1011    }
1012
1013    #[test]
1014    fn test_field59f_display() {
1015        let field = Field59F::new(
1016            "PARTYID123",
1017            vec!["1/JOHN DOE".to_string(), "2/123 MAIN ST".to_string()],
1018        )
1019        .unwrap();
1020        assert_eq!(
1021            format!("{}", field),
1022            "Party: PARTYID123, Name/Address: 1/JOHN DOE / 2/123 MAIN ST"
1023        );
1024    }
1025
1026    #[test]
1027    fn test_field59f_description() {
1028        let field = Field59F::new(
1029            "PARTYID123",
1030            vec!["1/JOHN DOE".to_string(), "2/123 MAIN ST".to_string()],
1031        )
1032        .unwrap();
1033        assert_eq!(
1034            field.description(),
1035            "Beneficiary Customer (Party ID: PARTYID123, Name/Address: 1/JOHN DOE, 2/123 MAIN ST)"
1036        );
1037    }
1038
1039    #[test]
1040    fn test_field59_enum_with_f_variant() {
1041        let field = Field59::F(
1042            Field59F::new(
1043                "PARTYID123",
1044                vec!["1/JOHN DOE".to_string(), "2/123 MAIN ST".to_string()],
1045            )
1046            .unwrap(),
1047        );
1048        assert_eq!(field.tag(), "59F");
1049        assert_eq!(
1050            format!("{}", field),
1051            "59F: Party: PARTYID123, Name/Address: 1/JOHN DOE / 2/123 MAIN ST"
1052        );
1053    }
1054
1055    #[test]
1056    fn test_field59_parse_with_tag_f() {
1057        let field =
1058            Field59::parse_with_tag("59F", "PARTYID123\n1/JOHN DOE\n2/123 MAIN ST").unwrap();
1059        assert!(matches!(field, Field59::F(_)));
1060        assert_eq!(field.tag(), "59F");
1061    }
1062
1063    #[test]
1064    fn test_field59_parse_f_variant() {
1065        let field = Field59::parse(":59F:PARTYID123\n1/JOHN DOE\n2/123 MAIN ST").unwrap();
1066        assert!(matches!(field, Field59::F(_)));
1067        assert_eq!(
1068            field.to_swift_string(),
1069            ":59F:PARTYID123\n1/JOHN DOE\n2/123 MAIN ST"
1070        );
1071    }
1072
1073    #[test]
1074    fn test_field59f_json_serialization() {
1075        let field = Field59::F(
1076            Field59F::new(
1077                "PARTYID123",
1078                vec!["1/JOHN DOE".to_string(), "2/123 MAIN ST".to_string()],
1079            )
1080            .unwrap(),
1081        );
1082
1083        let json = serde_json::to_string(&field).unwrap();
1084        println!("Field59F JSON: {}", json);
1085
1086        // Should be flattened - no "F" wrapper
1087        assert!(json.contains("\"party_identifier\""));
1088        assert!(json.contains("\"name_and_address\""));
1089        assert!(!json.contains("\"F\""));
1090    }
1091}