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