mx_message/plugin/
common.rs1use dataflow_rs::engine::error::{DataflowError, Result};
7use serde_json::Value;
8
9pub fn detect_format(payload: &str) -> String {
19 let trimmed = payload.trim();
20 if trimmed.starts_with('<') {
21 "xml".to_string()
22 } else if trimmed.starts_with('{') || trimmed.starts_with('[') {
23 "json".to_string()
24 } else {
25 "xml".to_string() }
27}
28
29pub fn map_document_element_to_message_type(element_name: &str) -> Result<String> {
38 let message_type = match element_name {
39 "FIToFICstmrCdtTrf" | "FIToFICustomerCreditTransferV08" => "pacs.008",
40 "FIToFIPmtStsRpt" | "FIToFIPaymentStatusReportV10" => "pacs.002",
41 "FinInstnCdtTrf" | "FinancialInstitutionCreditTransferV08" => "pacs.009",
42 "PmtRtr" | "PaymentReturnV09" => "pacs.004",
43 "FIToFICstmrDrctDbt" | "FIToFICustomerDirectDebitV08" => "pacs.003",
44 "FIDrctDbt" | "FinancialInstitutionDirectDebitV03" => "pacs.010",
45 "BkToCstmrStmt" | "BankToCustomerStatementV08" => "camt.053",
46 "BkToCstmrDbtCdtNtfctn" | "BankToCustomerDebitCreditNotificationV08" => "camt.054",
47 "AcctRptgReq" | "AccountReportingRequestV05" => "camt.060",
48 "BkToCstmrAcctRpt" | "BankToCustomerAccountReportV08" => "camt.052",
49 "RsltnOfInvstgtn" | "ResolutionOfInvestigationV09" => "camt.029",
50 "Rct" | "ReceiptV08" => "camt.025",
51 "FIToFIPmtCxlReq" | "FIToFIPaymentCancellationRequestV08" => "camt.056",
52 "NtfctnToRcv" | "NotificationToReceiveV06" => "camt.057",
53 "CstmrCdtTrfInitn" | "CustomerCreditTransferInitiationV09" => "pain.001",
54 "CstmrDrctDbtInitn" | "CustomerDirectDebitInitiationV08" => "pain.008",
55 "ChqPresntmntNtfctn" | "ChequePresentmentNotificationV01" => "camt.107",
56 "ChqCxlOrStopReq" | "ChequeCancellationOrStopRequestV01" => "camt.108",
57 "ChqCxlOrStopRpt" | "ChequeCancellationOrStopReportV01" => "camt.109",
58 "ClmNonRct" | "ClaimNonReceiptV07" => "camt.027",
59 _ => {
60 return Err(DataflowError::Validation(format!(
61 "Unknown document element: {}",
62 element_name
63 )));
64 }
65 };
66 Ok(message_type.to_string())
67}
68
69pub fn extract_message_type(data: &Value) -> Result<String> {
83 if let Some(mt) = data.get("message_type").and_then(Value::as_str) {
85 return Ok(mt.to_string());
86 }
87
88 if let Some(doc) = data.get("Document") {
90 if let Some(obj) = doc.as_object()
92 && let Some(first_key) = obj.keys().next()
93 {
94 return map_document_element_to_message_type(first_key);
96 }
97 }
98
99 Err(DataflowError::Validation(
100 "Could not determine message type from parsed data".to_string(),
101 ))
102}
103
104pub fn extract_message_type_from_xml(xml_str: &str) -> Result<String> {
116 let xml_str = xml_str.trim();
117
118 if let Some(doc_start) = xml_str.find("<Document") {
120 if let Some(doc_end) = xml_str[doc_start..].find('>') {
122 let after_doc = &xml_str[doc_start + doc_end + 1..];
123
124 if let Some(elem_start) = after_doc.find('<')
126 && after_doc.as_bytes()[elem_start + 1] != b'/'
127 {
128 let elem_name_start = elem_start + 1;
129 let elem_name_end = after_doc[elem_name_start..]
131 .find([' ', '>', '/'])
132 .map(|i| elem_name_start + i)
133 .unwrap_or(after_doc.len());
134
135 let element_name = &after_doc[elem_name_start..elem_name_end];
136 let element_name = if let Some(colon_pos) = element_name.rfind(':') {
138 &element_name[colon_pos + 1..]
139 } else {
140 element_name
141 };
142
143 return map_document_element_to_message_type(element_name);
144 }
145 }
146 }
147
148 Err(DataflowError::Validation(
149 "Could not extract message type from XML".to_string(),
150 ))
151}
152
153pub fn extract_mx_content(
169 message_data: &Value,
170 field_name: &str,
171 message_payload: &Value,
172) -> Result<String> {
173 if field_name == "payload" {
174 if let Some(s) = message_payload.as_str() {
176 Ok(s.to_string())
177 } else {
178 Ok(message_payload.to_string().trim_matches('"').to_string())
180 }
181 } else {
182 let field_value = message_data.get(field_name).ok_or_else(|| {
184 DataflowError::Validation(format!(
185 "MX message field '{}' not found in message data",
186 field_name
187 ))
188 })?;
189
190 if let Some(mx_msg) = field_value.get("mx_message").and_then(Value::as_str) {
192 Ok(mx_msg.to_string())
193 } else if let Some(s) = field_value.as_str() {
194 Ok(s.to_string())
196 } else {
197 Err(DataflowError::Validation(format!(
198 "Field '{}' does not contain a valid MX message",
199 field_name
200 )))
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use serde_json::json;
209
210 #[test]
211 fn test_detect_format() {
212 assert_eq!(detect_format("<?xml version=\"1.0\"?>"), "xml");
213 assert_eq!(detect_format("<Document>"), "xml");
214 assert_eq!(detect_format("{\"key\": \"value\"}"), "json");
215 assert_eq!(detect_format("[1, 2, 3]"), "json");
216 assert_eq!(detect_format("unknown"), "xml"); assert_eq!(detect_format(" <tag> "), "xml"); }
219
220 #[test]
221 fn test_map_document_element_to_message_type() {
222 assert_eq!(
223 map_document_element_to_message_type("FIToFICstmrCdtTrf").unwrap(),
224 "pacs.008"
225 );
226 assert_eq!(
227 map_document_element_to_message_type("BkToCstmrStmt").unwrap(),
228 "camt.053"
229 );
230 assert_eq!(
231 map_document_element_to_message_type("CstmrCdtTrfInitn").unwrap(),
232 "pain.001"
233 );
234 assert!(map_document_element_to_message_type("UnknownElement").is_err());
235 }
236
237 #[test]
238 fn test_extract_message_type() {
239 let data = json!({"message_type": "pacs.008", "other": "data"});
241 assert_eq!(extract_message_type(&data).unwrap(), "pacs.008");
242
243 let data = json!({
245 "Document": {
246 "FIToFICstmrCdtTrf": {
247 "GrpHdr": {}
248 }
249 }
250 });
251 assert_eq!(extract_message_type(&data).unwrap(), "pacs.008");
252
253 let data = json!({"other": "data"});
255 assert!(extract_message_type(&data).is_err());
256 }
257
258 #[test]
259 fn test_extract_mx_content() {
260 let payload = json!("test content");
261 let data = json!({
262 "field1": "direct string",
263 "field2": {
264 "mx_message": "nested message"
265 }
266 });
267
268 assert_eq!(
270 extract_mx_content(&data, "payload", &payload).unwrap(),
271 "test content"
272 );
273
274 assert_eq!(
276 extract_mx_content(&data, "field1", &payload).unwrap(),
277 "direct string"
278 );
279
280 assert_eq!(
282 extract_mx_content(&data, "field2", &payload).unwrap(),
283 "nested message"
284 );
285
286 assert!(extract_mx_content(&data, "missing", &payload).is_err());
288 }
289}