swift_mt_message/messages/mt192.rs
1use crate::errors::SwiftValidationError;
2use crate::fields::*;
3use crate::parser::utils::*;
4use serde::{Deserialize, Serialize};
5
6/// **MT192: Request for Cancellation**
7///
8/// Request to cancel a previously sent payment message before execution.
9///
10/// **Usage:** Payment cancellation requests, transaction reversal
11/// **Category:** Category 1 (Customer Payments & Cheques)
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct MT192 {
14 /// Sender's Reference (Field 20)
15 #[serde(rename = "20")]
16 pub field_20: Field20,
17
18 /// Related Reference (Field 21)
19 #[serde(rename = "21")]
20 pub field_21: Field21NoOption,
21
22 /// MT and Date (Field 11S)
23 #[serde(rename = "11S")]
24 pub field_11s: Field11S,
25
26 /// Narrative (Field 79)
27 #[serde(rename = "79", skip_serializing_if = "Option::is_none")]
28 pub field_79: Option<Field79>,
29}
30
31impl MT192 {
32 /// Parse message from Block 4 content
33 pub fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
34 let mut parser = crate::parser::MessageParser::new(block4, "192");
35
36 // Parse mandatory fields in order: 20, 21, 11S
37 let field_20 = parser.parse_field::<Field20>("20")?;
38 let field_21 = parser.parse_field::<Field21NoOption>("21")?;
39 let field_11s = parser.parse_field::<Field11S>("11S")?;
40
41 // Parse optional field 79
42 let field_79 = parser.parse_optional_field::<Field79>("79")?;
43
44 Ok(MT192 {
45 field_20,
46 field_21,
47 field_11s,
48 field_79,
49 })
50 }
51
52 /// Parse from generic SWIFT input (tries to detect blocks)
53 pub fn parse(input: &str) -> Result<Self, crate::errors::ParseError> {
54 let block4 = extract_block4(input)?;
55 Self::parse_from_block4(&block4)
56 }
57
58 // ========================================================================
59 // NETWORK VALIDATION RULES (SR 2025 MT192)
60 // ========================================================================
61
62 /// Field 79 valid cancellation reason codes for MT192
63 const MT192_VALID_79_CODES: &'static [&'static str] = &[
64 "AGNT", // Incorrect Agent
65 "AM09", // Wrong Amount
66 "COVR", // Cover Cancelled or Returned
67 "CURR", // Incorrect Currency
68 "CUST", // Requested by Customer
69 "CUTA", // Cancel upon Unable to Apply
70 "DUPL", // Duplicate Payment
71 "FRAD", // Fraudulent Origin
72 "TECH", // Technical Problem
73 "UPAY", // Undue Payment
74 ];
75
76 // ========================================================================
77 // HELPER METHODS
78 // ========================================================================
79
80 /// Check if Field 79 is present
81 fn has_field_79(&self) -> bool {
82 self.field_79.is_some()
83 }
84
85 /// Extract cancellation reason code from Field 79 if present
86 /// Returns the 4-character code from the first line if formatted as /CODE/...
87 fn get_field_79_cancellation_code(&self) -> Option<String> {
88 if let Some(ref field_79) = self.field_79 {
89 // Get the first line from the information vector
90 let first_line = field_79.information.first()?;
91 if first_line.starts_with('/') {
92 let parts: Vec<&str> = first_line.split('/').collect();
93 if parts.len() >= 2 && parts[1].len() == 4 {
94 return Some(parts[1].to_string());
95 }
96 }
97 }
98 None
99 }
100
101 // ========================================================================
102 // VALIDATION RULES (C1)
103 // ========================================================================
104
105 /// C1: Field 79 or Copy of Mandatory Fields Requirement (Error code: C25)
106 /// Field 79 or a copy of at least the mandatory fields of the original message
107 /// or both must be present.
108 ///
109 /// **Implementation Note**: This implementation only validates that Field 79 is present
110 /// since we don't currently support parsing the "copy of mandatory fields" section.
111 /// A full implementation would check for either Field 79 OR copied fields OR both.
112 fn validate_c1_field_79_or_copy(&self) -> Option<SwiftValidationError> {
113 // Check if Field 79 is present
114 // Note: In a complete implementation, we would also check for copied fields
115 // from the original message, but this is not currently supported
116 if !self.has_field_79() {
117 return Some(SwiftValidationError::content_error(
118 "C25",
119 "79",
120 "",
121 "Field 79 (Narrative Description) or a copy of at least the mandatory fields of the original message must be present",
122 "Either field 79 or a copy of at least the mandatory fields of the original message or both must be present to identify the transaction to be cancelled",
123 ));
124 }
125
126 None
127 }
128
129 /// Validate Field 79 cancellation reason codes (if present)
130 /// While not explicitly a network validation rule, this validates the cancellation
131 /// reason codes are from the allowed list when present in Field 79.
132 fn validate_field_79_codes(&self) -> Vec<SwiftValidationError> {
133 let mut errors = Vec::new();
134
135 if let Some(code) = self.get_field_79_cancellation_code()
136 && !Self::MT192_VALID_79_CODES.contains(&code.as_str())
137 {
138 errors.push(SwiftValidationError::content_error(
139 "T47",
140 "79",
141 &code,
142 &format!(
143 "Cancellation reason code '{}' is not valid for MT192. Valid codes: {}",
144 code,
145 Self::MT192_VALID_79_CODES.join(", ")
146 ),
147 "Cancellation reason must be one of the allowed codes when using /CODE/ format in field 79",
148 ));
149 }
150
151 errors
152 }
153
154 /// Main validation method - validates all network rules
155 /// Returns array of validation errors, respects stop_on_first_error flag
156 pub fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
157 let mut all_errors = Vec::new();
158
159 // C1: Field 79 or Copy of Mandatory Fields Requirement
160 if let Some(error) = self.validate_c1_field_79_or_copy() {
161 all_errors.push(error);
162 if stop_on_first_error {
163 return all_errors;
164 }
165 }
166
167 // Validate Field 79 cancellation codes (if present)
168 let f79_errors = self.validate_field_79_codes();
169 all_errors.extend(f79_errors);
170
171 all_errors
172 }
173}
174
175// Implement the SwiftMessageBody trait for MT192
176impl crate::traits::SwiftMessageBody for MT192 {
177 fn message_type() -> &'static str {
178 "192"
179 }
180
181 fn parse_from_block4(block4: &str) -> Result<Self, crate::errors::ParseError> {
182 Self::parse_from_block4(block4)
183 }
184
185 fn to_mt_string(&self) -> String {
186 let mut result = String::new();
187
188 append_field(&mut result, &self.field_20);
189 append_field(&mut result, &self.field_21);
190 append_field(&mut result, &self.field_11s);
191 append_optional_field(&mut result, &self.field_79);
192
193 finalize_mt_string(result, false)
194 }
195
196 fn validate_network_rules(&self, stop_on_first_error: bool) -> Vec<SwiftValidationError> {
197 // Call the existing public method implementation
198 MT192::validate_network_rules(self, stop_on_first_error)
199 }
200}