mockforge-intelligence 0.3.143

AI-powered behavior, response generation, and behavioral cloning for MockForge
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
//! AI-driven validation error generation
//!
//! This module generates realistic, context-aware validation error messages
//! using LLMs, learning from example error responses to create human-like
//! error messages.

use super::config::BehaviorModelConfig;
use super::llm_client::LlmClient;
use super::mutation_analyzer::{ValidationIssue, ValidationIssueType, ValidationSeverity};
use super::types::LlmGenerationRequest;
use mockforge_foundation::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

/// Example error response for learning validation error formats
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationErrorExample {
    /// Field that caused the error (if applicable)
    pub field: Option<String>,
    /// Error type
    pub error_type: String,
    /// Error message
    pub message: String,
    /// Error response body
    pub response: Value,
    /// HTTP status code
    pub status_code: u16,
}

/// Request context for error generation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestContext {
    /// HTTP method
    pub method: String,
    /// Request path
    pub path: String,
    /// Request body
    pub request_body: Option<Value>,
    /// Query parameters
    #[serde(default)]
    pub query_params: HashMap<String, String>,
    /// Headers
    #[serde(default)]
    pub headers: HashMap<String, String>,
}

/// Validation error response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationErrorResponse {
    /// HTTP status code
    pub status_code: u16,
    /// Error response body
    pub body: Value,
    /// Error format (field-level, object-level, custom)
    pub format: ErrorFormat,
}

/// Error response format
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ErrorFormat {
    /// Field-level errors (each field has its own error)
    FieldLevel,
    /// Object-level error (single error message)
    ObjectLevel,
    /// Custom format
    Custom,
}

/// Field-level error
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldError {
    /// Field name
    pub field: String,
    /// Error message
    pub message: String,
    /// Error code (optional)
    pub code: Option<String>,
    /// Rejected value (optional)
    pub rejected_value: Option<Value>,
}

/// Validation error generator
pub struct ValidationGenerator {
    /// LLM client for generating error messages
    llm_client: Option<LlmClient>,
    /// Configuration
    #[allow(dead_code)]
    config: BehaviorModelConfig,
    /// Learned error examples
    error_examples: Vec<ValidationErrorExample>,
}

impl ValidationGenerator {
    /// Create a new validation generator
    pub fn new(config: BehaviorModelConfig) -> Self {
        let llm_client = if config.llm_provider != "disabled" {
            Some(LlmClient::new(config.clone()))
        } else {
            None
        };

        Self {
            llm_client,
            config,
            error_examples: Vec::new(),
        }
    }

    /// Learn from an error example
    pub fn learn_from_example(&mut self, example: ValidationErrorExample) {
        self.error_examples.push(example);
    }

    /// Generate validation error response
    ///
    /// Creates a realistic, context-aware validation error based on the
    /// validation issue and request context.
    pub async fn generate_validation_error(
        &self,
        issue: &ValidationIssue,
        context: &RequestContext,
    ) -> Result<ValidationErrorResponse> {
        // Determine error format based on issue
        let format = self.determine_error_format(issue);

        // Generate error message
        let error_message = self.format_error_message(issue, context).await?;

        // Build error response body
        let body = match format {
            ErrorFormat::FieldLevel => {
                self.build_field_level_error(issue, &error_message, context).await?
            }
            ErrorFormat::ObjectLevel => {
                self.build_object_level_error(issue, &error_message, context).await?
            }
            ErrorFormat::Custom => self.build_custom_error(issue, &error_message, context).await?,
        };

        Ok(ValidationErrorResponse {
            status_code: self.determine_status_code(issue),
            body,
            format,
        })
    }

    /// Generate field-level error
    pub async fn generate_field_error(
        &self,
        field: &str,
        issue: &ValidationIssue,
        context: &RequestContext,
    ) -> Result<FieldError> {
        let message = self.format_error_message(issue, context).await?;

        // Extract rejected value from request if available
        let rejected_value =
            context.request_body.as_ref().and_then(|body| body.get(field)).cloned();

        Ok(FieldError {
            field: field.to_string(),
            message,
            code: Some(self.generate_error_code(issue)),
            rejected_value,
        })
    }

    /// Format error message using LLM or templates
    async fn format_error_message(
        &self,
        issue: &ValidationIssue,
        _context: &RequestContext,
    ) -> Result<String> {
        // First, try to find similar examples
        if let Some(similar_example) = self.find_similar_example(issue, &self.error_examples) {
            // Use similar example's message as template
            return Ok(similar_example.message.clone());
        }

        // If LLM is available, generate message
        if let Some(ref _llm_client) = self.llm_client {
            return self.generate_message_with_llm(issue).await;
        }

        // Fallback to template-based message
        Ok(self.generate_template_message(issue))
    }

    // ===== Private helper methods =====

    /// Determine error format based on issue
    fn determine_error_format(&self, issue: &ValidationIssue) -> ErrorFormat {
        // If field is specified, use field-level format
        if issue.field.is_some() {
            return ErrorFormat::FieldLevel;
        }

        // Otherwise, use object-level
        ErrorFormat::ObjectLevel
    }

    /// Build field-level error response
    async fn build_field_level_error(
        &self,
        issue: &ValidationIssue,
        message: &str,
        _context: &RequestContext,
    ) -> Result<Value> {
        let field = issue.field.as_deref().unwrap_or("unknown");

        // Standard field-level error format
        Ok(serde_json::json!({
            "error": {
                "type": "validation_error",
                "message": "Validation failed",
                "fields": {
                    field: {
                        "message": message,
                        "code": self.generate_error_code(issue),
                        "type": format!("{:?}", issue.issue_type).to_lowercase()
                    }
                }
            }
        }))
    }

    /// Build object-level error response
    async fn build_object_level_error(
        &self,
        issue: &ValidationIssue,
        message: &str,
        _context: &RequestContext,
    ) -> Result<Value> {
        Ok(serde_json::json!({
            "error": {
                "type": "validation_error",
                "message": message,
                "code": self.generate_error_code(issue)
            }
        }))
    }

    /// Build custom error response
    async fn build_custom_error(
        &self,
        issue: &ValidationIssue,
        message: &str,
        context: &RequestContext,
    ) -> Result<Value> {
        // Use LLM to generate custom format if available
        if let Some(ref _llm_client) = self.llm_client {
            return self.generate_custom_format_with_llm(issue, message, context).await;
        }

        // Fallback to object-level
        self.build_object_level_error(issue, message, context).await
    }

    /// Determine HTTP status code from issue
    fn determine_status_code(&self, issue: &ValidationIssue) -> u16 {
        match issue.severity {
            ValidationSeverity::Critical | ValidationSeverity::Error => 400,
            ValidationSeverity::Warning => 422,
            ValidationSeverity::Info => 200, // Info doesn't block
        }
    }

    /// Generate error code from issue type
    fn generate_error_code(&self, issue: &ValidationIssue) -> String {
        match issue.issue_type {
            ValidationIssueType::Required => "REQUIRED_FIELD".to_string(),
            ValidationIssueType::Format => "INVALID_FORMAT".to_string(),
            ValidationIssueType::MinLength => "MIN_LENGTH".to_string(),
            ValidationIssueType::MaxLength => "MAX_LENGTH".to_string(),
            ValidationIssueType::Pattern => "INVALID_PATTERN".to_string(),
            ValidationIssueType::Range => "OUT_OF_RANGE".to_string(),
            ValidationIssueType::Type => "INVALID_TYPE".to_string(),
            ValidationIssueType::Custom => "VALIDATION_ERROR".to_string(),
        }
    }

    /// Find similar error example
    fn find_similar_example<'a>(
        &self,
        issue: &ValidationIssue,
        examples: &'a [ValidationErrorExample],
    ) -> Option<&'a ValidationErrorExample> {
        examples.iter().find(|ex| {
            // Match by field if available
            if let Some(ref field) = issue.field {
                if let Some(ref ex_field) = ex.field {
                    if field == ex_field {
                        return true;
                    }
                }
            }

            // Match by error type
            ex.error_type == format!("{:?}", issue.issue_type)
        })
    }

    /// Generate error message using LLM
    async fn generate_message_with_llm(&self, issue: &ValidationIssue) -> Result<String> {
        let llm_client = self
            .llm_client
            .as_ref()
            .ok_or_else(|| mockforge_foundation::Error::internal("LLM client not available"))?;

        let issue_type_str = format!("{:?}", issue.issue_type);
        let field_str =
            issue.field.as_ref().map(|f| format!(" for field '{}'", f)).unwrap_or_default();

        let system_prompt = "You are an API error message generator. Generate clear, helpful validation error messages.";
        let user_prompt = format!(
            "Generate a validation error message{} for a {} error. \
             The error should be clear, helpful, and professional. \
             Return only the error message text, no additional formatting.",
            field_str, issue_type_str
        );

        let request = LlmGenerationRequest {
            system_prompt: system_prompt.to_string(),
            user_prompt,
            temperature: 0.3, // Lower temperature for consistent error messages
            max_tokens: 100,
            schema: None,
        };

        let response = llm_client.generate(&request).await?;

        // Extract message from response
        if let Some(text) = response.as_str() {
            Ok(text.trim().to_string())
        } else if let Some(message) = response.get("message").and_then(|m| m.as_str()) {
            Ok(message.to_string())
        } else {
            Ok(self.generate_template_message(issue))
        }
    }

    /// Generate template-based error message
    fn generate_template_message(&self, issue: &ValidationIssue) -> String {
        let field_str = issue.field.as_ref().map(|f| format!("Field '{}' ", f)).unwrap_or_default();

        match issue.issue_type {
            ValidationIssueType::Required => {
                format!("{}is required", field_str)
            }
            ValidationIssueType::Format => {
                format!("{}has an invalid format", field_str)
            }
            ValidationIssueType::MinLength => {
                format!("{}is too short", field_str)
            }
            ValidationIssueType::MaxLength => {
                format!("{}is too long", field_str)
            }
            ValidationIssueType::Pattern => {
                format!("{}does not match the required pattern", field_str)
            }
            ValidationIssueType::Range => {
                format!("{}is out of range", field_str)
            }
            ValidationIssueType::Type => {
                format!("{}has an invalid type", field_str)
            }
            ValidationIssueType::Custom => issue.error_message.clone(),
        }
    }

    /// Generate custom error format using LLM
    async fn generate_custom_format_with_llm(
        &self,
        issue: &ValidationIssue,
        message: &str,
        context: &RequestContext,
    ) -> Result<Value> {
        let llm_client = self
            .llm_client
            .as_ref()
            .ok_or_else(|| mockforge_foundation::Error::internal("LLM client not available"))?;

        let system_prompt = "You are an API error response generator. Generate realistic error responses in JSON format.";
        let user_prompt = format!(
            "Generate a validation error response for:\n\
             Method: {}\n\
             Path: {}\n\
             Error: {:?}\n\
             Message: {}\n\n\
             Return a JSON object with error details. Use a realistic API error format.",
            context.method, context.path, issue.issue_type, message
        );

        let request = LlmGenerationRequest {
            system_prompt: system_prompt.to_string(),
            user_prompt,
            temperature: 0.5,
            max_tokens: 300,
            schema: None,
        };

        let response = llm_client.generate(&request).await?;

        // Try to parse as JSON, fallback to wrapping in error object
        if let Some(obj) = response.as_object() {
            Ok(Value::Object(obj.clone()))
        } else {
            Ok(serde_json::json!({
                "error": {
                    "message": message,
                    "type": format!("{:?}", issue.issue_type)
                }
            }))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[tokio::test]
    async fn test_generate_template_message() {
        let config = BehaviorModelConfig::default();
        let generator = ValidationGenerator::new(config);

        let issue = ValidationIssue {
            field: Some("email".to_string()),
            issue_type: ValidationIssueType::Required,
            severity: ValidationSeverity::Error,
            context: json!({}),
            error_message: "".to_string(),
        };

        let message = generator.generate_template_message(&issue);
        assert!(message.contains("email"));
        assert!(message.contains("required"));
    }

    #[tokio::test]
    async fn test_determine_error_format() {
        let config = BehaviorModelConfig::default();
        let generator = ValidationGenerator::new(config);

        let field_issue = ValidationIssue {
            field: Some("email".to_string()),
            issue_type: ValidationIssueType::Required,
            severity: ValidationSeverity::Error,
            context: json!({}),
            error_message: "".to_string(),
        };

        assert_eq!(generator.determine_error_format(&field_issue), ErrorFormat::FieldLevel);

        let object_issue = ValidationIssue {
            field: None,
            issue_type: ValidationIssueType::Required,
            severity: ValidationSeverity::Error,
            context: json!({}),
            error_message: "".to_string(),
        };

        assert_eq!(generator.determine_error_format(&object_issue), ErrorFormat::ObjectLevel);
    }

    #[tokio::test]
    async fn test_generate_error_code() {
        let config = BehaviorModelConfig::default();
        let generator = ValidationGenerator::new(config);

        let issue = ValidationIssue {
            field: Some("email".to_string()),
            issue_type: ValidationIssueType::Format,
            severity: ValidationSeverity::Error,
            context: json!({}),
            error_message: "".to_string(),
        };

        let code = generator.generate_error_code(&issue);
        assert_eq!(code, "INVALID_FORMAT");
    }
}