swift_mt_message/fields/
field21.rs

1use crate::SwiftField;
2use serde::{Deserialize, Serialize};
3
4/// # Field 21: Related Reference
5///
6/// ## Overview
7/// Field 21 contains the sender's reference to the related transaction or message.
8/// This field establishes a link between the current message and a previously sent
9/// message or transaction, enabling tracking of related operations and maintaining
10/// audit trails across multiple message exchanges in correspondent banking and
11/// payment processing workflows.
12///
13/// ## Format Specification
14/// **Format**: `16x`
15/// - **16x**: Up to 16 alphanumeric characters
16/// - **Character set**: A-Z, a-z, 0-9, and limited special characters
17/// - **Case sensitivity**: Preserved as entered
18/// - **Padding**: No padding required (variable length up to 16 characters)
19///
20/// ## Structure and Content
21/// The related reference typically links to:
22/// ```text
23/// FT21034567890123
24/// │└─────────────┘
25/// │  Related transaction reference
26/// └── Transaction type prefix (optional)
27/// ```
28///
29/// ## Common Reference Patterns
30/// Different institutions use various patterns for related references:
31/// - **Sequential linking**: `REL000001`, `REL000002`, `REL000003`
32/// - **Original reference**: Same as Field 20 from original message
33/// - **Cover references**: `COV001234567`, `COV987654321`
34/// - **Amendment references**: `AMD1234567890`, `AMD0987654321`
35/// - **Cancellation references**: `CAN12345678`, `CAN87654321`
36///
37/// ## Usage Context
38/// Field 21 is used in numerous SWIFT MT message types:
39/// - **MT202**: General Financial Institution Transfer
40/// - **MT202COV**: Cover for customer credit transfer
41/// - **MT205**: Financial Institution Transfer for its own account
42/// - **MT210**: Notice to Receive
43/// - **MT292**: Request for Cancellation
44/// - **MT296**: Answer to Amendment/Cancellation Request
45///
46/// ### Business Applications
47/// - **Transaction linking**: Connecting related messages and transactions
48/// - **Cover operations**: Linking cover messages to original customer transfers
49/// - **Amendment tracking**: Referencing original messages in amendments
50/// - **Cancellation requests**: Identifying transactions to be cancelled
51/// - **Reconciliation**: Matching related transactions across systems
52/// - **Audit trails**: Maintaining complete transaction history chains
53/// - **Regulatory reporting**: Providing transaction relationship information
54///
55/// ## Validation Rules
56/// 1. **Length**: Maximum 16 characters
57/// 2. **Character set**: Alphanumeric characters and limited special characters
58/// 3. **Non-empty**: Cannot be empty or contain only whitespace
59/// 4. **Format consistency**: Should follow institutional standards
60/// 5. **Relationship validity**: Should reference valid related transactions
61///
62/// ## Network Validated Rules (SWIFT Standards)
63/// - Related reference must not exceed 16 characters (Error: T13)
64/// - Must contain valid SWIFT character set (Error: T61)
65/// - Cannot be empty (Error: T18)
66/// - Should reference existing transaction when applicable (Warning: recommended practice)
67///
68/// ## Examples
69/// ```text
70/// :21:FT21234567890
71/// └─── Related wire transfer reference
72///
73/// :21:COV0000012345
74/// └─── Cover message reference
75///
76/// :21:NYC20241201001
77/// └─── Branch and date-based related reference
78///
79/// :21:AMD123456789A
80/// └─── Amendment reference with check digit
81/// ```
82
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, SwiftField)]
84#[format("16x")]
85pub struct Field21 {
86    /// Related reference value (maximum 16 characters)
87    ///
88    /// Contains the sender's reference to the related transaction or message.
89    /// This value establishes the link between the current message and a
90    /// previously sent message, enabling transaction tracking and audit trails.
91    ///
92    /// **Format**: Up to 16 alphanumeric characters
93    /// **Character set**: A-Z, a-z, 0-9, and limited special characters
94    /// **Case sensitivity**: Preserved as entered
95    /// **Purpose**: Links current message to related transaction or message
96    ///
97    /// # Examples
98    /// - `"FT21234567890"` - Related wire transfer reference
99    /// - `"COV0000012345"` - Cover message reference
100    /// - `"AMD20241201001"` - Amendment reference
101    /// - `"CAN123456789A"` - Cancellation reference with check digit
102    #[format("16x")]
103    pub related_reference: String,
104}
105
106impl Field21 {
107    /// Create a new Field21 with comprehensive validation
108    ///
109    /// Creates a new related reference field with the provided reference string.
110    /// The reference is validated for length and character set compliance.
111    ///
112    /// # Arguments
113    /// * `related_reference` - The related reference string (max 16 chars)
114    ///
115    /// # Examples
116    /// ```rust
117    /// use swift_mt_message::fields::Field21;
118    ///
119    /// // Standard related reference
120    /// let field = Field21::new("FT21234567890".to_string());
121    ///
122    /// // Cover message reference
123    /// let field = Field21::new("COV0000012345".to_string());
124    ///
125    /// // Amendment reference
126    /// let field = Field21::new("AMD20241201001".to_string());
127    /// ```
128    ///
129    /// # Validation
130    /// The constructor performs basic validation but full validation
131    /// occurs when calling `validate()` method or during SWIFT message processing.
132    pub fn new(related_reference: String) -> Self {
133        Self { related_reference }
134    }
135
136    /// Get the related reference value
137    ///
138    /// Returns the related reference string that links this message
139    /// to a previously sent message or transaction.
140    ///
141    /// # Returns
142    /// A string slice containing the related reference
143    ///
144    /// # Example
145    /// ```rust
146    /// # use swift_mt_message::fields::Field21;
147    /// let field = Field21::new("FT21234567890".to_string());
148    /// assert_eq!(field.related_reference(), "FT21234567890");
149    /// ```
150    pub fn related_reference(&self) -> &str {
151        &self.related_reference
152    }
153
154    /// Check if the reference follows a common pattern
155    ///
156    /// Analyzes the related reference to determine if it follows
157    /// common institutional patterns for reference generation.
158    ///
159    /// # Returns
160    /// A string describing the detected pattern, or "Custom" if no pattern is detected
161    ///
162    /// # Example
163    /// ```rust
164    /// # use swift_mt_message::fields::Field21;
165    /// let cover_ref = Field21::new("COV21234567890".to_string());
166    /// assert_eq!(cover_ref.reference_pattern(), "Cover Message");
167    ///
168    /// let amd_ref = Field21::new("AMD0000012345".to_string());
169    /// assert_eq!(amd_ref.reference_pattern(), "Amendment");
170    /// ```
171    pub fn reference_pattern(&self) -> &'static str {
172        let ref_upper = self.related_reference.to_uppercase();
173
174        if ref_upper.starts_with("COV") {
175            "Cover Message"
176        } else if ref_upper.starts_with("AMD") {
177            "Amendment"
178        } else if ref_upper.starts_with("CAN") {
179            "Cancellation"
180        } else if ref_upper.starts_with("REL") {
181            "Related Transaction"
182        } else if ref_upper.starts_with("FT") {
183            "Wire Transfer"
184        } else if ref_upper.starts_with("TXN") {
185            "Transaction ID"
186        } else if ref_upper.starts_with("REF") {
187            "Reference ID"
188        } else if self.is_date_based() {
189            "Date-based"
190        } else if ref_upper.chars().all(|c| c.is_ascii_digit()) {
191            "Numeric Sequential"
192        } else {
193            "Custom"
194        }
195    }
196
197    /// Check if the reference appears to be date-based
198    ///
199    /// Determines if the related reference contains date information
200    /// based on common date patterns in reference strings.
201    ///
202    /// # Returns
203    /// `true` if the reference appears to contain date information
204    ///
205    /// # Example
206    /// ```rust
207    /// # use swift_mt_message::fields::Field21;
208    /// let date_ref = Field21::new("20241201001".to_string());
209    /// assert!(date_ref.is_date_based());
210    ///
211    /// let simple_ref = Field21::new("COV12345".to_string());
212    /// assert!(!simple_ref.is_date_based());
213    /// ```
214    pub fn is_date_based(&self) -> bool {
215        let ref_str = &self.related_reference;
216
217        // Check for YYYYMMDD pattern (8 digits at start)
218        if ref_str.len() >= 8 {
219            let date_part = &ref_str[0..8];
220            if date_part.chars().all(|c| c.is_ascii_digit()) {
221                // Basic date validation (year 2000-2099, month 01-12, day 01-31)
222                if let (Ok(year), Ok(month), Ok(day)) = (
223                    date_part[0..4].parse::<u32>(),
224                    date_part[4..6].parse::<u32>(),
225                    date_part[6..8].parse::<u32>(),
226                ) {
227                    if (2000..=2099).contains(&year)
228                        && (1..=12).contains(&month)
229                        && (1..=31).contains(&day)
230                    {
231                        return true;
232                    }
233                }
234            }
235        }
236
237        // Check for YYYYMMDD pattern embedded in the string (after prefix)
238        if ref_str.len() >= 8 {
239            for i in 0..=ref_str.len() - 8 {
240                let date_part = &ref_str[i..i + 8];
241                if date_part.chars().all(|c| c.is_ascii_digit()) {
242                    // Basic date validation (year 2000-2099, month 01-12, day 01-31)
243                    if let (Ok(year), Ok(month), Ok(day)) = (
244                        date_part[0..4].parse::<u32>(),
245                        date_part[4..6].parse::<u32>(),
246                        date_part[6..8].parse::<u32>(),
247                    ) {
248                        if (2000..=2099).contains(&year)
249                            && (1..=12).contains(&month)
250                            && (1..=31).contains(&day)
251                        {
252                            return true;
253                        }
254                    }
255                }
256            }
257        }
258
259        false
260    }
261
262    /// Extract potential sequence number from reference
263    ///
264    /// Attempts to extract a sequence number from the related reference,
265    /// which is commonly found at the end of structured references.
266    ///
267    /// # Returns
268    /// `Some(number)` if a sequence number is found, `None` otherwise
269    ///
270    /// # Example
271    /// ```rust
272    /// # use swift_mt_message::fields::Field21;
273    /// let seq_ref1 = Field21::new("COV001".to_string());
274    /// assert_eq!(seq_ref1.sequence_number(), Some(1));
275    ///
276    /// let seq_ref2 = Field21::new("AMD123456789".to_string());
277    /// assert_eq!(seq_ref2.sequence_number(), Some(123456789));
278    ///
279    /// let seq_ref3 = Field21::new("CAN000000042".to_string());
280    /// assert_eq!(seq_ref3.sequence_number(), Some(42));
281    ///
282    /// let no_seq1 = Field21::new("CUSTOMREF".to_string());
283    /// assert_eq!(no_seq1.sequence_number(), None);
284    ///
285    /// let no_seq2 = Field21::new("COV_NO_NUM".to_string());
286    /// assert_eq!(no_seq2.sequence_number(), None);
287    ///
288    /// let all_num = Field21::new("123456789".to_string());
289    /// assert_eq!(all_num.sequence_number(), Some(123456789));
290    ///
291    /// let too_long = Field21::new("AMD12345678901".to_string());
292    /// assert_eq!(too_long.sequence_number(), None);
293    /// ```
294    pub fn sequence_number(&self) -> Option<u32> {
295        let ref_str = &self.related_reference;
296
297        // Find the longest numeric suffix
298        let mut numeric_suffix = String::new();
299        for ch in ref_str.chars().rev() {
300            if ch.is_ascii_digit() {
301                numeric_suffix.insert(0, ch);
302            } else {
303                break;
304            }
305        }
306
307        if numeric_suffix.is_empty() {
308            return None;
309        }
310
311        // If the numeric suffix is too long, it might contain a date pattern
312        // Try to extract just the sequence part after the date
313        if numeric_suffix.len() > 10 {
314            // Check if this looks like a date followed by a sequence number
315            // Common patterns: YYYYMMDD + sequence (8 + up to 3 digits)
316            if numeric_suffix.len() >= 8 {
317                let potential_date = &numeric_suffix[0..8];
318                let potential_seq = &numeric_suffix[8..];
319
320                // Validate if the first 8 digits could be a date
321                if let (Ok(year), Ok(month), Ok(day)) = (
322                    potential_date[0..4].parse::<u32>(),
323                    potential_date[4..6].parse::<u32>(),
324                    potential_date[6..8].parse::<u32>(),
325                ) {
326                    if (2000..=2099).contains(&year)
327                        && (1..=12).contains(&month)
328                        && (1..=31).contains(&day)
329                        && !potential_seq.is_empty()
330                        && potential_seq.len() <= 10
331                    {
332                        return potential_seq.parse().ok();
333                    }
334                }
335            }
336            return None;
337        }
338
339        // Normal case: numeric suffix is reasonable length
340        if numeric_suffix.len() <= 10 {
341            numeric_suffix.parse().ok()
342        } else {
343            None
344        }
345    }
346
347    /// Check if this is a cover message reference
348    ///
349    /// Determines if the related reference indicates this is related
350    /// to a cover message operation.
351    ///
352    /// # Returns
353    /// `true` if the reference appears to be for a cover message
354    ///
355    /// # Example
356    /// ```rust
357    /// # use swift_mt_message::fields::Field21;
358    /// let cover_ref = Field21::new("COV21234567890".to_string());
359    /// assert!(cover_ref.is_cover_reference());
360    ///
361    /// let regular_ref = Field21::new("FT12345".to_string());
362    /// assert!(!regular_ref.is_cover_reference());
363    /// ```
364    pub fn is_cover_reference(&self) -> bool {
365        self.related_reference.to_uppercase().starts_with("COV")
366    }
367
368    /// Check if this is an amendment reference
369    ///
370    /// Determines if the related reference indicates this is related
371    /// to an amendment operation.
372    ///
373    /// # Returns
374    /// `true` if the reference appears to be for an amendment
375    ///
376    /// # Example
377    /// ```rust
378    /// # use swift_mt_message::fields::Field21;
379    /// let amd_ref = Field21::new("AMD21234567890".to_string());
380    /// assert!(amd_ref.is_amendment_reference());
381    ///
382    /// let regular_ref = Field21::new("FT12345".to_string());
383    /// assert!(!regular_ref.is_amendment_reference());
384    /// ```
385    pub fn is_amendment_reference(&self) -> bool {
386        self.related_reference.to_uppercase().starts_with("AMD")
387    }
388
389    /// Check if this is a cancellation reference
390    ///
391    /// Determines if the related reference indicates this is related
392    /// to a cancellation operation.
393    ///
394    /// # Returns
395    /// `true` if the reference appears to be for a cancellation
396    ///
397    /// # Example
398    /// ```rust
399    /// # use swift_mt_message::fields::Field21;
400    /// let can_ref = Field21::new("CAN21234567890".to_string());
401    /// assert!(can_ref.is_cancellation_reference());
402    ///
403    /// let regular_ref = Field21::new("FT12345".to_string());
404    /// assert!(!regular_ref.is_cancellation_reference());
405    /// ```
406    pub fn is_cancellation_reference(&self) -> bool {
407        self.related_reference.to_uppercase().starts_with("CAN")
408    }
409
410    /// Get a human-readable description of the related reference
411    ///
412    /// Returns a descriptive string explaining the related reference
413    /// format and detected patterns.
414    ///
415    /// # Returns
416    /// A descriptive string
417    ///
418    /// # Example
419    /// ```rust
420    /// # use swift_mt_message::fields::Field21;
421    /// let field = Field21::new("COV21234567890".to_string());
422    /// println!("{}", field.description());
423    /// ```
424    pub fn description(&self) -> String {
425        let pattern = self.reference_pattern();
426        let length = self.related_reference.len();
427
428        let mut desc = format!(
429            "Related Reference: {} (Pattern: {}, Length: {})",
430            self.related_reference, pattern, length
431        );
432
433        if self.is_cover_reference() {
434            desc.push_str(", Cover Message");
435        } else if self.is_amendment_reference() {
436            desc.push_str(", Amendment");
437        } else if self.is_cancellation_reference() {
438            desc.push_str(", Cancellation");
439        }
440
441        if self.is_date_based() {
442            desc.push_str(", Date-based");
443        }
444
445        if let Some(seq) = self.sequence_number() {
446            desc.push_str(&format!(", Sequence: {}", seq));
447        }
448
449        desc
450    }
451
452    /// Validate reference format according to institutional standards
453    ///
454    /// Performs additional validation beyond the standard SWIFT field validation,
455    /// checking for common institutional reference format requirements.
456    ///
457    /// # Returns
458    /// `true` if the reference follows good practices, `false` otherwise
459    ///
460    /// # Example
461    /// ```rust
462    /// # use swift_mt_message::fields::Field21;
463    /// let good_ref1 = Field21::new("COV21234567890".to_string());
464    /// assert!(good_ref1.is_well_formed());
465    ///
466    /// let good_ref2 = Field21::new("AMD-123456".to_string());
467    /// assert!(good_ref2.is_well_formed());
468    ///
469    /// let good_ref3 = Field21::new("REL_001.234".to_string());
470    /// assert!(good_ref3.is_well_formed());
471    ///
472    /// let good_ref4 = Field21::new("CAN/12345".to_string());
473    /// assert!(good_ref4.is_well_formed());
474    ///
475    /// let too_short = Field21::new("AB".to_string());
476    /// assert!(!too_short.is_well_formed());
477    ///
478    /// let all_same = Field21::new("AAAAAAA".to_string());
479    /// assert!(!all_same.is_well_formed());
480    ///
481    /// let invalid_chars = Field21::new("REF@123#".to_string());
482    /// assert!(!invalid_chars.is_well_formed());
483    ///
484    /// let spaces = Field21::new("REF 123".to_string());
485    /// assert!(!spaces.is_well_formed());
486    /// ```
487    pub fn is_well_formed(&self) -> bool {
488        let ref_str = &self.related_reference;
489
490        // Check minimum length (at least 3 characters for meaningful reference)
491        if ref_str.len() < 3 {
492            return false;
493        }
494
495        // Check for reasonable character distribution (not all same character)
496        let unique_chars: std::collections::HashSet<char> = ref_str.chars().collect();
497        if unique_chars.len() < 2 {
498            return false;
499        }
500
501        // Check for valid characters (alphanumeric and common special chars)
502        if !ref_str
503            .chars()
504            .all(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '/' | '.'))
505        {
506            return false;
507        }
508
509        true
510    }
511}
512
513impl std::fmt::Display for Field21 {
514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515        write!(f, "{}", self.related_reference)
516    }
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522
523    #[test]
524    fn test_field21_creation() {
525        let field = Field21::new("COV21234567890".to_string());
526        assert_eq!(field.related_reference(), "COV21234567890");
527    }
528
529    #[test]
530    fn test_field21_parse() {
531        let field = Field21::parse("AMD21234567890").unwrap();
532        assert_eq!(field.related_reference, "AMD21234567890");
533    }
534
535    #[test]
536    fn test_field21_parse_with_prefix() {
537        let field = Field21::parse(":21:CAN21234567890").unwrap();
538        assert_eq!(field.related_reference, "CAN21234567890");
539    }
540
541    #[test]
542    fn test_field21_to_swift_string() {
543        let field = Field21::new("REL21234567890".to_string());
544        assert_eq!(field.to_swift_string(), ":21:REL21234567890");
545    }
546
547    #[test]
548    fn test_field21_validation() {
549        let valid_field = Field21::new("COV12345".to_string());
550        let result = valid_field.validate();
551        assert!(result.is_valid);
552
553        let invalid_field = Field21::new("THIS_IS_TOO_LONG_FOR_FIELD21".to_string());
554        let result = invalid_field.validate();
555        assert!(!result.is_valid);
556    }
557
558    #[test]
559    fn test_field21_format_spec() {
560        assert_eq!(Field21::format_spec(), "16x");
561    }
562
563    #[test]
564    fn test_field21_reference_pattern_detection() {
565        // Test cover message pattern
566        let cov_ref = Field21::new("COV21234567890".to_string());
567        assert_eq!(cov_ref.reference_pattern(), "Cover Message");
568
569        // Test amendment pattern
570        let amd_ref = Field21::new("AMD0000012345".to_string());
571        assert_eq!(amd_ref.reference_pattern(), "Amendment");
572
573        // Test cancellation pattern
574        let can_ref = Field21::new("CAN987654321".to_string());
575        assert_eq!(can_ref.reference_pattern(), "Cancellation");
576
577        // Test related transaction pattern
578        let rel_ref = Field21::new("REL12345678".to_string());
579        assert_eq!(rel_ref.reference_pattern(), "Related Transaction");
580
581        // Test wire transfer pattern
582        let ft_ref = Field21::new("FT21234567890".to_string());
583        assert_eq!(ft_ref.reference_pattern(), "Wire Transfer");
584
585        // Test transaction ID pattern
586        let txn_ref = Field21::new("TXN0000012345".to_string());
587        assert_eq!(txn_ref.reference_pattern(), "Transaction ID");
588
589        // Test reference ID pattern
590        let ref_ref = Field21::new("REF987654321".to_string());
591        assert_eq!(ref_ref.reference_pattern(), "Reference ID");
592
593        // Test date-based pattern
594        let date_ref = Field21::new("20241201001".to_string());
595        assert_eq!(date_ref.reference_pattern(), "Date-based");
596
597        // Test numeric sequential pattern
598        let num_ref = Field21::new("123456789".to_string());
599        assert_eq!(num_ref.reference_pattern(), "Numeric Sequential");
600
601        // Test custom pattern
602        let custom_ref = Field21::new("CUSTOM_REF".to_string());
603        assert_eq!(custom_ref.reference_pattern(), "Custom");
604    }
605
606    #[test]
607    fn test_field21_case_insensitive_pattern_detection() {
608        // Test lowercase patterns
609        let cov_lower = Field21::new("cov21234567890".to_string());
610        assert_eq!(cov_lower.reference_pattern(), "Cover Message");
611
612        let amd_lower = Field21::new("amd0000012345".to_string());
613        assert_eq!(amd_lower.reference_pattern(), "Amendment");
614
615        // Test mixed case patterns
616        let mixed_case = Field21::new("Can21234567890".to_string());
617        assert_eq!(mixed_case.reference_pattern(), "Cancellation");
618    }
619
620    #[test]
621    fn test_field21_date_based_detection() {
622        // Test valid date-based references
623        let date_ref1 = Field21::new("20241201001".to_string());
624        assert!(date_ref1.is_date_based());
625
626        let date_ref2 = Field21::new("20230315999".to_string());
627        assert!(date_ref2.is_date_based());
628
629        let date_ref3 = Field21::new("20221231ABC".to_string());
630        assert!(date_ref3.is_date_based());
631
632        // Test invalid date-based references
633        let invalid_year = Field21::new("19991201001".to_string());
634        assert!(!invalid_year.is_date_based());
635
636        let invalid_month = Field21::new("20241301001".to_string());
637        assert!(!invalid_month.is_date_based());
638
639        let invalid_day = Field21::new("20241232001".to_string());
640        assert!(!invalid_day.is_date_based());
641
642        let too_short = Field21::new("2024120".to_string());
643        assert!(!too_short.is_date_based());
644
645        let non_numeric = Field21::new("COV241201001".to_string());
646        assert!(!non_numeric.is_date_based());
647    }
648
649    #[test]
650    fn test_field21_sequence_number_extraction() {
651        // Test sequence number extraction
652        let seq_ref1 = Field21::new("COV001".to_string());
653        assert_eq!(seq_ref1.sequence_number(), Some(1));
654
655        let seq_ref2 = Field21::new("AMD123456789".to_string());
656        assert_eq!(seq_ref2.sequence_number(), Some(123456789));
657
658        let seq_ref3 = Field21::new("CAN000000042".to_string());
659        assert_eq!(seq_ref3.sequence_number(), Some(42));
660
661        // Test no sequence number
662        let no_seq1 = Field21::new("CUSTOMREF".to_string());
663        assert_eq!(no_seq1.sequence_number(), None);
664
665        let no_seq2 = Field21::new("COV_NO_NUM".to_string());
666        assert_eq!(no_seq2.sequence_number(), None);
667
668        // Test all numeric reference
669        let all_num = Field21::new("123456789".to_string());
670        assert_eq!(all_num.sequence_number(), Some(123456789));
671
672        // Test sequence too long (more than 10 digits)
673        let too_long = Field21::new("AMD12345678901".to_string());
674        assert_eq!(too_long.sequence_number(), None);
675    }
676
677    #[test]
678    fn test_field21_operation_type_detection() {
679        // Test cover reference detection
680        let cov_ref = Field21::new("COV21234567890".to_string());
681        assert!(cov_ref.is_cover_reference());
682        assert!(!cov_ref.is_amendment_reference());
683        assert!(!cov_ref.is_cancellation_reference());
684
685        // Test amendment reference detection
686        let amd_ref = Field21::new("AMD21234567890".to_string());
687        assert!(!amd_ref.is_cover_reference());
688        assert!(amd_ref.is_amendment_reference());
689        assert!(!amd_ref.is_cancellation_reference());
690
691        // Test cancellation reference detection
692        let can_ref = Field21::new("CAN21234567890".to_string());
693        assert!(!can_ref.is_cover_reference());
694        assert!(!can_ref.is_amendment_reference());
695        assert!(can_ref.is_cancellation_reference());
696
697        // Test regular reference
698        let reg_ref = Field21::new("FT12345".to_string());
699        assert!(!reg_ref.is_cover_reference());
700        assert!(!reg_ref.is_amendment_reference());
701        assert!(!reg_ref.is_cancellation_reference());
702
703        // Test case insensitive detection
704        let cov_lower = Field21::new("cov12345".to_string());
705        assert!(cov_lower.is_cover_reference());
706    }
707
708    #[test]
709    fn test_field21_description_generation() {
710        // Test cover message description
711        let cov_ref = Field21::new("COV21234567890".to_string());
712        let description = cov_ref.description();
713        assert!(description.contains("COV21234567890"));
714        assert!(description.contains("Cover Message"));
715        assert!(description.contains("Length: 14"));
716
717        // Test amendment description
718        let amd_seq_ref = Field21::new("AMD20241201001".to_string());
719        let description = amd_seq_ref.description();
720        assert!(description.contains("Amendment"));
721        assert!(description.contains("Date-based"));
722
723        // Test custom reference
724        let custom_ref = Field21::new("MYREF123".to_string());
725        let description = custom_ref.description();
726        assert!(description.contains("Custom"));
727        assert!(description.contains("Sequence: 123"));
728    }
729
730    #[test]
731    fn test_field21_well_formed_validation() {
732        // Test well-formed references
733        let good_ref1 = Field21::new("COV21234567890".to_string());
734        assert!(good_ref1.is_well_formed());
735
736        let good_ref2 = Field21::new("AMD-123456".to_string());
737        assert!(good_ref2.is_well_formed());
738
739        let good_ref3 = Field21::new("REL_001.234".to_string());
740        assert!(good_ref3.is_well_formed());
741
742        let good_ref4 = Field21::new("CAN/12345".to_string());
743        assert!(good_ref4.is_well_formed());
744
745        // Test poorly formed references
746        let too_short = Field21::new("AB".to_string());
747        assert!(!too_short.is_well_formed());
748
749        let all_same = Field21::new("AAAAAAA".to_string());
750        assert!(!all_same.is_well_formed());
751
752        let invalid_chars = Field21::new("REF@123#".to_string());
753        assert!(!invalid_chars.is_well_formed());
754
755        let spaces = Field21::new("REF 123".to_string());
756        assert!(!spaces.is_well_formed());
757    }
758
759    #[test]
760    fn test_field21_display_formatting() {
761        let field = Field21::new("COV21234567890".to_string());
762        assert_eq!(format!("{}", field), "COV21234567890");
763
764        let field2 = Field21::new("AMD0000012345".to_string());
765        assert_eq!(format!("{}", field2), "AMD0000012345");
766    }
767
768    #[test]
769    fn test_field21_edge_cases() {
770        // Test minimum valid length
771        let min_ref = Field21::new("COV".to_string());
772        assert_eq!(min_ref.related_reference(), "COV");
773        assert!(min_ref.is_well_formed());
774
775        // Test maximum length
776        let max_ref = Field21::new("1234567890123456".to_string());
777        assert_eq!(max_ref.related_reference(), "1234567890123456");
778        assert_eq!(max_ref.related_reference().len(), 16);
779
780        // Test single character (should not be well-formed)
781        let single_char = Field21::new("A".to_string());
782        assert!(!single_char.is_well_formed());
783
784        // Test empty string (should not be well-formed)
785        let empty_ref = Field21::new("".to_string());
786        assert!(!empty_ref.is_well_formed());
787    }
788
789    #[test]
790    fn test_field21_real_world_examples() {
791        // Test realistic cover reference
792        let cover_ref = Field21::new("COV001".to_string());
793        assert_eq!(cover_ref.reference_pattern(), "Cover Message");
794        assert!(!cover_ref.is_date_based());
795        assert_eq!(cover_ref.sequence_number(), Some(1));
796        assert!(cover_ref.is_well_formed());
797        assert!(cover_ref.is_cover_reference());
798
799        // Test amendment reference
800        let amd_ref = Field21::new("AMD0000012345".to_string());
801        assert_eq!(amd_ref.reference_pattern(), "Amendment");
802        assert!(!amd_ref.is_date_based());
803        assert_eq!(amd_ref.sequence_number(), Some(12345));
804        assert!(amd_ref.is_well_formed());
805        assert!(amd_ref.is_amendment_reference());
806
807        // Test cancellation reference with date
808        let can_ref = Field21::new("CAN20241201001".to_string());
809        assert_eq!(can_ref.reference_pattern(), "Cancellation");
810        assert!(can_ref.is_date_based());
811        assert_eq!(can_ref.sequence_number(), Some(1));
812        assert!(can_ref.is_well_formed());
813        assert!(can_ref.is_cancellation_reference());
814
815        // Test related transaction reference
816        let rel_ref = Field21::new("REL001234567".to_string());
817        assert_eq!(rel_ref.reference_pattern(), "Related Transaction");
818        assert!(!rel_ref.is_date_based());
819        assert_eq!(rel_ref.sequence_number(), Some(1234567));
820        assert!(rel_ref.is_well_formed());
821    }
822
823    #[test]
824    fn test_field21_serialization() {
825        let field = Field21::new("COV21234567890".to_string());
826
827        // Test JSON serialization
828        let json = serde_json::to_string(&field).unwrap();
829        let deserialized: Field21 = serde_json::from_str(&json).unwrap();
830
831        assert_eq!(field, deserialized);
832        assert_eq!(field.related_reference(), deserialized.related_reference());
833    }
834
835    #[test]
836    fn test_field21_comprehensive_validation() {
837        // Test various validation scenarios
838        let valid_cases = vec![
839            "COV12345",
840            "AMD0000012345",
841            "CAN20241201001",
842            "REL123456789A",
843            "FT-123_456.78",
844            "ABC/DEF",
845        ];
846
847        for case in valid_cases {
848            let field = Field21::new(case.to_string());
849            let validation = field.validate();
850            assert!(validation.is_valid, "Failed validation for: {}", case);
851        }
852
853        // Test invalid cases
854        let invalid_cases = vec![
855            "THIS_IS_WAY_TOO_LONG_FOR_FIELD21_VALIDATION", // Too long
856        ];
857
858        for case in invalid_cases {
859            let field = Field21::new(case.to_string());
860            let validation = field.validate();
861            assert!(
862                !validation.is_valid,
863                "Should have failed validation for: {}",
864                case
865            );
866        }
867
868        // Test empty case separately (it's valid for Field21 creation but not well-formed)
869        let empty_field = Field21::new("".to_string());
870        let _validation = empty_field.validate();
871        // Empty field might be valid for Field21 but not well-formed
872        assert!(!empty_field.is_well_formed());
873    }
874}