1use super::context::StatefulAiContext;
7use super::types::BehaviorRules;
8use mockforge_foundation::Result;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct MutationAnalysis {
15 pub changed_fields: Vec<FieldChange>,
17 pub added_fields: Vec<String>,
19 pub removed_fields: Vec<String>,
21 pub validation_issues: Vec<ValidationIssue>,
23 pub mutation_type: MutationType,
25 pub confidence: f64,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct FieldChange {
32 pub field: String,
34 pub previous_value: Value,
36 pub new_value: Value,
38 pub change_type: ChangeType,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(rename_all = "lowercase")]
45pub enum ChangeType {
46 Modified,
48 TypeChanged,
50 Cleared,
52 Set,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
58#[serde(rename_all = "lowercase")]
59pub enum MutationType {
60 Create,
62 Update,
64 Delete,
66 PartialUpdate,
68 NoChange,
70 Invalid,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ValidationIssue {
77 pub field: Option<String>,
79 pub issue_type: ValidationIssueType,
81 pub severity: ValidationSeverity,
83 pub context: Value,
85 pub error_message: String,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
91#[serde(rename_all = "lowercase")]
92pub enum ValidationIssueType {
93 Required,
95 Format,
97 MinLength,
99 MaxLength,
101 Pattern,
103 Range,
105 Type,
107 Custom,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
113#[serde(rename_all = "lowercase")]
114pub enum ValidationSeverity {
115 Info,
117 Warning,
119 Error,
121 Critical,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127#[serde(rename_all = "lowercase")]
128pub enum ResponseType {
129 Success,
131 ValidationError,
133 NotFound,
135 Conflict,
137 Unauthorized,
139 Forbidden,
141 ServerError,
143}
144
145pub struct MutationAnalyzer {
147 rules: Option<BehaviorRules>,
149}
150
151impl MutationAnalyzer {
152 pub fn new() -> Self {
154 Self { rules: None }
155 }
156
157 pub fn with_rules(mut self, rules: BehaviorRules) -> Self {
159 self.rules = Some(rules);
160 self
161 }
162
163 pub async fn analyze_mutation(
168 &self,
169 current: &Value,
170 previous: Option<&Value>,
171 _context: &StatefulAiContext,
172 ) -> Result<MutationAnalysis> {
173 let mut changed_fields = Vec::new();
174 let mut added_fields = Vec::new();
175 let mut removed_fields = Vec::new();
176
177 if previous.is_none() {
179 if let Value::Object(obj) = current {
180 for (key, _value) in obj {
181 added_fields.push(key.clone());
182 }
183 }
184
185 return Ok(MutationAnalysis {
186 changed_fields,
187 added_fields,
188 removed_fields,
189 validation_issues: Vec::new(),
190 mutation_type: MutationType::Create,
191 confidence: 0.9,
192 });
193 }
194
195 let previous = previous.unwrap();
196
197 if let (Value::Object(current_obj), Value::Object(prev_obj)) = (current, previous) {
199 for (key, current_val) in current_obj {
201 if let Some(prev_val) = prev_obj.get(key) {
202 if current_val != prev_val {
203 let change_type = self.determine_change_type(prev_val, current_val);
204 changed_fields.push(FieldChange {
205 field: key.clone(),
206 previous_value: prev_val.clone(),
207 new_value: current_val.clone(),
208 change_type,
209 });
210 }
211 } else {
212 added_fields.push(key.clone());
214 }
215 }
216
217 for key in prev_obj.keys() {
219 if !current_obj.contains_key(key) {
220 removed_fields.push(key.clone());
221 }
222 }
223 }
224
225 let validation_issues = self
227 .detect_validation_issues(&MutationAnalysis {
228 changed_fields: changed_fields.clone(),
229 added_fields: added_fields.clone(),
230 removed_fields: removed_fields.clone(),
231 validation_issues: Vec::new(),
232 mutation_type: MutationType::NoChange,
233 confidence: 0.0,
234 })
235 .await?;
236
237 let mutation_type = self.determine_mutation_type(
239 &changed_fields,
240 &added_fields,
241 &removed_fields,
242 &validation_issues,
243 );
244
245 let confidence = self.calculate_confidence(&mutation_type, &validation_issues);
247
248 Ok(MutationAnalysis {
249 changed_fields,
250 added_fields,
251 removed_fields,
252 validation_issues,
253 mutation_type,
254 confidence,
255 })
256 }
257
258 pub async fn detect_validation_issues(
260 &self,
261 mutation: &MutationAnalysis,
262 ) -> Result<Vec<ValidationIssue>> {
263 let mut issues = Vec::new();
264
265 if mutation.mutation_type == MutationType::Create {
267 if let Some(ref rules) = self.rules {
269 for (resource_name, schema) in &rules.schemas {
270 if let Some(required) = schema.get("required").and_then(|r| r.as_array()) {
271 for field_name in required {
272 if let Some(field_str) = field_name.as_str() {
273 if !mutation.added_fields.contains(&field_str.to_string()) {
275 issues.push(ValidationIssue {
276 field: Some(field_str.to_string()),
277 issue_type: ValidationIssueType::Required,
278 severity: ValidationSeverity::Error,
279 context: serde_json::json!({
280 "resource": resource_name,
281 "field": field_str
282 }),
283 error_message: format!("Field '{}' is required", field_str),
284 });
285 }
286 }
287 }
288 }
289 }
290 }
291 }
292
293 for change in &mutation.changed_fields {
295 if change.change_type == ChangeType::TypeChanged {
297 issues.push(ValidationIssue {
298 field: Some(change.field.clone()),
299 issue_type: ValidationIssueType::Type,
300 severity: ValidationSeverity::Error,
301 context: serde_json::json!({
302 "previous_type": self.value_type(&change.previous_value),
303 "new_type": self.value_type(&change.new_value)
304 }),
305 error_message: format!(
306 "Field '{}' cannot change type from {} to {}",
307 change.field,
308 self.value_type(&change.previous_value),
309 self.value_type(&change.new_value)
310 ),
311 });
312 }
313
314 if change.change_type == ChangeType::Cleared {
316 if let Some(ref rules) = self.rules {
317 for schema in rules.schemas.values() {
319 if let Some(required) = schema.get("required").and_then(|r| r.as_array()) {
320 if required
321 .iter()
322 .any(|f| f.as_str().map(|s| s == change.field).unwrap_or(false))
323 {
324 issues.push(ValidationIssue {
325 field: Some(change.field.clone()),
326 issue_type: ValidationIssueType::Required,
327 severity: ValidationSeverity::Error,
328 context: serde_json::json!({
329 "field": change.field
330 }),
331 error_message: format!(
332 "Field '{}' cannot be cleared",
333 change.field
334 ),
335 });
336 }
337 }
338 }
339 }
340 }
341 }
342
343 Ok(issues)
344 }
345
346 pub fn infer_response_type(
348 &self,
349 mutation: &MutationAnalysis,
350 _context: &StatefulAiContext,
351 ) -> ResponseType {
352 if !mutation.validation_issues.is_empty() {
354 let has_errors = mutation
355 .validation_issues
356 .iter()
357 .any(|i| i.severity >= ValidationSeverity::Error);
358 if has_errors {
359 return ResponseType::ValidationError;
360 }
361 }
362
363 match mutation.mutation_type {
365 MutationType::Create => ResponseType::Success,
366 MutationType::Update | MutationType::PartialUpdate => ResponseType::Success,
367 MutationType::Delete => ResponseType::Success,
368 MutationType::Invalid => ResponseType::ValidationError,
369 MutationType::NoChange => ResponseType::Success,
370 }
371 }
372
373 fn determine_change_type(&self, previous: &Value, current: &Value) -> ChangeType {
377 if previous.is_null() || (previous.is_string() && previous.as_str() == Some("")) {
379 return ChangeType::Set;
380 }
381
382 if current.is_null() || (current.is_string() && current.as_str() == Some("")) {
384 return ChangeType::Cleared;
385 }
386
387 if std::mem::discriminant(previous) != std::mem::discriminant(current) {
389 return ChangeType::TypeChanged;
390 }
391
392 ChangeType::Modified
394 }
395
396 fn determine_mutation_type(
398 &self,
399 changed_fields: &[FieldChange],
400 added_fields: &[String],
401 removed_fields: &[String],
402 validation_issues: &[ValidationIssue],
403 ) -> MutationType {
404 if validation_issues.iter().any(|i| i.severity == ValidationSeverity::Critical) {
406 return MutationType::Invalid;
407 }
408
409 if added_fields.len() > changed_fields.len() && removed_fields.is_empty() {
411 return MutationType::Create;
412 }
413
414 if !removed_fields.is_empty()
416 && removed_fields.len() > added_fields.len() + changed_fields.len()
417 {
418 return MutationType::Delete;
419 }
420
421 if !changed_fields.is_empty() {
423 if !added_fields.is_empty() || !removed_fields.is_empty() {
425 return MutationType::Update;
426 }
427 return MutationType::Update;
432 }
433
434 MutationType::NoChange
435 }
436
437 fn calculate_confidence(
439 &self,
440 mutation_type: &MutationType,
441 validation_issues: &[ValidationIssue],
442 ) -> f64 {
443 let mut confidence = 0.5; match mutation_type {
447 MutationType::Create | MutationType::Delete => confidence += 0.3,
448 MutationType::Update | MutationType::PartialUpdate => confidence += 0.2,
449 MutationType::Invalid => confidence -= 0.2,
450 MutationType::NoChange => confidence = 0.1,
451 }
452
453 if !validation_issues.is_empty() {
455 confidence -= 0.1 * validation_issues.len() as f64;
456 }
457
458 confidence.clamp(0.0, 1.0)
459 }
460
461 fn value_type(&self, value: &Value) -> &str {
463 match value {
464 Value::Null => "null",
465 Value::Bool(_) => "boolean",
466 Value::Number(_) => "number",
467 Value::String(_) => "string",
468 Value::Array(_) => "array",
469 Value::Object(_) => "object",
470 }
471 }
472}
473
474impl Default for MutationAnalyzer {
475 fn default() -> Self {
476 Self::new()
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483 use serde_json::json;
484
485 #[tokio::test]
486 async fn test_analyze_mutation_create() {
487 let analyzer = MutationAnalyzer::new();
488 let config = super::super::config::IntelligentBehaviorConfig::default();
489 let context = StatefulAiContext::new("test_session", config);
490
491 let current = json!({
492 "name": "Alice",
493 "email": "alice@example.com"
494 });
495
496 let analysis = analyzer.analyze_mutation(¤t, None, &context).await.unwrap();
497
498 assert_eq!(analysis.mutation_type, MutationType::Create);
499 assert!(!analysis.added_fields.is_empty());
500 }
501
502 #[tokio::test]
503 async fn test_analyze_mutation_update() {
504 let analyzer = MutationAnalyzer::new();
505 let config = super::super::config::IntelligentBehaviorConfig::default();
506 let context = StatefulAiContext::new("test_session", config);
507
508 let previous = json!({
509 "name": "Alice",
510 "email": "alice@example.com"
511 });
512
513 let current = json!({
514 "name": "Alice Smith",
515 "email": "alice@example.com"
516 });
517
518 let analysis =
519 analyzer.analyze_mutation(¤t, Some(&previous), &context).await.unwrap();
520
521 assert_eq!(analysis.mutation_type, MutationType::Update);
522 assert!(!analysis.changed_fields.is_empty());
523 }
524
525 #[tokio::test]
526 async fn test_determine_change_type() {
527 let analyzer = MutationAnalyzer::new();
528
529 let prev = json!("old");
530 let curr = json!("new");
531 assert_eq!(analyzer.determine_change_type(&prev, &curr), ChangeType::Modified);
532
533 let prev = json!(null);
534 let curr = json!("value");
535 assert_eq!(analyzer.determine_change_type(&prev, &curr), ChangeType::Set);
536
537 let prev = json!("value");
538 let curr = json!(null);
539 assert_eq!(analyzer.determine_change_type(&prev, &curr), ChangeType::Cleared);
540 }
541
542 #[tokio::test]
543 async fn test_infer_response_type() {
544 let analyzer = MutationAnalyzer::new();
545 let config = super::super::config::IntelligentBehaviorConfig::default();
546 let context = StatefulAiContext::new("test_session", config);
547
548 let mutation = MutationAnalysis {
549 changed_fields: Vec::new(),
550 added_fields: vec!["name".to_string()],
551 removed_fields: Vec::new(),
552 validation_issues: Vec::new(),
553 mutation_type: MutationType::Create,
554 confidence: 0.9,
555 };
556
557 let response_type = analyzer.infer_response_type(&mutation, &context);
558 assert_eq!(response_type, ResponseType::Success);
559 }
560}