1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use std::collections::HashSet;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct MT103 {
17 #[serde(rename = "20")]
19 pub field_20: Field20,
20
21 #[serde(rename = "23B")]
23 pub field_23b: Field23B,
24
25 #[serde(rename = "32A")]
27 pub field_32a: Field32A,
28
29 #[serde(flatten)]
31 pub field_50: Field50OrderingCustomerAFK,
32
33 #[serde(flatten)]
35 pub field_59: Field59,
36
37 #[serde(rename = "71A")]
39 pub field_71a: Field71A,
40
41 #[serde(rename = "13C")]
43 pub field_13c: Option<Vec<Field13C>>,
44
45 #[serde(rename = "23E")]
47 pub field_23e: Option<Vec<Field23E>>,
48
49 #[serde(rename = "26T")]
51 pub field_26t: Option<Field26T>,
52
53 #[serde(rename = "33B")]
55 pub field_33b: Option<Field33B>,
56
57 #[serde(rename = "36")]
59 pub field_36: Option<Field36>,
60
61 #[serde(rename = "51A")]
63 pub field_51a: Option<Field51A>,
64
65 #[serde(flatten)]
67 pub field_52: Option<Field52OrderingInstitution>,
68
69 #[serde(flatten)]
71 pub field_53: Option<Field53SenderCorrespondent>,
72
73 #[serde(flatten)]
75 pub field_54: Option<Field54ReceiverCorrespondent>,
76
77 #[serde(flatten)]
79 pub field_55: Option<Field55ThirdReimbursementInstitution>,
80
81 #[serde(flatten)]
83 pub field_56: Option<Field56Intermediary>,
84
85 #[serde(flatten)]
87 pub field_57: Option<Field57AccountWithInstitution>,
88
89 #[serde(rename = "70")]
91 pub field_70: Option<Field70>,
92
93 #[serde(rename = "71F")]
95 pub field_71f: Option<Vec<Field71F>>,
96
97 #[serde(rename = "71G")]
99 pub field_71g: Option<Field71G>,
100
101 #[serde(rename = "72")]
103 pub field_72: Option<Field72>,
104
105 #[serde(rename = "77B")]
107 pub field_77b: Option<Field77B>,
108
109 #[serde(rename = "77T")]
111 pub field_77t: Option<Field77T>,
112}
113
114impl MT103 {
116 pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
118 let block4 = extract_block4(input)?;
119 <Self as crate::traits::SwiftMessageBody>::parse_from_block4(&block4)
120 }
121
122 pub fn to_mt_string(&self) -> String {
124 let mut result = String::new();
125
126 append_field(&mut result, &self.field_20);
128 append_vec_field(&mut result, &self.field_13c);
129 append_field(&mut result, &self.field_23b);
130 append_vec_field(&mut result, &self.field_23e);
131 append_optional_field(&mut result, &self.field_26t);
132 append_field(&mut result, &self.field_32a);
133 append_optional_field(&mut result, &self.field_33b);
134 append_optional_field(&mut result, &self.field_36);
135 append_field(&mut result, &self.field_50);
136 append_optional_field(&mut result, &self.field_51a);
137 append_optional_field(&mut result, &self.field_52);
138 append_optional_field(&mut result, &self.field_53);
139 append_optional_field(&mut result, &self.field_54);
140 append_optional_field(&mut result, &self.field_55);
141 append_optional_field(&mut result, &self.field_56);
142 append_optional_field(&mut result, &self.field_57);
143 append_field(&mut result, &self.field_59);
144 append_optional_field(&mut result, &self.field_70);
145 append_field(&mut result, &self.field_71a);
146 append_vec_field(&mut result, &self.field_71f);
147 append_optional_field(&mut result, &self.field_71g);
148 append_optional_field(&mut result, &self.field_72);
149 append_optional_field(&mut result, &self.field_77b);
150 append_optional_field(&mut result, &self.field_77t);
151
152 finalize_mt_string(result, false)
153 }
154
155 pub fn has_reject_codes(&self) -> bool {
157 if let Some(ref field_72) = self.field_72 {
159 for line in &field_72.information {
160 if line.contains("/REJT/") || line.contains("/RETN/") {
161 return true;
162 }
163 }
164 }
165 false
166 }
167
168 pub fn has_return_codes(&self) -> bool {
170 if let Some(ref field_72) = self.field_72 {
172 for line in &field_72.information {
173 if line.contains("/RETN/") {
174 return true;
175 }
176 }
177 }
178 false
179 }
180
181 pub fn is_stp_compliant(&self) -> bool {
183 let bank_op_code = &self.field_23b.instruction_code;
185 if !["SPRI", "SSTD", "SPAY"].contains(&bank_op_code.as_str()) {
186 return true;
188 }
189
190 if bank_op_code == "SPRI" {
193 if let Some(ref field_23e_vec) = self.field_23e {
194 let allowed_codes = ["SDVA", "TELB", "PHOB", "INTC"];
195 for field_23e in field_23e_vec {
196 if !allowed_codes.contains(&field_23e.instruction_code.as_str()) {
197 return false;
198 }
199 }
200 }
201 } else if ["SSTD", "SPAY"].contains(&bank_op_code.as_str()) && self.field_23e.is_some() {
202 return false;
203 }
204
205 if bank_op_code == "SPRI" && self.field_56.is_some() {
208 return false;
209 }
210
211 true
214 }
215
216 const MT103_VALID_23B_CODES: &'static [&'static str] =
222 &["CRED", "CRTS", "SPAY", "SPRI", "SSTD"];
223
224 const MT103_VALID_23E_CODES: &'static [&'static str] = &[
226 "CHQB", "CORT", "HOLD", "INTC", "PHOB", "PHOI", "PHON", "REPA", "SDVA", "TELB", "TELE",
227 "TELI",
228 ];
229
230 const CODES_WITH_ADDITIONAL_INFO: &'static [&'static str] = &[
232 "PHON", "PHOB", "PHOI", "TELE", "TELB", "TELI", "HOLD", "REPA",
233 ];
234
235 const REMIT_SPRI_ALLOWED_23E: &'static [&'static str] = &["SDVA", "TELB", "PHOB", "INTC"];
237
238 const INVALID_23E_COMBINATIONS: &'static [(&'static str, &'static [&'static str])] = &[
240 ("SDVA", &["HOLD", "CHQB"]),
241 ("INTC", &["HOLD", "CHQB"]),
242 ("REPA", &["HOLD", "CHQB", "CORT"]),
243 ("CORT", &["HOLD", "CHQB"]),
244 ("HOLD", &["CHQB"]),
245 ("PHOB", &["TELB"]),
246 ("PHON", &["TELE"]),
247 ("PHOI", &["TELI"]),
248 ];
249
250 const FIELD_23E_CODE_ORDER: &'static [&'static str] = &[
252 "SDVA", "INTC", "REPA", "CORT", "HOLD", "CHQB", "PHOB", "TELB", "PHON", "TELE", "PHOI",
253 "TELI",
254 ];
255
256 fn has_field_56(&self) -> bool {
262 self.field_56.is_some()
263 }
264
265 fn has_field_57(&self) -> bool {
267 self.field_57.is_some()
268 }
269
270 fn has_field_53(&self) -> bool {
272 self.field_53.is_some()
273 }
274
275 fn has_field_54(&self) -> bool {
277 self.field_54.is_some()
278 }
279
280 fn has_field_55(&self) -> bool {
282 self.field_55.is_some()
283 }
284
285 fn has_field_71f(&self) -> bool {
287 self.field_71f.is_some() && !self.field_71f.as_ref().unwrap().is_empty()
288 }
289
290 fn has_field_71g(&self) -> bool {
292 self.field_71g.is_some()
293 }
294
295 fn validate_c1_currency_exchange(&self) -> Option<SwiftValidationError> {
302 if let Some(ref field_33b) = self.field_33b {
303 let currency_32a = &self.field_32a.currency;
304 let currency_33b = &field_33b.currency;
305
306 if currency_32a != currency_33b {
307 if self.field_36.is_none() {
309 return Some(SwiftValidationError::content_error(
310 "D75",
311 "36",
312 "",
313 "Field 36 (Exchange Rate) is mandatory when field 33B is present and currency code differs from field 32A",
314 "If field 33B is present and the currency code is different from the currency code in field 32A, field 36 must be present",
315 ));
316 }
317 } else {
318 if self.field_36.is_some() {
320 return Some(SwiftValidationError::content_error(
321 "D75",
322 "36",
323 "",
324 "Field 36 (Exchange Rate) is not allowed when field 33B currency code is the same as field 32A",
325 "If field 33B is present and the currency code is equal to the currency code in field 32A, field 36 must not be present",
326 ));
327 }
328 }
329 } else {
330 if self.field_36.is_some() {
332 return Some(SwiftValidationError::content_error(
333 "D75",
334 "36",
335 "",
336 "Field 36 (Exchange Rate) is not allowed when field 33B is not present",
337 "Field 36 is only allowed when field 33B is present",
338 ));
339 }
340 }
341
342 None
343 }
344
345 fn validate_c3_bank_op_instruction_codes(&self) -> Vec<SwiftValidationError> {
348 let mut errors = Vec::new();
349 let bank_op_code = &self.field_23b.instruction_code;
350
351 if bank_op_code == "SPRI" {
352 if let Some(ref field_23e_vec) = self.field_23e {
355 for field_23e in field_23e_vec {
356 let code = &field_23e.instruction_code;
357 if !Self::REMIT_SPRI_ALLOWED_23E.contains(&code.as_str()) {
358 errors.push(SwiftValidationError::content_error(
359 "E01",
360 "23E",
361 code,
362 &format!(
363 "When field 23B is SPRI, field 23E may only contain codes: {}. Code '{}' is not allowed",
364 Self::REMIT_SPRI_ALLOWED_23E.join(", "),
365 code
366 ),
367 "If field 23B contains SPRI, field 23E may contain only SDVA, TELB, PHOB, or INTC",
368 ));
369 }
370 }
371 }
372 } else if bank_op_code == "SSTD" || bank_op_code == "SPAY" {
373 if self.field_23e.is_some() {
375 errors.push(SwiftValidationError::content_error(
376 "E02",
377 "23E",
378 "",
379 &format!(
380 "When field 23B is {} or {}, field 23E must not be used",
381 "SSTD", "SPAY"
382 ),
383 "If field 23B contains one of the codes SSTD or SPAY, field 23E must not be used",
384 ));
385 }
386 }
387
388 errors
389 }
390
391 fn validate_c4_third_reimbursement(&self) -> Option<SwiftValidationError> {
394 if self.has_field_55() && (!self.has_field_53() || !self.has_field_54()) {
395 return Some(SwiftValidationError::content_error(
396 "E06",
397 "55a",
398 "",
399 "Fields 53a (Sender's Correspondent) and 54a (Receiver's Correspondent) are mandatory when field 55a (Third Reimbursement Institution) is present",
400 "If field 55a is present, both fields 53a and 54a must also be present",
401 ));
402 }
403
404 None
405 }
406
407 fn validate_c5_intermediary(&self) -> Option<SwiftValidationError> {
410 if self.has_field_56() && !self.has_field_57() {
411 return Some(SwiftValidationError::content_error(
412 "C81",
413 "57a",
414 "",
415 "Field 57a (Account With Institution) is mandatory when field 56a (Intermediary) is present",
416 "If field 56a is present, field 57a must also be present",
417 ));
418 }
419
420 None
421 }
422
423 fn validate_c6_field_56_restrictions(&self) -> Option<SwiftValidationError> {
427 let bank_op_code = &self.field_23b.instruction_code;
428
429 if bank_op_code == "SPRI" && self.has_field_56() {
430 return Some(SwiftValidationError::content_error(
431 "E16",
432 "56a",
433 "",
434 "Field 56a (Intermediary Institution) must not be present when field 23B is SPRI",
435 "If field 23B contains the code SPRI, field 56a must not be present",
436 ));
437 }
438
439 None
440 }
441
442 fn validate_c7_charges(&self) -> Vec<SwiftValidationError> {
445 let mut errors = Vec::new();
446 let charges_code = &self.field_71a.code;
447
448 match charges_code.as_str() {
449 "OUR" => {
450 if self.has_field_71f() {
452 errors.push(SwiftValidationError::content_error(
453 "E13",
454 "71F",
455 "",
456 "Field 71F (Sender's Charges) is not allowed when field 71A is OUR",
457 "If field 71A contains OUR, then field 71F is not allowed",
458 ));
459 }
460 }
461 "SHA" => {
462 if self.has_field_71g() {
464 errors.push(SwiftValidationError::content_error(
465 "D50",
466 "71G",
467 "",
468 "Field 71G (Receiver's Charges) is not allowed when field 71A is SHA",
469 "If field 71A contains SHA, then field 71G is not allowed",
470 ));
471 }
472 }
473 "BEN" => {
474 if !self.has_field_71f() {
476 errors.push(SwiftValidationError::content_error(
477 "E15",
478 "71F",
479 "",
480 "At least one occurrence of field 71F (Sender's Charges) is mandatory when field 71A is BEN",
481 "If field 71A contains BEN, then at least one occurrence of field 71F is mandatory",
482 ));
483 }
484 if self.has_field_71g() {
485 errors.push(SwiftValidationError::content_error(
486 "E15",
487 "71G",
488 "",
489 "Field 71G (Receiver's Charges) is not allowed when field 71A is BEN",
490 "If field 71A contains BEN, then field 71G is not allowed",
491 ));
492 }
493 }
494 _ => {}
495 }
496
497 errors
498 }
499
500 fn validate_c8_charges_instructed_amount(&self) -> Option<SwiftValidationError> {
503 if (self.has_field_71f() || self.has_field_71g()) && self.field_33b.is_none() {
504 return Some(SwiftValidationError::content_error(
505 "D51",
506 "33B",
507 "",
508 "Field 33B (Currency/Instructed Amount) is mandatory when field 71F or 71G is present",
509 "If either field 71F (at least one occurrence) or field 71G is present, then field 33B is mandatory",
510 ));
511 }
512
513 None
514 }
515
516 fn validate_c9_receiver_charges_currency(&self) -> Option<SwiftValidationError> {
519 if let Some(ref field_71g) = self.field_71g {
520 let currency_32a = &self.field_32a.currency;
521 let currency_71g = &field_71g.currency;
522
523 if currency_32a != currency_71g {
524 return Some(SwiftValidationError::content_error(
525 "C02",
526 "71G",
527 currency_71g,
528 &format!(
529 "Currency code in field 71G ({}) must be the same as in field 32A ({})",
530 currency_71g, currency_32a
531 ),
532 "The currency code in fields 71G and 32A must be the same",
533 ));
534 }
535 }
536
537 None
538 }
539
540 fn validate_c13_chqb_beneficiary_account(&self) -> Option<SwiftValidationError> {
543 if let Some(ref field_23e_vec) = self.field_23e {
544 let has_chqb = field_23e_vec.iter().any(|f| f.instruction_code == "CHQB");
545
546 if has_chqb {
547 let has_account = match &self.field_59 {
550 Field59::NoOption(f) => f.account.is_some(),
551 Field59::A(f) => f.account.is_some(),
552 Field59::F(_) => false, };
554
555 if has_account {
556 return Some(SwiftValidationError::content_error(
557 "E18",
558 "59a",
559 "",
560 "Subfield 1 (Account) in field 59a (Beneficiary Customer) is not allowed when field 23E contains code CHQB",
561 "If any field 23E contains the code CHQB, subfield 1 (Account) in field 59a Beneficiary Customer is not allowed",
562 ));
563 }
564 }
565 }
566
567 None
568 }
569
570 fn validate_c16_teli_phoi_restriction(&self) -> Vec<SwiftValidationError> {
573 let mut errors = Vec::new();
574
575 if !self.has_field_56()
576 && let Some(ref field_23e_vec) = self.field_23e
577 {
578 for field_23e in field_23e_vec {
579 let code = &field_23e.instruction_code;
580 if code == "TELI" || code == "PHOI" {
581 errors.push(SwiftValidationError::content_error(
582 "E44",
583 "23E",
584 code,
585 &format!(
586 "Field 23E code '{}' is not allowed when field 56a is not present",
587 code
588 ),
589 "If field 56a is not present, no field 23E may contain TELI or PHOI",
590 ));
591 }
592 }
593 }
594
595 errors
596 }
597
598 fn validate_c17_tele_phon_restriction(&self) -> Vec<SwiftValidationError> {
601 let mut errors = Vec::new();
602
603 if !self.has_field_57()
604 && let Some(ref field_23e_vec) = self.field_23e
605 {
606 for field_23e in field_23e_vec {
607 let code = &field_23e.instruction_code;
608 if code == "TELE" || code == "PHON" {
609 errors.push(SwiftValidationError::content_error(
610 "E45",
611 "23E",
612 code,
613 &format!(
614 "Field 23E code '{}' is not allowed when field 57a is not present",
615 code
616 ),
617 "If field 57a is not present, no field 23E may contain TELE or PHON",
618 ));
619 }
620 }
621 }
622
623 errors
624 }
625
626 fn validate_field_23b(&self) -> Option<SwiftValidationError> {
628 let code = &self.field_23b.instruction_code;
629
630 if !Self::MT103_VALID_23B_CODES.contains(&code.as_str()) {
631 return Some(SwiftValidationError::format_error(
632 "T36",
633 "23B",
634 code,
635 &format!("One of: {}", Self::MT103_VALID_23B_CODES.join(", ")),
636 &format!(
637 "Bank operation code '{}' is not valid for MT103. Valid codes: {}",
638 code,
639 Self::MT103_VALID_23B_CODES.join(", ")
640 ),
641 ));
642 }
643
644 None
645 }
646
647 fn validate_field_23e(&self) -> Vec<SwiftValidationError> {
650 let mut errors = Vec::new();
651
652 if let Some(ref field_23e_vec) = self.field_23e {
653 let mut seen_codes = HashSet::new();
654 let mut code_positions: Vec<(String, usize)> = Vec::new();
655
656 for field_23e in field_23e_vec {
657 let code = &field_23e.instruction_code;
658
659 if !Self::MT103_VALID_23E_CODES.contains(&code.as_str()) {
661 errors.push(SwiftValidationError::format_error(
662 "T48",
663 "23E",
664 code,
665 &format!("One of: {}", Self::MT103_VALID_23E_CODES.join(", ")),
666 &format!(
667 "Instruction code '{}' is not valid for MT103. Valid codes: {}",
668 code,
669 Self::MT103_VALID_23E_CODES.join(", ")
670 ),
671 ));
672 }
673
674 if field_23e.additional_info.is_some()
676 && !Self::CODES_WITH_ADDITIONAL_INFO.contains(&code.as_str())
677 {
678 errors.push(SwiftValidationError::content_error(
679 "D97",
680 "23E",
681 code,
682 &format!(
683 "Additional information is only allowed for codes: {}. Code '{}' does not allow additional information",
684 Self::CODES_WITH_ADDITIONAL_INFO.join(", "),
685 code
686 ),
687 "Additional information in field 23E is only allowed for codes: PHON, PHOB, PHOI, TELE, TELB, TELI, HOLD, REPA",
688 ));
689 }
690
691 if seen_codes.contains(code) {
693 errors.push(SwiftValidationError::relation_error(
694 "E46",
695 "23E",
696 vec![],
697 &format!(
698 "Instruction code '{}' appears more than once. Same code must not be repeated",
699 code
700 ),
701 "When field 23E is repeated, the same code must not be present more than once",
702 ));
703 }
704 seen_codes.insert(code.clone());
705
706 if let Some(pos) = Self::FIELD_23E_CODE_ORDER.iter().position(|&c| c == code) {
708 code_positions.push((code.clone(), pos));
709 }
710 }
711
712 for i in 1..code_positions.len() {
714 if code_positions[i].1 < code_positions[i - 1].1 {
715 errors.push(SwiftValidationError::content_error(
716 "D98",
717 "23E",
718 &code_positions[i].0,
719 &format!(
720 "Instruction codes must appear in the following order: {}. Code '{}' appears out of order",
721 Self::FIELD_23E_CODE_ORDER.join(", "),
722 code_positions[i].0
723 ),
724 "When field 23E is repeated, codes must appear in specified order",
725 ));
726 break;
727 }
728 }
729
730 for field_23e in field_23e_vec {
732 let code = &field_23e.instruction_code;
733
734 for &(base_code, forbidden_codes) in Self::INVALID_23E_COMBINATIONS {
735 if code == base_code {
736 for other_field in field_23e_vec {
737 let other_code = &other_field.instruction_code;
738 if forbidden_codes.contains(&other_code.as_str()) {
739 errors.push(SwiftValidationError::content_error(
740 "D67",
741 "23E",
742 code,
743 &format!(
744 "Instruction code '{}' cannot be combined with code '{}'. Invalid combination",
745 code, other_code
746 ),
747 &format!(
748 "Code '{}' cannot be combined with: {}",
749 base_code,
750 forbidden_codes.join(", ")
751 ),
752 ));
753 }
754 }
755 }
756 }
757 }
758 }
759
760 errors
761 }
762
763 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
766 let mut all_errors = Vec::new();
767
768 if let Some(error) = self.validate_field_23b() {
770 all_errors.push(error);
771 if stop_on_first_error {
772 return all_errors;
773 }
774 }
775
776 let f23e_errors = self.validate_field_23e();
778 all_errors.extend(f23e_errors);
779 if stop_on_first_error && !all_errors.is_empty() {
780 return all_errors;
781 }
782
783 if let Some(error) = self.validate_c1_currency_exchange() {
785 all_errors.push(error);
786 if stop_on_first_error {
787 return all_errors;
788 }
789 }
790
791 let c3_errors = self.validate_c3_bank_op_instruction_codes();
793 all_errors.extend(c3_errors);
794 if stop_on_first_error && !all_errors.is_empty() {
795 return all_errors;
796 }
797
798 if let Some(error) = self.validate_c4_third_reimbursement() {
800 all_errors.push(error);
801 if stop_on_first_error {
802 return all_errors;
803 }
804 }
805
806 if let Some(error) = self.validate_c5_intermediary() {
808 all_errors.push(error);
809 if stop_on_first_error {
810 return all_errors;
811 }
812 }
813
814 if let Some(error) = self.validate_c6_field_56_restrictions() {
816 all_errors.push(error);
817 if stop_on_first_error {
818 return all_errors;
819 }
820 }
821
822 let c7_errors = self.validate_c7_charges();
824 all_errors.extend(c7_errors);
825 if stop_on_first_error && !all_errors.is_empty() {
826 return all_errors;
827 }
828
829 if let Some(error) = self.validate_c8_charges_instructed_amount() {
831 all_errors.push(error);
832 if stop_on_first_error {
833 return all_errors;
834 }
835 }
836
837 if let Some(error) = self.validate_c9_receiver_charges_currency() {
839 all_errors.push(error);
840 if stop_on_first_error {
841 return all_errors;
842 }
843 }
844
845 if let Some(error) = self.validate_c13_chqb_beneficiary_account() {
847 all_errors.push(error);
848 if stop_on_first_error {
849 return all_errors;
850 }
851 }
852
853 let c16_errors = self.validate_c16_teli_phoi_restriction();
855 all_errors.extend(c16_errors);
856 if stop_on_first_error && !all_errors.is_empty() {
857 return all_errors;
858 }
859
860 let c17_errors = self.validate_c17_tele_phon_restriction();
862 all_errors.extend(c17_errors);
863
864 all_errors
865 }
866}
867
868impl crate::traits::SwiftMessageBody for MT103 {
869 fn message_type() -> &'static str {
870 "103"
871 }
872
873 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
874 let mut parser = crate::parser::MessageParser::new(block4, "103");
875
876 let field_20 = parser.parse_field::<Field20>("20")?;
878
879 parser = parser.with_duplicates(true);
881 let mut field_13c = Vec::new();
882 while let Ok(field) = parser.parse_field::<Field13C>("13C") {
883 field_13c.push(field);
884 }
885 parser = parser.with_duplicates(false);
886
887 let field_23b = parser.parse_field::<Field23B>("23B")?;
889
890 parser = parser.with_duplicates(true);
892 let mut field_23e = Vec::new();
893 while let Ok(field) = parser.parse_field::<Field23E>("23E") {
894 field_23e.push(field);
895 }
896 parser = parser.with_duplicates(false);
897
898 let field_26t = parser.parse_optional_field::<Field26T>("26T")?;
900
901 let field_32a = parser.parse_field::<Field32A>("32A")?;
903
904 let field_33b = parser.parse_optional_field::<Field33B>("33B")?;
906 let field_36 = parser.parse_optional_field::<Field36>("36")?;
907
908 let field_50 = parser.parse_variant_field::<Field50OrderingCustomerAFK>("50")?;
910
911 let field_51a = parser.parse_optional_field::<Field51A>("51A")?;
913 let field_52 = parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
914 let field_53 = parser.parse_optional_variant_field::<Field53SenderCorrespondent>("53")?;
915 let field_54 = parser.parse_optional_variant_field::<Field54ReceiverCorrespondent>("54")?;
916 let field_55 =
917 parser.parse_optional_variant_field::<Field55ThirdReimbursementInstitution>("55")?;
918 let field_56 = parser.parse_optional_variant_field::<Field56Intermediary>("56")?;
919 let field_57 =
920 parser.parse_optional_variant_field::<Field57AccountWithInstitution>("57")?;
921
922 let field_59 = parser.parse_variant_field::<Field59>("59")?;
924
925 let field_70 = parser.parse_optional_field::<Field70>("70")?;
927
928 let field_71a = parser.parse_field::<Field71A>("71A")?;
930
931 parser = parser.with_duplicates(true);
933 let mut field_71f = Vec::new();
934 while let Ok(field) = parser.parse_field::<Field71F>("71F") {
935 field_71f.push(field);
936 }
937 parser = parser.with_duplicates(false);
938
939 let field_71g = parser.parse_optional_field::<Field71G>("71G")?;
941 let field_72 = parser.parse_optional_field::<Field72>("72")?;
942 let field_77b = parser.parse_optional_field::<Field77B>("77B")?;
943 let field_77t = parser.parse_optional_field::<Field77T>("77T")?;
944
945 verify_parser_complete(&parser)?;
947
948 Ok(Self {
949 field_20,
950 field_23b,
951 field_32a,
952 field_50,
953 field_59,
954 field_71a,
955 field_13c: if field_13c.is_empty() {
956 None
957 } else {
958 Some(field_13c)
959 },
960 field_23e: if field_23e.is_empty() {
961 None
962 } else {
963 Some(field_23e)
964 },
965 field_26t,
966 field_33b,
967 field_36,
968 field_51a,
969 field_52,
970 field_53,
971 field_54,
972 field_55,
973 field_56,
974 field_57,
975 field_70,
976 field_71f: if field_71f.is_empty() {
977 None
978 } else {
979 Some(field_71f)
980 },
981 field_71g,
982 field_72,
983 field_77b,
984 field_77t,
985 })
986 }
987
988 fn to_mt_string(&self) -> String {
989 MT103::to_mt_string(self)
991 }
992
993 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
994 MT103::validate_network_rules(self, stop_on_first_error)
996 }
997}
998
999#[cfg(test)]
1000mod tests {
1001 use super::*;
1002
1003 #[test]
1004 fn test_mt103_parse() {
1005 let mt103_text = r#":20:123456789012345
1006:23B:CRED
1007:32A:241201USD1000000,00
1008:50K:/12345678901234567890
1009JOHN DOE
1010123 MAIN STREET
1011NEW YORK, NY 10001
1012:59:/98765432109876543210
1013JANE SMITH
1014456 OAK AVENUE
1015LOS ANGELES, CA 90001
1016:71A:OUR
1017-"#;
1018 let result = <MT103 as crate::traits::SwiftMessageBody>::parse_from_block4(mt103_text);
1019 assert!(result.is_ok());
1020 let mt103 = result.unwrap();
1021 assert_eq!(mt103.field_20.reference, "123456789012345");
1022 assert_eq!(mt103.field_23b.instruction_code, "CRED");
1023 assert_eq!(mt103.field_71a.code, "OUR");
1024 }
1025
1026 #[test]
1027 fn test_mt103_stp_compliance() {
1028 let mt103_text = r#":20:123456789012345
1029:23B:SPRI
1030:32A:241201USD1000000,00
1031:50K:/12345678901234567890
1032JOHN DOE
1033123 MAIN STREET
1034NEW YORK, NY 10001
1035:59:/98765432109876543210
1036JANE SMITH
1037456 OAK AVENUE
1038LOS ANGELES, CA 90001
1039:71A:OUR
1040-"#;
1041 let result = <MT103 as crate::traits::SwiftMessageBody>::parse_from_block4(mt103_text);
1042 assert!(result.is_ok());
1043 let mt103 = result.unwrap();
1044
1045 assert!(mt103.is_stp_compliant());
1047 }
1048}