swift_mt_message/plugin/
validate.rs

1use crate::{ParseError, SwiftParser, SwiftValidationError};
2use async_trait::async_trait;
3use dataflow_rs::engine::error::DataflowError;
4use dataflow_rs::engine::{
5    AsyncFunctionHandler, FunctionConfig,
6    error::Result,
7    message::{Change, Message},
8};
9use datalogic_rs::DataLogic;
10use serde_json::{Value, json};
11use std::sync::Arc;
12use tracing::{debug, instrument};
13
14pub struct Validate;
15
16#[async_trait]
17impl AsyncFunctionHandler for Validate {
18    #[instrument(skip(self, message, config, _datalogic))]
19    async fn execute(
20        &self,
21        message: &mut Message,
22        config: &FunctionConfig,
23        _datalogic: Arc<DataLogic>,
24    ) -> Result<(usize, Vec<Change>)> {
25        debug!("Starting MT message validation");
26
27        // Extract custom configuration
28        let input = match config {
29            FunctionConfig::Custom { input, .. } => input,
30            _ => {
31                return Err(DataflowError::Validation(
32                    "Invalid configuration type".to_string(),
33                ));
34            }
35        };
36
37        let source_field = input.get("source").and_then(Value::as_str).ok_or_else(|| {
38            DataflowError::Validation("'source' parameter is required".to_string())
39        })?;
40
41        let target_field = input.get("target").and_then(Value::as_str).ok_or_else(|| {
42            DataflowError::Validation("'target' parameter is required".to_string())
43        })?;
44
45        // Get the MT message to validate
46        let mt_content = if source_field == "payload" {
47            // Extract string value from the payload JSON
48            if let Some(s) = message.payload.as_str() {
49                s.to_string()
50            } else {
51                // If it's not a string directly, try to convert
52                message.payload.to_string().trim_matches('"').to_string()
53            }
54        } else {
55            // Check if the field contains an object with mt_message (from generate_mt output)
56            let field_value = message.data().get(source_field).ok_or_else(|| {
57                DataflowError::Validation(format!(
58                    "MT message field '{}' not found in message data",
59                    source_field
60                ))
61            })?;
62
63            // If it's an object with mt_message field, extract that
64            if let Some(mt_msg) = field_value.get("mt_message").and_then(Value::as_str) {
65                mt_msg.to_string()
66            } else if let Some(s) = field_value.as_str() {
67                // If it's a direct string, use it
68                s.to_string()
69            } else {
70                return Err(DataflowError::Validation(format!(
71                    "Field '{}' does not contain a valid MT message",
72                    source_field
73                )));
74            }
75        };
76
77        debug!(
78            source_field = %source_field,
79            target_field = %target_field,
80            "Validating MT message"
81        );
82
83        // Perform validation
84        let validation_result = self.validate_mt_message(&mt_content)?;
85
86        // Store validation result
87        message
88            .data_mut()
89            .as_object_mut()
90            .unwrap()
91            .insert(target_field.to_string(), validation_result.clone());
92
93        // Update metadata with validation summary
94        message.metadata_mut().as_object_mut().unwrap().insert(
95            "validation".to_string(),
96            json!({
97                "validated": true,
98                "timestamp": chrono::Utc::now().to_rfc3339(),
99            }),
100        );
101
102        message.invalidate_context_cache();
103
104        Ok((
105            200,
106            vec![Change {
107                path: Arc::from(format!("data.{}", target_field)),
108                old_value: Arc::new(Value::Null),
109                new_value: Arc::new(validation_result),
110            }],
111        ))
112    }
113}
114
115impl Validate {
116    fn validate_mt_message(&self, mt_content: &str) -> Result<Value> {
117        let mut errors = Vec::new();
118        let mut message_type: Option<String> = None;
119
120        // Try to parse the message
121        match SwiftParser::parse_auto(mt_content) {
122            Ok(parsed_message) => {
123                // Extract message type from parsed message
124                message_type = Some(parsed_message.message_type().to_string());
125
126                // Use the new network validation rules from SwiftMessageBody trait
127                let validation_errors = self.validate_network_rules(&parsed_message);
128
129                if !validation_errors.is_empty() {
130                    // Convert SwiftValidationError instances to formatted error strings
131                    for validation_error in validation_errors {
132                        errors.push(self.format_validation_error(&validation_error));
133                    }
134                }
135            }
136            Err(parse_error) => {
137                // Accumulate parse errors - the Display trait already provides good error messages
138                errors.push(format!("Parse error: {}", parse_error));
139
140                // Add more specific error details for certain error types
141                match &parse_error {
142                    ParseError::InvalidFieldFormat(e) => {
143                        errors.push(format!(
144                            "Field {} - {}: Invalid value '{}', expected format: {}",
145                            e.field_tag, e.component_name, e.value, e.format_spec
146                        ));
147                    }
148                    ParseError::MissingRequiredField {
149                        field_tag,
150                        field_name,
151                        ..
152                    } => {
153                        errors.push(format!(
154                            "Missing required field: {} ({})",
155                            field_tag, field_name
156                        ));
157                    }
158                    ParseError::InvalidFormat { message } => {
159                        errors.push(format!("Invalid message format: {}", message));
160                    }
161                    ParseError::ValidationFailed {
162                        errors: validation_errors,
163                    } => {
164                        for validation_error in validation_errors {
165                            errors.push(validation_error.to_string());
166                        }
167                    }
168                    _ => {
169                        // Other error types are already covered by the Display trait
170                    }
171                }
172            }
173        }
174
175        let is_valid = errors.is_empty();
176
177        let mut result = json!({
178            "valid": is_valid,
179            "errors": errors,
180            "timestamp": chrono::Utc::now().to_rfc3339(),
181        });
182
183        // Add message_type if detected
184        if let Some(msg_type) = message_type {
185            result
186                .as_object_mut()
187                .unwrap()
188                .insert("message_type".to_string(), json!(msg_type));
189        }
190
191        Ok(result)
192    }
193
194    /// Validate network rules on parsed message using the SwiftMessageBody trait
195    fn validate_network_rules(
196        &self,
197        parsed_message: &crate::parsed_message::ParsedSwiftMessage,
198    ) -> Vec<SwiftValidationError> {
199        use crate::parsed_message::ParsedSwiftMessage;
200
201        // Call validate_network_rules on the message body (stop_on_first_error = false to get all errors)
202        match parsed_message {
203            ParsedSwiftMessage::MT101(msg) => msg.fields.validate_network_rules(false),
204            ParsedSwiftMessage::MT103(msg) => msg.fields.validate_network_rules(false),
205            ParsedSwiftMessage::MT104(msg) => msg.fields.validate_network_rules(false),
206            ParsedSwiftMessage::MT107(msg) => msg.fields.validate_network_rules(false),
207            ParsedSwiftMessage::MT110(msg) => msg.fields.validate_network_rules(false),
208            ParsedSwiftMessage::MT111(msg) => msg.fields.validate_network_rules(false),
209            ParsedSwiftMessage::MT112(msg) => msg.fields.validate_network_rules(false),
210            ParsedSwiftMessage::MT190(msg) => msg.fields.validate_network_rules(false),
211            ParsedSwiftMessage::MT191(msg) => msg.fields.validate_network_rules(false),
212            ParsedSwiftMessage::MT192(msg) => msg.fields.validate_network_rules(false),
213            ParsedSwiftMessage::MT196(msg) => msg.fields.validate_network_rules(false),
214            ParsedSwiftMessage::MT199(msg) => msg.fields.validate_network_rules(false),
215            ParsedSwiftMessage::MT200(msg) => msg.fields.validate_network_rules(false),
216            ParsedSwiftMessage::MT202(msg) => msg.fields.validate_network_rules(false),
217            ParsedSwiftMessage::MT204(msg) => msg.fields.validate_network_rules(false),
218            ParsedSwiftMessage::MT205(msg) => msg.fields.validate_network_rules(false),
219            ParsedSwiftMessage::MT210(msg) => msg.fields.validate_network_rules(false),
220            ParsedSwiftMessage::MT290(msg) => msg.fields.validate_network_rules(false),
221            ParsedSwiftMessage::MT291(msg) => msg.fields.validate_network_rules(false),
222            ParsedSwiftMessage::MT292(msg) => msg.fields.validate_network_rules(false),
223            ParsedSwiftMessage::MT296(msg) => msg.fields.validate_network_rules(false),
224            ParsedSwiftMessage::MT299(msg) => msg.fields.validate_network_rules(false),
225            ParsedSwiftMessage::MT900(msg) => msg.fields.validate_network_rules(false),
226            ParsedSwiftMessage::MT910(msg) => msg.fields.validate_network_rules(false),
227            ParsedSwiftMessage::MT920(msg) => msg.fields.validate_network_rules(false),
228            ParsedSwiftMessage::MT935(msg) => msg.fields.validate_network_rules(false),
229            ParsedSwiftMessage::MT940(msg) => msg.fields.validate_network_rules(false),
230            ParsedSwiftMessage::MT941(msg) => msg.fields.validate_network_rules(false),
231            ParsedSwiftMessage::MT942(msg) => msg.fields.validate_network_rules(false),
232            ParsedSwiftMessage::MT950(msg) => msg.fields.validate_network_rules(false),
233        }
234    }
235
236    /// Format a SwiftValidationError into a human-readable string
237    fn format_validation_error(&self, error: &SwiftValidationError) -> String {
238        use crate::errors::SwiftValidationError;
239
240        match error {
241            SwiftValidationError::Format(err) => {
242                format!(
243                    "[{}] Field {}: {} - Invalid value '{}' (expected: {})",
244                    err.code, err.field, err.message, err.value, err.expected
245                )
246            }
247            SwiftValidationError::Content(err) => {
248                format!(
249                    "[{}] Field {}: {} - Content '{}' violates requirement: {}",
250                    err.code, err.field, err.message, err.content, err.requirements
251                )
252            }
253            SwiftValidationError::Relation(err) => {
254                let related = if !err.related_fields.is_empty() {
255                    format!(" (related: {})", err.related_fields.join(", "))
256                } else {
257                    String::new()
258                };
259                let instruction = err
260                    .instruction_context
261                    .as_ref()
262                    .map(|ctx| format!(" [{}]", ctx))
263                    .unwrap_or_default();
264                format!(
265                    "[{}] Field {}: {}{}{}",
266                    err.code, err.field, err.message, related, instruction
267                )
268            }
269            SwiftValidationError::Business(err) => {
270                let related = if !err.related_fields.is_empty() {
271                    format!(" (related: {})", err.related_fields.join(", "))
272                } else {
273                    String::new()
274                };
275                format!(
276                    "[{}] Field {}: {} - Rule: {}{}",
277                    err.code, err.field, err.message, err.rule_description, related
278                )
279            }
280            SwiftValidationError::General(err) => {
281                let category = err
282                    .category
283                    .as_ref()
284                    .map(|c| format!(" [{}]", c))
285                    .unwrap_or_default();
286                format!(
287                    "[{}] Field {}: {} - Value '{}'{}",
288                    err.code, err.field, err.message, err.value, category
289                )
290            }
291        }
292    }
293}