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 result.push('-');
153 result
154 }
155
156 pub fn has_reject_codes(&self) -> bool {
158 if let Some(ref field_72) = self.field_72 {
160 for line in &field_72.information {
161 if line.contains("/REJT/") || line.contains("/RETN/") {
162 return true;
163 }
164 }
165 }
166 false
167 }
168
169 pub fn has_return_codes(&self) -> bool {
171 if let Some(ref field_72) = self.field_72 {
173 for line in &field_72.information {
174 if line.contains("/RETN/") {
175 return true;
176 }
177 }
178 }
179 false
180 }
181
182 pub fn is_stp_compliant(&self) -> bool {
184 let bank_op_code = &self.field_23b.instruction_code;
186 if !["SPRI", "SSTD", "SPAY"].contains(&bank_op_code.as_str()) {
187 return true;
189 }
190
191 if bank_op_code == "SPRI" {
194 if let Some(ref field_23e_vec) = self.field_23e {
195 let allowed_codes = ["SDVA", "TELB", "PHOB", "INTC"];
196 for field_23e in field_23e_vec {
197 if !allowed_codes.contains(&field_23e.instruction_code.as_str()) {
198 return false;
199 }
200 }
201 }
202 } else if ["SSTD", "SPAY"].contains(&bank_op_code.as_str()) && self.field_23e.is_some() {
203 return false;
204 }
205
206 if bank_op_code == "SPRI" && self.field_56.is_some() {
209 return false;
210 }
211
212 true
215 }
216
217 const MT103_VALID_23B_CODES: &'static [&'static str] =
223 &["CRED", "CRTS", "SPAY", "SPRI", "SSTD"];
224
225 const MT103_VALID_23E_CODES: &'static [&'static str] = &[
227 "CHQB", "CORT", "HOLD", "INTC", "PHOB", "PHOI", "PHON", "REPA", "SDVA", "TELB", "TELE",
228 "TELI",
229 ];
230
231 const CODES_WITH_ADDITIONAL_INFO: &'static [&'static str] = &[
233 "PHON", "PHOB", "PHOI", "TELE", "TELB", "TELI", "HOLD", "REPA",
234 ];
235
236 const REMIT_SPRI_ALLOWED_23E: &'static [&'static str] = &["SDVA", "TELB", "PHOB", "INTC"];
238
239 const INVALID_23E_COMBINATIONS: &'static [(&'static str, &'static [&'static str])] = &[
241 ("SDVA", &["HOLD", "CHQB"]),
242 ("INTC", &["HOLD", "CHQB"]),
243 ("REPA", &["HOLD", "CHQB", "CORT"]),
244 ("CORT", &["HOLD", "CHQB"]),
245 ("HOLD", &["CHQB"]),
246 ("PHOB", &["TELB"]),
247 ("PHON", &["TELE"]),
248 ("PHOI", &["TELI"]),
249 ];
250
251 const FIELD_23E_CODE_ORDER: &'static [&'static str] = &[
253 "SDVA", "INTC", "REPA", "CORT", "HOLD", "CHQB", "PHOB", "TELB", "PHON", "TELE", "PHOI",
254 "TELI",
255 ];
256
257 fn has_field_56(&self) -> bool {
263 self.field_56.is_some()
264 }
265
266 fn has_field_57(&self) -> bool {
268 self.field_57.is_some()
269 }
270
271 fn has_field_53(&self) -> bool {
273 self.field_53.is_some()
274 }
275
276 fn has_field_54(&self) -> bool {
278 self.field_54.is_some()
279 }
280
281 fn has_field_55(&self) -> bool {
283 self.field_55.is_some()
284 }
285
286 fn has_field_71f(&self) -> bool {
288 self.field_71f.is_some() && !self.field_71f.as_ref().unwrap().is_empty()
289 }
290
291 fn has_field_71g(&self) -> bool {
293 self.field_71g.is_some()
294 }
295
296 fn validate_c1_currency_exchange(&self) -> Option<SwiftValidationError> {
303 if let Some(ref field_33b) = self.field_33b {
304 let currency_32a = &self.field_32a.currency;
305 let currency_33b = &field_33b.currency;
306
307 if currency_32a != currency_33b {
308 if self.field_36.is_none() {
310 return Some(SwiftValidationError::content_error(
311 "D75",
312 "36",
313 "",
314 "Field 36 (Exchange Rate) is mandatory when field 33B is present and currency code differs from field 32A",
315 "If field 33B is present and the currency code is different from the currency code in field 32A, field 36 must be present",
316 ));
317 }
318 } else {
319 if self.field_36.is_some() {
321 return Some(SwiftValidationError::content_error(
322 "D75",
323 "36",
324 "",
325 "Field 36 (Exchange Rate) is not allowed when field 33B currency code is the same as field 32A",
326 "If field 33B is present and the currency code is equal to the currency code in field 32A, field 36 must not be present",
327 ));
328 }
329 }
330 } else {
331 if self.field_36.is_some() {
333 return Some(SwiftValidationError::content_error(
334 "D75",
335 "36",
336 "",
337 "Field 36 (Exchange Rate) is not allowed when field 33B is not present",
338 "Field 36 is only allowed when field 33B is present",
339 ));
340 }
341 }
342
343 None
344 }
345
346 fn validate_c3_bank_op_instruction_codes(&self) -> Vec<SwiftValidationError> {
349 let mut errors = Vec::new();
350 let bank_op_code = &self.field_23b.instruction_code;
351
352 if bank_op_code == "SPRI" {
353 if let Some(ref field_23e_vec) = self.field_23e {
356 for field_23e in field_23e_vec {
357 let code = &field_23e.instruction_code;
358 if !Self::REMIT_SPRI_ALLOWED_23E.contains(&code.as_str()) {
359 errors.push(SwiftValidationError::content_error(
360 "E01",
361 "23E",
362 code,
363 &format!(
364 "When field 23B is SPRI, field 23E may only contain codes: {}. Code '{}' is not allowed",
365 Self::REMIT_SPRI_ALLOWED_23E.join(", "),
366 code
367 ),
368 "If field 23B contains SPRI, field 23E may contain only SDVA, TELB, PHOB, or INTC",
369 ));
370 }
371 }
372 }
373 } else if bank_op_code == "SSTD" || bank_op_code == "SPAY" {
374 if self.field_23e.is_some() {
376 errors.push(SwiftValidationError::content_error(
377 "E02",
378 "23E",
379 "",
380 &format!(
381 "When field 23B is {} or {}, field 23E must not be used",
382 "SSTD", "SPAY"
383 ),
384 "If field 23B contains one of the codes SSTD or SPAY, field 23E must not be used",
385 ));
386 }
387 }
388
389 errors
390 }
391
392 fn validate_c4_third_reimbursement(&self) -> Option<SwiftValidationError> {
395 if self.has_field_55() && (!self.has_field_53() || !self.has_field_54()) {
396 return Some(SwiftValidationError::content_error(
397 "E06",
398 "55a",
399 "",
400 "Fields 53a (Sender's Correspondent) and 54a (Receiver's Correspondent) are mandatory when field 55a (Third Reimbursement Institution) is present",
401 "If field 55a is present, both fields 53a and 54a must also be present",
402 ));
403 }
404
405 None
406 }
407
408 fn validate_c5_intermediary(&self) -> Option<SwiftValidationError> {
411 if self.has_field_56() && !self.has_field_57() {
412 return Some(SwiftValidationError::content_error(
413 "C81",
414 "57a",
415 "",
416 "Field 57a (Account With Institution) is mandatory when field 56a (Intermediary) is present",
417 "If field 56a is present, field 57a must also be present",
418 ));
419 }
420
421 None
422 }
423
424 fn validate_c6_field_56_restrictions(&self) -> Option<SwiftValidationError> {
428 let bank_op_code = &self.field_23b.instruction_code;
429
430 if bank_op_code == "SPRI" && self.has_field_56() {
431 return Some(SwiftValidationError::content_error(
432 "E16",
433 "56a",
434 "",
435 "Field 56a (Intermediary Institution) must not be present when field 23B is SPRI",
436 "If field 23B contains the code SPRI, field 56a must not be present",
437 ));
438 }
439
440 None
441 }
442
443 fn validate_c7_charges(&self) -> Vec<SwiftValidationError> {
446 let mut errors = Vec::new();
447 let charges_code = &self.field_71a.code;
448
449 match charges_code.as_str() {
450 "OUR" => {
451 if self.has_field_71f() {
453 errors.push(SwiftValidationError::content_error(
454 "E13",
455 "71F",
456 "",
457 "Field 71F (Sender's Charges) is not allowed when field 71A is OUR",
458 "If field 71A contains OUR, then field 71F is not allowed",
459 ));
460 }
461 }
462 "SHA" => {
463 if self.has_field_71g() {
465 errors.push(SwiftValidationError::content_error(
466 "D50",
467 "71G",
468 "",
469 "Field 71G (Receiver's Charges) is not allowed when field 71A is SHA",
470 "If field 71A contains SHA, then field 71G is not allowed",
471 ));
472 }
473 }
474 "BEN" => {
475 if !self.has_field_71f() {
477 errors.push(SwiftValidationError::content_error(
478 "E15",
479 "71F",
480 "",
481 "At least one occurrence of field 71F (Sender's Charges) is mandatory when field 71A is BEN",
482 "If field 71A contains BEN, then at least one occurrence of field 71F is mandatory",
483 ));
484 }
485 if self.has_field_71g() {
486 errors.push(SwiftValidationError::content_error(
487 "E15",
488 "71G",
489 "",
490 "Field 71G (Receiver's Charges) is not allowed when field 71A is BEN",
491 "If field 71A contains BEN, then field 71G is not allowed",
492 ));
493 }
494 }
495 _ => {}
496 }
497
498 errors
499 }
500
501 fn validate_c8_charges_instructed_amount(&self) -> Option<SwiftValidationError> {
504 if (self.has_field_71f() || self.has_field_71g()) && self.field_33b.is_none() {
505 return Some(SwiftValidationError::content_error(
506 "D51",
507 "33B",
508 "",
509 "Field 33B (Currency/Instructed Amount) is mandatory when field 71F or 71G is present",
510 "If either field 71F (at least one occurrence) or field 71G is present, then field 33B is mandatory",
511 ));
512 }
513
514 None
515 }
516
517 fn validate_c9_receiver_charges_currency(&self) -> Option<SwiftValidationError> {
520 if let Some(ref field_71g) = self.field_71g {
521 let currency_32a = &self.field_32a.currency;
522 let currency_71g = &field_71g.currency;
523
524 if currency_32a != currency_71g {
525 return Some(SwiftValidationError::content_error(
526 "C02",
527 "71G",
528 currency_71g,
529 &format!(
530 "Currency code in field 71G ({}) must be the same as in field 32A ({})",
531 currency_71g, currency_32a
532 ),
533 "The currency code in fields 71G and 32A must be the same",
534 ));
535 }
536 }
537
538 None
539 }
540
541 fn validate_c13_chqb_beneficiary_account(&self) -> Option<SwiftValidationError> {
544 if let Some(ref field_23e_vec) = self.field_23e {
545 let has_chqb = field_23e_vec.iter().any(|f| f.instruction_code == "CHQB");
546
547 if has_chqb {
548 let has_account = match &self.field_59 {
551 Field59::NoOption(f) => f.account.is_some(),
552 Field59::A(f) => f.account.is_some(),
553 Field59::F(_) => false, };
555
556 if has_account {
557 return Some(SwiftValidationError::content_error(
558 "E18",
559 "59a",
560 "",
561 "Subfield 1 (Account) in field 59a (Beneficiary Customer) is not allowed when field 23E contains code CHQB",
562 "If any field 23E contains the code CHQB, subfield 1 (Account) in field 59a Beneficiary Customer is not allowed",
563 ));
564 }
565 }
566 }
567
568 None
569 }
570
571 fn validate_c16_teli_phoi_restriction(&self) -> Vec<SwiftValidationError> {
574 let mut errors = Vec::new();
575
576 if !self.has_field_56()
577 && let Some(ref field_23e_vec) = self.field_23e
578 {
579 for field_23e in field_23e_vec {
580 let code = &field_23e.instruction_code;
581 if code == "TELI" || code == "PHOI" {
582 errors.push(SwiftValidationError::content_error(
583 "E44",
584 "23E",
585 code,
586 &format!(
587 "Field 23E code '{}' is not allowed when field 56a is not present",
588 code
589 ),
590 "If field 56a is not present, no field 23E may contain TELI or PHOI",
591 ));
592 }
593 }
594 }
595
596 errors
597 }
598
599 fn validate_c17_tele_phon_restriction(&self) -> Vec<SwiftValidationError> {
602 let mut errors = Vec::new();
603
604 if !self.has_field_57()
605 && let Some(ref field_23e_vec) = self.field_23e
606 {
607 for field_23e in field_23e_vec {
608 let code = &field_23e.instruction_code;
609 if code == "TELE" || code == "PHON" {
610 errors.push(SwiftValidationError::content_error(
611 "E45",
612 "23E",
613 code,
614 &format!(
615 "Field 23E code '{}' is not allowed when field 57a is not present",
616 code
617 ),
618 "If field 57a is not present, no field 23E may contain TELE or PHON",
619 ));
620 }
621 }
622 }
623
624 errors
625 }
626
627 fn validate_field_23b(&self) -> Option<SwiftValidationError> {
629 let code = &self.field_23b.instruction_code;
630
631 if !Self::MT103_VALID_23B_CODES.contains(&code.as_str()) {
632 return Some(SwiftValidationError::format_error(
633 "T36",
634 "23B",
635 code,
636 &format!("One of: {}", Self::MT103_VALID_23B_CODES.join(", ")),
637 &format!(
638 "Bank operation code '{}' is not valid for MT103. Valid codes: {}",
639 code,
640 Self::MT103_VALID_23B_CODES.join(", ")
641 ),
642 ));
643 }
644
645 None
646 }
647
648 fn validate_field_23e(&self) -> Vec<SwiftValidationError> {
651 let mut errors = Vec::new();
652
653 if let Some(ref field_23e_vec) = self.field_23e {
654 let mut seen_codes = HashSet::new();
655 let mut code_positions: Vec<(String, usize)> = Vec::new();
656
657 for field_23e in field_23e_vec {
658 let code = &field_23e.instruction_code;
659
660 if !Self::MT103_VALID_23E_CODES.contains(&code.as_str()) {
662 errors.push(SwiftValidationError::format_error(
663 "T48",
664 "23E",
665 code,
666 &format!("One of: {}", Self::MT103_VALID_23E_CODES.join(", ")),
667 &format!(
668 "Instruction code '{}' is not valid for MT103. Valid codes: {}",
669 code,
670 Self::MT103_VALID_23E_CODES.join(", ")
671 ),
672 ));
673 }
674
675 if field_23e.additional_info.is_some()
677 && !Self::CODES_WITH_ADDITIONAL_INFO.contains(&code.as_str())
678 {
679 errors.push(SwiftValidationError::content_error(
680 "D97",
681 "23E",
682 code,
683 &format!(
684 "Additional information is only allowed for codes: {}. Code '{}' does not allow additional information",
685 Self::CODES_WITH_ADDITIONAL_INFO.join(", "),
686 code
687 ),
688 "Additional information in field 23E is only allowed for codes: PHON, PHOB, PHOI, TELE, TELB, TELI, HOLD, REPA",
689 ));
690 }
691
692 if seen_codes.contains(code) {
694 errors.push(SwiftValidationError::relation_error(
695 "E46",
696 "23E",
697 vec![],
698 &format!(
699 "Instruction code '{}' appears more than once. Same code must not be repeated",
700 code
701 ),
702 "When field 23E is repeated, the same code must not be present more than once",
703 ));
704 }
705 seen_codes.insert(code.clone());
706
707 if let Some(pos) = Self::FIELD_23E_CODE_ORDER.iter().position(|&c| c == code) {
709 code_positions.push((code.clone(), pos));
710 }
711 }
712
713 for i in 1..code_positions.len() {
715 if code_positions[i].1 < code_positions[i - 1].1 {
716 errors.push(SwiftValidationError::content_error(
717 "D98",
718 "23E",
719 &code_positions[i].0,
720 &format!(
721 "Instruction codes must appear in the following order: {}. Code '{}' appears out of order",
722 Self::FIELD_23E_CODE_ORDER.join(", "),
723 code_positions[i].0
724 ),
725 "When field 23E is repeated, codes must appear in specified order",
726 ));
727 break;
728 }
729 }
730
731 for field_23e in field_23e_vec {
733 let code = &field_23e.instruction_code;
734
735 for &(base_code, forbidden_codes) in Self::INVALID_23E_COMBINATIONS {
736 if code == base_code {
737 for other_field in field_23e_vec {
738 let other_code = &other_field.instruction_code;
739 if forbidden_codes.contains(&other_code.as_str()) {
740 errors.push(SwiftValidationError::content_error(
741 "D67",
742 "23E",
743 code,
744 &format!(
745 "Instruction code '{}' cannot be combined with code '{}'. Invalid combination",
746 code, other_code
747 ),
748 &format!(
749 "Code '{}' cannot be combined with: {}",
750 base_code,
751 forbidden_codes.join(", ")
752 ),
753 ));
754 }
755 }
756 }
757 }
758 }
759 }
760
761 errors
762 }
763
764 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
767 let mut all_errors = Vec::new();
768
769 if let Some(error) = self.validate_field_23b() {
771 all_errors.push(error);
772 if stop_on_first_error {
773 return all_errors;
774 }
775 }
776
777 let f23e_errors = self.validate_field_23e();
779 all_errors.extend(f23e_errors);
780 if stop_on_first_error && !all_errors.is_empty() {
781 return all_errors;
782 }
783
784 if let Some(error) = self.validate_c1_currency_exchange() {
786 all_errors.push(error);
787 if stop_on_first_error {
788 return all_errors;
789 }
790 }
791
792 let c3_errors = self.validate_c3_bank_op_instruction_codes();
794 all_errors.extend(c3_errors);
795 if stop_on_first_error && !all_errors.is_empty() {
796 return all_errors;
797 }
798
799 if let Some(error) = self.validate_c4_third_reimbursement() {
801 all_errors.push(error);
802 if stop_on_first_error {
803 return all_errors;
804 }
805 }
806
807 if let Some(error) = self.validate_c5_intermediary() {
809 all_errors.push(error);
810 if stop_on_first_error {
811 return all_errors;
812 }
813 }
814
815 if let Some(error) = self.validate_c6_field_56_restrictions() {
817 all_errors.push(error);
818 if stop_on_first_error {
819 return all_errors;
820 }
821 }
822
823 let c7_errors = self.validate_c7_charges();
825 all_errors.extend(c7_errors);
826 if stop_on_first_error && !all_errors.is_empty() {
827 return all_errors;
828 }
829
830 if let Some(error) = self.validate_c8_charges_instructed_amount() {
832 all_errors.push(error);
833 if stop_on_first_error {
834 return all_errors;
835 }
836 }
837
838 if let Some(error) = self.validate_c9_receiver_charges_currency() {
840 all_errors.push(error);
841 if stop_on_first_error {
842 return all_errors;
843 }
844 }
845
846 if let Some(error) = self.validate_c13_chqb_beneficiary_account() {
848 all_errors.push(error);
849 if stop_on_first_error {
850 return all_errors;
851 }
852 }
853
854 let c16_errors = self.validate_c16_teli_phoi_restriction();
856 all_errors.extend(c16_errors);
857 if stop_on_first_error && !all_errors.is_empty() {
858 return all_errors;
859 }
860
861 let c17_errors = self.validate_c17_tele_phon_restriction();
863 all_errors.extend(c17_errors);
864
865 all_errors
866 }
867}
868
869impl crate::traits::SwiftMessageBody for MT103 {
870 fn message_type() -> &'static str {
871 "103"
872 }
873
874 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
875 let mut parser = crate::parser::MessageParser::new(block4, "103");
876
877 let field_20 = parser.parse_field::<Field20>("20")?;
879
880 parser = parser.with_duplicates(true);
882 let mut field_13c = Vec::new();
883 while let Ok(field) = parser.parse_field::<Field13C>("13C") {
884 field_13c.push(field);
885 }
886 parser = parser.with_duplicates(false);
887
888 let field_23b = parser.parse_field::<Field23B>("23B")?;
890
891 parser = parser.with_duplicates(true);
893 let mut field_23e = Vec::new();
894 while let Ok(field) = parser.parse_field::<Field23E>("23E") {
895 field_23e.push(field);
896 }
897 parser = parser.with_duplicates(false);
898
899 let field_26t = parser.parse_optional_field::<Field26T>("26T")?;
901
902 let field_32a = parser.parse_field::<Field32A>("32A")?;
904
905 let field_33b = parser.parse_optional_field::<Field33B>("33B")?;
907 let field_36 = parser.parse_optional_field::<Field36>("36")?;
908
909 let field_50 = parser.parse_variant_field::<Field50OrderingCustomerAFK>("50")?;
911
912 let field_51a = parser.parse_optional_field::<Field51A>("51A")?;
914 let field_52 = parser.parse_optional_variant_field::<Field52OrderingInstitution>("52")?;
915 let field_53 = parser.parse_optional_variant_field::<Field53SenderCorrespondent>("53")?;
916 let field_54 = parser.parse_optional_variant_field::<Field54ReceiverCorrespondent>("54")?;
917 let field_55 =
918 parser.parse_optional_variant_field::<Field55ThirdReimbursementInstitution>("55")?;
919 let field_56 = parser.parse_optional_variant_field::<Field56Intermediary>("56")?;
920 let field_57 =
921 parser.parse_optional_variant_field::<Field57AccountWithInstitution>("57")?;
922
923 let field_59 = parser.parse_variant_field::<Field59>("59")?;
925
926 let field_70 = parser.parse_optional_field::<Field70>("70")?;
928
929 let field_71a = parser.parse_field::<Field71A>("71A")?;
931
932 parser = parser.with_duplicates(true);
934 let mut field_71f = Vec::new();
935 while let Ok(field) = parser.parse_field::<Field71F>("71F") {
936 field_71f.push(field);
937 }
938 parser = parser.with_duplicates(false);
939
940 let field_71g = parser.parse_optional_field::<Field71G>("71G")?;
942 let field_72 = parser.parse_optional_field::<Field72>("72")?;
943 let field_77b = parser.parse_optional_field::<Field77B>("77B")?;
944 let field_77t = parser.parse_optional_field::<Field77T>("77T")?;
945
946 verify_parser_complete(&parser)?;
948
949 Ok(Self {
950 field_20,
951 field_23b,
952 field_32a,
953 field_50,
954 field_59,
955 field_71a,
956 field_13c: if field_13c.is_empty() {
957 None
958 } else {
959 Some(field_13c)
960 },
961 field_23e: if field_23e.is_empty() {
962 None
963 } else {
964 Some(field_23e)
965 },
966 field_26t,
967 field_33b,
968 field_36,
969 field_51a,
970 field_52,
971 field_53,
972 field_54,
973 field_55,
974 field_56,
975 field_57,
976 field_70,
977 field_71f: if field_71f.is_empty() {
978 None
979 } else {
980 Some(field_71f)
981 },
982 field_71g,
983 field_72,
984 field_77b,
985 field_77t,
986 })
987 }
988
989 fn to_mt_string(&self) -> String {
990 MT103::to_mt_string(self)
992 }
993
994 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
995 MT103::validate_network_rules(self, stop_on_first_error)
997 }
998}
999
1000#[cfg(test)]
1001mod tests {
1002 use super::*;
1003
1004 #[test]
1005 fn test_mt103_parse() {
1006 let mt103_text = r#":20:123456789012345
1007:23B:CRED
1008:32A:241201USD1000000,00
1009:50K:/12345678901234567890
1010JOHN DOE
1011123 MAIN STREET
1012NEW YORK, NY 10001
1013:59:/98765432109876543210
1014JANE SMITH
1015456 OAK AVENUE
1016LOS ANGELES, CA 90001
1017:71A:OUR
1018-"#;
1019 let result = <MT103 as crate::traits::SwiftMessageBody>::parse_from_block4(mt103_text);
1020 assert!(result.is_ok());
1021 let mt103 = result.unwrap();
1022 assert_eq!(mt103.field_20.reference, "123456789012345");
1023 assert_eq!(mt103.field_23b.instruction_code, "CRED");
1024 assert_eq!(mt103.field_71a.code, "OUR");
1025 }
1026
1027 #[test]
1028 fn test_mt103_stp_compliance() {
1029 let mt103_text = r#":20:123456789012345
1030:23B:SPRI
1031:32A:241201USD1000000,00
1032:50K:/12345678901234567890
1033JOHN DOE
1034123 MAIN STREET
1035NEW YORK, NY 10001
1036:59:/98765432109876543210
1037JANE SMITH
1038456 OAK AVENUE
1039LOS ANGELES, CA 90001
1040:71A:OUR
1041-"#;
1042 let result = <MT103 as crate::traits::SwiftMessageBody>::parse_from_block4(mt103_text);
1043 assert!(result.is_ok());
1044 let mt103 = result.unwrap();
1045
1046 assert!(mt103.is_stp_compliant());
1048 }
1049}