Skip to main content

mockforge_data/
mock_generator.rs

1//! Enhanced Mock Data Generator for OpenAPI Specifications
2//!
3//! This module provides comprehensive mock data generation capabilities that go beyond
4//! the basic schema generator, offering intelligent data generation based on OpenAPI
5//! specifications with type safety and realistic data patterns.
6
7use crate::consistency::ConsistencyStore;
8use crate::domains::Domain;
9use crate::faker::EnhancedFaker;
10use crate::persona::PersonaRegistry;
11use crate::persona_backstory::BackstoryGenerator;
12use crate::persona_templates::PersonaTemplateRegistry;
13use crate::schema::{FieldDefinition, SchemaDefinition};
14use crate::{Error, Result};
15use serde_json::{json, Value};
16use std::collections::HashMap;
17use std::sync::Arc;
18use tracing::{debug, info, warn};
19
20/// Configuration for mock data generation
21#[derive(Debug, Clone)]
22pub struct MockGeneratorConfig {
23    /// Whether to use realistic data patterns
24    pub realistic_mode: bool,
25    /// Default array size for generated arrays
26    pub default_array_size: usize,
27    /// Maximum array size for generated arrays
28    pub max_array_size: usize,
29    /// Whether to include optional fields
30    pub include_optional_fields: bool,
31    /// Custom field mappings for specific field names
32    pub field_mappings: HashMap<String, String>,
33    /// Whether to validate generated data against schemas
34    pub validate_generated_data: bool,
35    /// Whether to generate backstories for personas
36    pub enable_backstories: bool,
37}
38
39impl Default for MockGeneratorConfig {
40    fn default() -> Self {
41        Self {
42            realistic_mode: true,
43            default_array_size: 3,
44            max_array_size: 10,
45            include_optional_fields: true,
46            field_mappings: HashMap::new(),
47            validate_generated_data: true,
48            enable_backstories: false,
49        }
50    }
51}
52
53impl MockGeneratorConfig {
54    /// Create a new configuration with realistic defaults
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Enable realistic data generation
60    pub fn realistic_mode(mut self, enabled: bool) -> Self {
61        self.realistic_mode = enabled;
62        self
63    }
64
65    /// Set default array size
66    pub fn default_array_size(mut self, size: usize) -> Self {
67        self.default_array_size = size;
68        self
69    }
70
71    /// Set maximum array size
72    pub fn max_array_size(mut self, size: usize) -> Self {
73        self.max_array_size = size;
74        self
75    }
76
77    /// Control whether to include optional fields
78    pub fn include_optional_fields(mut self, include: bool) -> Self {
79        self.include_optional_fields = include;
80        self
81    }
82
83    /// Add a custom field mapping
84    pub fn field_mapping(mut self, field_name: String, faker_type: String) -> Self {
85        self.field_mappings.insert(field_name, faker_type);
86        self
87    }
88
89    /// Enable/disable data validation
90    pub fn validate_generated_data(mut self, validate: bool) -> Self {
91        self.validate_generated_data = validate;
92        self
93    }
94
95    /// Enable/disable backstory generation for personas
96    pub fn enable_backstories(mut self, enable: bool) -> Self {
97        self.enable_backstories = enable;
98        self
99    }
100}
101
102/// Enhanced mock data generator with intelligent schema analysis
103#[derive(Debug)]
104pub struct MockDataGenerator {
105    /// Configuration for the generator
106    config: MockGeneratorConfig,
107    /// Enhanced faker instance
108    faker: EnhancedFaker,
109    /// Field name patterns for intelligent mapping
110    field_patterns: HashMap<String, String>,
111    /// Persona registry for consistent persona-based generation
112    persona_registry: Option<Arc<PersonaRegistry>>,
113    /// Consistency store for entity ID → persona mappings
114    consistency_store: Option<Arc<ConsistencyStore>>,
115    /// Active domain for persona-based generation
116    active_domain: Option<Domain>,
117}
118
119impl MockDataGenerator {
120    /// Create a new mock data generator with default configuration
121    pub fn new() -> Self {
122        Self::with_config(MockGeneratorConfig::new())
123    }
124
125    /// Create a new mock data generator with custom configuration
126    pub fn with_config(config: MockGeneratorConfig) -> Self {
127        let mut generator = Self {
128            config,
129            faker: EnhancedFaker::new(),
130            field_patterns: Self::create_field_patterns(),
131            persona_registry: None,
132            consistency_store: None,
133            active_domain: None,
134        };
135
136        // Initialize with common schema patterns
137        generator.initialize_common_schemas();
138        generator
139    }
140
141    /// Create a new mock data generator with persona support
142    pub fn with_persona_support(config: MockGeneratorConfig, domain: Option<Domain>) -> Self {
143        let persona_registry = Arc::new(PersonaRegistry::new());
144        let consistency_store =
145            Arc::new(ConsistencyStore::with_registry_and_domain(persona_registry.clone(), domain));
146
147        let mut generator = Self {
148            config,
149            faker: EnhancedFaker::new(),
150            field_patterns: Self::create_field_patterns(),
151            persona_registry: Some(persona_registry),
152            consistency_store: Some(consistency_store),
153            active_domain: domain,
154        };
155
156        // Initialize with common schema patterns
157        generator.initialize_common_schemas();
158        generator
159    }
160
161    /// Set the active domain for persona-based generation
162    pub fn set_active_domain(&mut self, domain: Option<Domain>) {
163        self.active_domain = domain;
164        // Note: ConsistencyStore doesn't have a setter for default domain,
165        // so we just update the active_domain field which is used when generating values
166    }
167
168    /// Get the persona registry
169    pub fn persona_registry(&self) -> Option<&Arc<PersonaRegistry>> {
170        self.persona_registry.as_ref()
171    }
172
173    /// Get the consistency store
174    pub fn consistency_store(&self) -> Option<&Arc<ConsistencyStore>> {
175        self.consistency_store.as_ref()
176    }
177
178    /// Generate mock data from an OpenAPI specification
179    pub fn generate_from_openapi_spec(&mut self, spec: &Value) -> Result<MockDataResult> {
180        info!("Generating mock data from OpenAPI specification");
181
182        // Parse the OpenAPI spec info
183        let spec_info = self.parse_openapi_spec_info(spec)?;
184
185        // Extract all schemas from the spec
186        let schemas = self.extract_schemas_from_spec(spec)?;
187
188        // Generate mock data for each schema
189        let mut generated_data = HashMap::new();
190        let mut warnings = Vec::new();
191
192        for (schema_name, schema_def) in schemas {
193            debug!("Generating data for schema: {}", schema_name);
194
195            match self.generate_schema_data(&schema_def) {
196                Ok(data) => {
197                    generated_data.insert(schema_name, data);
198                }
199                Err(e) => {
200                    let warning =
201                        format!("Failed to generate data for schema '{}': {}", schema_name, e);
202                    warn!("{}", warning);
203                    warnings.push(warning);
204                }
205            }
206        }
207
208        // Generate mock responses for each endpoint
209        // Parse paths directly from the JSON spec since parse_openapi_spec doesn't parse them
210        let mut mock_responses = HashMap::new();
211        if let Some(paths) = spec.get("paths") {
212            if let Some(paths_obj) = paths.as_object() {
213                for (path, path_item) in paths_obj {
214                    if let Some(path_obj) = path_item.as_object() {
215                        for (method, operation) in path_obj {
216                            if let Some(op_obj) = operation.as_object() {
217                                let endpoint_key = format!("{} {}", method.to_uppercase(), path);
218
219                                // Extract response schema from the operation
220                                if let Some(responses) = op_obj.get("responses") {
221                                    if let Some(resp_obj) = responses.as_object() {
222                                        // Look for 200, 201, or any 2xx response
223                                        let mut response_schema = None;
224
225                                        // Try 200 first
226                                        if let Some(response) = resp_obj.get("200") {
227                                            response_schema = self
228                                                .extract_response_schema_from_json(response)
229                                                .ok()
230                                                .flatten();
231                                        }
232
233                                        // Try 201 if 200 not found
234                                        if response_schema.is_none() {
235                                            if let Some(response) = resp_obj.get("201") {
236                                                response_schema = self
237                                                    .extract_response_schema_from_json(response)
238                                                    .ok()
239                                                    .flatten();
240                                            }
241                                        }
242
243                                        // Try any 2xx if still not found
244                                        if response_schema.is_none() {
245                                            for (status_code, response) in resp_obj {
246                                                if let Ok(code) = status_code.parse::<u16>() {
247                                                    if (200..300).contains(&code) {
248                                                        if let Some(schema) = self
249                                                            .extract_response_schema_from_json(
250                                                                response,
251                                                            )
252                                                            .ok()
253                                                            .flatten()
254                                                        {
255                                                            response_schema = Some(schema);
256                                                            break;
257                                                        }
258                                                    }
259                                                }
260                                            }
261                                        }
262
263                                        // Generate mock response if we found a schema
264                                        if let Some(schema) = response_schema {
265                                            // Resolve $ref if present
266                                            let resolved_schema = if let Some(ref_path) =
267                                                schema.get("$ref").and_then(|r| r.as_str())
268                                            {
269                                                self.resolve_schema_ref(spec, ref_path)?
270                                            } else {
271                                                Some(schema)
272                                            };
273
274                                            if let Some(resolved) = resolved_schema {
275                                                if let Ok(mock_data) =
276                                                    self.generate_from_json_schema(&resolved)
277                                                {
278                                                    mock_responses.insert(
279                                                        endpoint_key,
280                                                        MockResponse {
281                                                            status: 200,
282                                                            headers: HashMap::new(),
283                                                            body: mock_data,
284                                                        },
285                                                    );
286                                                }
287                                            }
288                                        }
289                                    }
290                                }
291                            }
292                        }
293                    }
294                }
295            }
296        }
297
298        Ok(MockDataResult {
299            schemas: generated_data,
300            responses: mock_responses,
301            warnings,
302            spec_info,
303        })
304    }
305
306    /// Generate mock data from a JSON Schema
307    pub fn generate_from_json_schema(&mut self, schema: &Value) -> Result<Value> {
308        debug!("Generating mock data from JSON Schema");
309
310        // Convert JSON Schema to our internal schema format
311        let schema_def = SchemaDefinition::from_json_schema(schema)?;
312
313        // Generate data using our enhanced generator
314        self.generate_schema_data(&schema_def)
315    }
316
317    /// Generate mock data for a specific schema definition
318    fn generate_schema_data(&mut self, schema: &SchemaDefinition) -> Result<Value> {
319        let mut object = serde_json::Map::new();
320
321        for field in &schema.fields {
322            // Skip optional fields if configured to do so
323            if !field.required && !self.config.include_optional_fields {
324                continue;
325            }
326
327            // Determine the best faker type for this field
328            let faker_type = self.determine_faker_type(field);
329
330            // Generate the value
331            let value = self.generate_field_value(field, &faker_type)?;
332
333            // Validate the generated value if configured
334            if self.config.validate_generated_data {
335                field.validate_value(&value)?;
336            }
337
338            object.insert(field.name.clone(), value);
339        }
340
341        Ok(Value::Object(object))
342    }
343
344    /// Extract schema from an OpenAPI response (JSON format)
345    fn extract_response_schema_from_json(&self, response: &Value) -> Result<Option<Value>> {
346        // Check for content -> application/json -> schema
347        if let Some(content) = response.get("content") {
348            if let Some(json_content) = content.get("application/json") {
349                if let Some(schema) = json_content.get("schema") {
350                    // Handle $ref references
351                    if let Some(ref_path) = schema.get("$ref").and_then(|r| r.as_str()) {
352                        // Extract schema name from $ref (e.g., "#/components/schemas/User" -> "User")
353                        if let Some(schema_name) = ref_path.split('/').next_back() {
354                            // We'll need to resolve this from components, but for now return the ref
355                            // The caller should handle resolving from components
356                            return Ok(Some(json!({
357                                "$ref": ref_path,
358                                "schema_name": schema_name
359                            })));
360                        }
361                    }
362                    return Ok(Some(schema.clone()));
363                }
364            }
365        }
366        Ok(None)
367    }
368
369    /// Resolve a $ref reference to an actual schema
370    fn resolve_schema_ref(&self, spec: &Value, ref_path: &str) -> Result<Option<Value>> {
371        // Handle #/components/schemas/Name format
372        if ref_path.starts_with("#/components/schemas/") {
373            let schema_name = ref_path
374                .strip_prefix("#/components/schemas/")
375                .expect("already verified starts_with this prefix");
376            if let Some(components) = spec.get("components") {
377                if let Some(schemas) = components.get("schemas") {
378                    if let Some(schema) = schemas.get(schema_name) {
379                        return Ok(Some(schema.clone()));
380                    }
381                }
382            }
383        }
384        Ok(None)
385    }
386
387    /// Determine the best faker type for a field based on its name and type
388    fn determine_faker_type(&self, field: &FieldDefinition) -> String {
389        let field_name = field.name.to_lowercase();
390
391        // Check custom field mappings first
392        if let Some(mapped_type) = self.config.field_mappings.get(&field_name) {
393            return mapped_type.clone();
394        }
395
396        // Use field name patterns for intelligent mapping
397        // Find the longest matching pattern to prioritize more specific matches
398        // Also prioritize certain patterns (like "email" over "address")
399        let mut best_match: Option<(&String, &String)> = None;
400        let priority_patterns = ["email", "mail"]; // Patterns that should take precedence
401
402        for (pattern, faker_type) in &self.field_patterns {
403            if field_name.contains(pattern) {
404                // Check if this is a priority pattern
405                let is_priority = priority_patterns.contains(&pattern.as_str());
406
407                if let Some((best_pattern, _best_faker_type)) = best_match {
408                    let best_is_priority = priority_patterns.contains(&best_pattern.as_str());
409
410                    // Priority patterns always win, or longer patterns win
411                    if is_priority && !best_is_priority {
412                        best_match = Some((pattern, faker_type));
413                    } else if !is_priority && best_is_priority {
414                        // Keep the priority match
415                    } else if pattern.len() > best_pattern.len() {
416                        best_match = Some((pattern, faker_type));
417                    }
418                } else {
419                    best_match = Some((pattern, faker_type));
420                }
421            }
422        }
423
424        if let Some((_, faker_type)) = best_match {
425            return faker_type.clone();
426        }
427
428        // Fall back to field type
429        field.field_type.clone()
430    }
431
432    /// Generate a value for a specific field
433    fn generate_field_value(&mut self, field: &FieldDefinition, faker_type: &str) -> Result<Value> {
434        // Note: Automatic persona-based generation from field names would require
435        // entity ID values from request context (path params, query params, body).
436        // For now, use explicit generate_with_persona() for persona-based generation.
437        // Automatic detection can be enhanced in the future when request context is available.
438
439        // Use faker template if provided
440        if let Some(template) = &field.faker_template {
441            return Ok(self.faker.generate_by_type(template));
442        }
443
444        // Handle array generation specially
445        if field.field_type == "array" {
446            return self.generate_array_value(field);
447        }
448
449        // Handle nested object generation specially
450        if field.field_type == "object" && field.constraints.contains_key("properties") {
451            return self.generate_object_value(field);
452        }
453
454        // Generate based on determined faker type
455        let value = self.faker.generate_by_type(faker_type);
456
457        // Apply constraints if present
458        self.apply_constraints(&value, field)
459    }
460
461    /// Generate an array value for a field
462    fn generate_array_value(&mut self, field: &FieldDefinition) -> Result<Value> {
463        // Determine array size from constraints or use defaults
464        let min_items =
465            field.constraints.get("minItems").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
466        let max_items = field
467            .constraints
468            .get("maxItems")
469            .and_then(|v| v.as_u64())
470            .unwrap_or(self.config.max_array_size as u64) as usize;
471
472        // Use default array size if no constraints
473        let array_size = if min_items > 0 || max_items < self.config.max_array_size {
474            // Use a size within the constraints
475            let size = if min_items > 0 {
476                min_items.max(self.config.default_array_size)
477            } else {
478                self.config.default_array_size
479            };
480            size.min(max_items.max(min_items))
481        } else {
482            self.config.default_array_size
483        };
484
485        // Generate array of items
486        let mut array = Vec::new();
487
488        // Check if we have a full items schema (for objects, nested arrays, etc.)
489        if let Some(items_schema) = field.constraints.get("itemsSchema") {
490            // Generate items from the schema recursively
491            let items_schema_def = SchemaDefinition::from_json_schema(items_schema)?;
492            for _ in 0..array_size {
493                let item = self.generate_schema_data(&items_schema_def)?;
494                array.push(item);
495            }
496        } else {
497            // Simple type - use faker
498            let items_type =
499                field.constraints.get("itemsType").and_then(|v| v.as_str()).unwrap_or("string");
500
501            for _ in 0..array_size {
502                let item = self.faker.generate_by_type(items_type);
503                array.push(item);
504            }
505        }
506
507        Ok(Value::Array(array))
508    }
509
510    /// Generate an object value for a field with nested properties
511    fn generate_object_value(&mut self, field: &FieldDefinition) -> Result<Value> {
512        // Get nested properties from constraints
513        let properties = field
514            .constraints
515            .get("properties")
516            .ok_or_else(|| Error::generic("Object field missing properties constraint"))?;
517
518        // Get required fields if present
519        let required_fields: Vec<String> = field
520            .constraints
521            .get("required")
522            .and_then(|v| v.as_array())
523            .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
524            .unwrap_or_default();
525
526        // Create a nested schema from the properties
527        let nested_schema = SchemaDefinition::from_json_schema(&json!({
528            "type": "object",
529            "properties": properties,
530            "required": required_fields
531        }))?;
532
533        // Generate the nested object recursively
534        self.generate_schema_data(&nested_schema)
535    }
536
537    /// Generate data with explicit persona support
538    ///
539    /// Generates data for a schema using a specific entity ID and domain.
540    /// This ensures the same entity ID always generates the same data pattern.
541    /// If backstories are enabled, automatically generates backstories for personas.
542    pub fn generate_with_persona(
543        &mut self,
544        entity_id: &str,
545        domain: Domain,
546        schema: &SchemaDefinition,
547    ) -> Result<Value> {
548        // Ensure consistency store is available
549        let store = self.consistency_store.as_ref().ok_or_else(|| {
550            Error::generic("Persona support not enabled. Use with_persona_support() to create generator with persona support.")
551        })?;
552
553        // Generate backstory if enabled
554        if self.config.enable_backstories {
555            self.ensure_persona_backstory(store, entity_id, domain)?;
556        }
557
558        let mut object = serde_json::Map::new();
559
560        for field in &schema.fields {
561            // Skip optional fields if configured to do so
562            if !field.required && !self.config.include_optional_fields {
563                continue;
564            }
565
566            // Determine the best faker type for this field
567            let faker_type = self.determine_faker_type(field);
568
569            // Generate value using persona-based generation
570            let value = match store.generate_consistent_value(entity_id, &faker_type, Some(domain))
571            {
572                Ok(v) => v,
573                Err(_) => {
574                    // Fallback to regular generation
575                    self.faker.generate_by_type(&faker_type)
576                }
577            };
578
579            // Validate the generated value if configured
580            if self.config.validate_generated_data {
581                field.validate_value(&value)?;
582            }
583
584            object.insert(field.name.clone(), value);
585        }
586
587        Ok(Value::Object(object))
588    }
589
590    /// Ensure a persona has a backstory, generating one if needed
591    ///
592    /// Checks if the persona has a backstory, and if not, generates one
593    /// using the PersonaTemplateRegistry and BackstoryGenerator.
594    fn ensure_persona_backstory(
595        &self,
596        store: &ConsistencyStore,
597        entity_id: &str,
598        domain: Domain,
599    ) -> Result<()> {
600        let persona_registry = store.persona_registry();
601        let persona = store.get_entity_persona(entity_id, Some(domain));
602
603        // If persona already has a backstory, no need to generate
604        if persona.has_backstory() {
605            return Ok(());
606        }
607
608        // Generate traits using template if persona doesn't have traits
609        let mut persona_mut = persona.clone();
610        if persona_mut.traits.is_empty() {
611            let template_registry = PersonaTemplateRegistry::new();
612            template_registry.apply_template_to_persona(&mut persona_mut)?;
613        }
614
615        // Generate backstory using BackstoryGenerator
616        let backstory_generator = BackstoryGenerator::new();
617        match backstory_generator.generate_backstory(&persona_mut) {
618            Ok(backstory) => {
619                // Update persona in registry with traits and backstory
620                let mut traits = HashMap::new();
621                for (key, value) in &persona_mut.traits {
622                    traits.insert(key.clone(), value.clone());
623                }
624
625                // Update traits first
626                if !traits.is_empty() {
627                    persona_registry.update_persona(entity_id, traits)?;
628                }
629
630                // Update backstory
631                persona_registry.update_persona_backstory(entity_id, backstory)?;
632            }
633            Err(e) => {
634                warn!("Failed to generate backstory for persona {}: {}", entity_id, e);
635            }
636        }
637
638        Ok(())
639    }
640
641    /// Apply constraints to a generated value
642    fn apply_constraints(&mut self, value: &Value, field: &FieldDefinition) -> Result<Value> {
643        let mut constrained_value = value.clone();
644
645        // Apply numeric constraints
646        if let Value::Number(num) = value {
647            // Check if field type is integer to preserve integer type when applying constraints
648            let is_integer_field = field.field_type == "int" || field.field_type == "integer";
649
650            if let Some(minimum) = field.constraints.get("minimum") {
651                if let Some(min_val) = minimum.as_f64() {
652                    if num.as_f64().unwrap_or(0.0) < min_val {
653                        // Preserve integer type if field is integer
654                        if is_integer_field {
655                            constrained_value = json!(min_val as i64);
656                        } else {
657                            constrained_value = json!(min_val);
658                        }
659                    }
660                }
661            }
662
663            if let Some(maximum) = field.constraints.get("maximum") {
664                if let Some(max_val) = maximum.as_f64() {
665                    if num.as_f64().unwrap_or(0.0) > max_val {
666                        // Preserve integer type if field is integer
667                        if is_integer_field {
668                            constrained_value = json!(max_val as i64);
669                        } else {
670                            constrained_value = json!(max_val);
671                        }
672                    }
673                }
674            }
675        }
676
677        // Apply string constraints
678        if let Value::String(s) = value {
679            let mut constrained_string = s.clone();
680
681            // Apply min/max length constraints
682            if let Some(min_length) = field.constraints.get("minLength") {
683                if let Some(min_len) = min_length.as_u64() {
684                    if constrained_string.len() < min_len as usize {
685                        // Pad with random characters
686                        let padding_needed = min_len as usize - constrained_string.len();
687                        let padding = self.faker.string(padding_needed);
688                        constrained_string = format!("{}{}", constrained_string, padding);
689                    }
690                }
691            }
692
693            if let Some(max_length) = field.constraints.get("maxLength") {
694                if let Some(max_len) = max_length.as_u64() {
695                    if constrained_string.len() > max_len as usize {
696                        constrained_string.truncate(max_len as usize);
697                    }
698                }
699            }
700
701            constrained_value = json!(constrained_string);
702        }
703
704        // Apply enum constraints
705        if let Some(enum_values) = field.constraints.get("enum") {
706            if let Some(enum_array) = enum_values.as_array() {
707                if !enum_array.is_empty() {
708                    if let Some(random_value) = self.faker.random_element(enum_array) {
709                        constrained_value = random_value.clone();
710                    }
711                }
712            }
713        }
714
715        Ok(constrained_value)
716    }
717
718    /// Parse OpenAPI specification info section
719    fn parse_openapi_spec_info(&self, spec: &Value) -> Result<OpenApiInfo> {
720        let spec_obj = spec
721            .as_object()
722            .ok_or_else(|| Error::generic("Invalid OpenAPI specification"))?;
723
724        let info = spec_obj
725            .get("info")
726            .ok_or_else(|| Error::generic("Missing 'info' section in OpenAPI spec"))?;
727
728        let title = info.get("title").and_then(|t| t.as_str()).unwrap_or("Unknown API").to_string();
729
730        let version = info.get("version").and_then(|v| v.as_str()).unwrap_or("1.0.0").to_string();
731
732        let description = info.get("description").and_then(|d| d.as_str()).map(|s| s.to_string());
733
734        Ok(OpenApiInfo {
735            title,
736            version,
737            description,
738        })
739    }
740
741    /// Extract schemas from OpenAPI specification
742    fn extract_schemas_from_spec(
743        &mut self,
744        spec: &Value,
745    ) -> Result<HashMap<String, SchemaDefinition>> {
746        let mut schemas = HashMap::new();
747
748        // Extract component schemas
749        if let Some(components) = spec.get("components") {
750            if let Some(schemas_section) = components.get("schemas") {
751                if let Some(schema_obj) = schemas_section.as_object() {
752                    for (name, schema_def) in schema_obj {
753                        let schema = SchemaDefinition::from_json_schema(schema_def)?;
754                        schemas.insert(name.clone(), schema);
755                    }
756                }
757            }
758        }
759
760        // Extract schemas from paths
761        if let Some(paths) = spec.get("paths") {
762            if let Some(paths_obj) = paths.as_object() {
763                for (path, path_item) in paths_obj {
764                    if let Some(path_obj) = path_item.as_object() {
765                        for (method, operation) in path_obj {
766                            if let Some(op_obj) = operation.as_object() {
767                                // Extract request body schemas
768                                if let Some(request_body) = op_obj.get("requestBody") {
769                                    if let Some(content) = request_body.get("content") {
770                                        if let Some(json_content) = content.get("application/json")
771                                        {
772                                            if let Some(schema) = json_content.get("schema") {
773                                                let schema_name = format!(
774                                                    "{}_{}_request",
775                                                    path.replace("/", "_").trim_start_matches("_"),
776                                                    method
777                                                );
778                                                let schema_def =
779                                                    SchemaDefinition::from_json_schema(schema)?;
780                                                schemas.insert(schema_name, schema_def);
781                                            }
782                                        }
783                                    }
784                                }
785
786                                // Extract response schemas
787                                if let Some(responses) = op_obj.get("responses") {
788                                    if let Some(resp_obj) = responses.as_object() {
789                                        for (status_code, response) in resp_obj {
790                                            if let Some(content) = response.get("content") {
791                                                if let Some(json_content) =
792                                                    content.get("application/json")
793                                                {
794                                                    if let Some(schema) = json_content.get("schema")
795                                                    {
796                                                        let schema_name = format!(
797                                                            "{}_{}_response_{}",
798                                                            path.replace("/", "_")
799                                                                .trim_start_matches("_"),
800                                                            method,
801                                                            status_code
802                                                        );
803                                                        let schema_def =
804                                                            SchemaDefinition::from_json_schema(
805                                                                schema,
806                                                            )?;
807                                                        schemas.insert(schema_name, schema_def);
808                                                    }
809                                                }
810                                            }
811                                        }
812                                    }
813                                }
814                            }
815                        }
816                    }
817                }
818            }
819        }
820
821        Ok(schemas)
822    }
823
824    /// Create field name patterns for intelligent mapping
825    fn create_field_patterns() -> HashMap<String, String> {
826        let mut patterns = HashMap::new();
827
828        // Email patterns
829        patterns.insert("email".to_string(), "email".to_string());
830        patterns.insert("mail".to_string(), "email".to_string());
831
832        // Name patterns
833        patterns.insert("name".to_string(), "name".to_string());
834        patterns.insert("firstname".to_string(), "name".to_string());
835        patterns.insert("lastname".to_string(), "name".to_string());
836        patterns.insert("username".to_string(), "name".to_string());
837
838        // Phone patterns
839        patterns.insert("phone".to_string(), "phone".to_string());
840        patterns.insert("mobile".to_string(), "phone".to_string());
841        patterns.insert("telephone".to_string(), "phone".to_string());
842
843        // Address patterns
844        patterns.insert("address".to_string(), "address".to_string());
845        patterns.insert("street".to_string(), "address".to_string());
846        patterns.insert("city".to_string(), "string".to_string());
847        patterns.insert("state".to_string(), "string".to_string());
848        patterns.insert("zip".to_string(), "string".to_string());
849        patterns.insert("postal".to_string(), "string".to_string());
850
851        // Company patterns
852        patterns.insert("company".to_string(), "company".to_string());
853        patterns.insert("organization".to_string(), "company".to_string());
854        patterns.insert("corp".to_string(), "company".to_string());
855
856        // URL patterns
857        patterns.insert("url".to_string(), "url".to_string());
858        patterns.insert("website".to_string(), "url".to_string());
859        patterns.insert("link".to_string(), "url".to_string());
860
861        // Date patterns
862        patterns.insert("date".to_string(), "date".to_string());
863        patterns.insert("created".to_string(), "date".to_string());
864        patterns.insert("updated".to_string(), "date".to_string());
865        patterns.insert("timestamp".to_string(), "date".to_string());
866
867        // ID patterns
868        patterns.insert("id".to_string(), "uuid".to_string());
869        patterns.insert("uuid".to_string(), "uuid".to_string());
870        patterns.insert("guid".to_string(), "uuid".to_string());
871
872        // IP patterns
873        patterns.insert("ip".to_string(), "ip".to_string());
874        patterns.insert("ipv4".to_string(), "ip".to_string());
875        patterns.insert("ipv6".to_string(), "ip".to_string());
876
877        patterns
878    }
879
880    /// Initialize common schemas
881    fn initialize_common_schemas(&mut self) {
882        // Add common schema patterns here
883        // This could include User, Product, Order, etc.
884    }
885}
886
887impl Default for MockDataGenerator {
888    fn default() -> Self {
889        Self::new()
890    }
891}
892
893/// Result of mock data generation
894#[derive(Debug, Clone, serde::Serialize)]
895pub struct MockDataResult {
896    /// Generated data for each schema
897    pub schemas: HashMap<String, Value>,
898    /// Generated mock responses for each endpoint
899    pub responses: HashMap<String, MockResponse>,
900    /// Warnings encountered during generation
901    pub warnings: Vec<String>,
902    /// OpenAPI specification info
903    pub spec_info: OpenApiInfo,
904}
905
906/// Mock response data
907#[derive(Debug, Clone, serde::Serialize)]
908pub struct MockResponse {
909    /// HTTP status code
910    pub status: u16,
911    /// Response headers
912    pub headers: HashMap<String, String>,
913    /// Response body
914    pub body: Value,
915}
916
917/// OpenAPI specification info
918#[derive(Debug, Clone, serde::Serialize)]
919pub struct OpenApiInfo {
920    /// API title
921    pub title: String,
922    /// API version
923    pub version: String,
924    /// API description
925    pub description: Option<String>,
926}
927
928#[cfg(test)]
929mod tests {
930    use super::*;
931
932    #[test]
933    fn test_mock_generator_config_default() {
934        let config = MockGeneratorConfig::default();
935
936        assert!(config.realistic_mode);
937        assert_eq!(config.default_array_size, 3);
938        assert_eq!(config.max_array_size, 10);
939        assert!(config.include_optional_fields);
940        assert!(config.validate_generated_data);
941    }
942
943    #[test]
944    fn test_mock_generator_config_custom() {
945        let config = MockGeneratorConfig::new()
946            .realistic_mode(false)
947            .default_array_size(5)
948            .max_array_size(20)
949            .include_optional_fields(false)
950            .field_mapping("email".to_string(), "email".to_string())
951            .validate_generated_data(false);
952
953        assert!(!config.realistic_mode);
954        assert_eq!(config.default_array_size, 5);
955        assert_eq!(config.max_array_size, 20);
956        assert!(!config.include_optional_fields);
957        assert!(!config.validate_generated_data);
958        assert!(config.field_mappings.contains_key("email"));
959    }
960
961    #[test]
962    fn test_mock_data_generator_new() {
963        let generator = MockDataGenerator::new();
964
965        assert!(generator.config.realistic_mode);
966        assert!(!generator.field_patterns.is_empty());
967    }
968
969    #[test]
970    fn test_mock_data_generator_with_config() {
971        let config = MockGeneratorConfig::new().realistic_mode(false).default_array_size(10);
972
973        let generator = MockDataGenerator::with_config(config);
974
975        assert!(!generator.config.realistic_mode);
976        assert_eq!(generator.config.default_array_size, 10);
977    }
978
979    #[test]
980    fn test_determine_faker_type_custom_mapping() {
981        let mut config = MockGeneratorConfig::new();
982        config.field_mappings.insert("user_email".to_string(), "email".to_string());
983
984        let generator = MockDataGenerator::with_config(config);
985
986        let field = FieldDefinition::new("user_email".to_string(), "string".to_string());
987        let faker_type = generator.determine_faker_type(&field);
988
989        assert_eq!(faker_type, "email");
990    }
991
992    #[test]
993    fn test_determine_faker_type_pattern_matching() {
994        let generator = MockDataGenerator::new();
995
996        let field = FieldDefinition::new("email_address".to_string(), "string".to_string());
997        let faker_type = generator.determine_faker_type(&field);
998
999        assert_eq!(faker_type, "email");
1000    }
1001
1002    #[test]
1003    fn test_determine_faker_type_fallback() {
1004        let generator = MockDataGenerator::new();
1005
1006        let field = FieldDefinition::new("unknown_field".to_string(), "integer".to_string());
1007        let faker_type = generator.determine_faker_type(&field);
1008
1009        assert_eq!(faker_type, "integer");
1010    }
1011
1012    #[test]
1013    fn test_field_patterns_creation() {
1014        let patterns = MockDataGenerator::create_field_patterns();
1015
1016        assert!(patterns.contains_key("email"));
1017        assert!(patterns.contains_key("name"));
1018        assert!(patterns.contains_key("phone"));
1019        assert!(patterns.contains_key("address"));
1020        assert!(patterns.contains_key("company"));
1021        assert!(patterns.contains_key("url"));
1022        assert!(patterns.contains_key("date"));
1023        assert!(patterns.contains_key("id"));
1024        assert!(patterns.contains_key("ip"));
1025    }
1026
1027    #[test]
1028    fn test_generate_from_json_schema_simple() {
1029        let mut generator = MockDataGenerator::new();
1030
1031        let schema = json!({
1032            "type": "object",
1033            "properties": {
1034                "name": { "type": "string" },
1035                "age": { "type": "integer" },
1036                "email": { "type": "string" }
1037            },
1038            "required": ["name", "age"]
1039        });
1040
1041        let result = generator.generate_from_json_schema(&schema).unwrap();
1042
1043        assert!(result.is_object());
1044        let obj = result.as_object().unwrap();
1045        assert!(obj.contains_key("name"));
1046        assert!(obj.contains_key("age"));
1047        assert!(obj.contains_key("email"));
1048    }
1049
1050    #[test]
1051    fn test_generate_from_json_schema_with_constraints() {
1052        let mut generator = MockDataGenerator::new();
1053
1054        let schema = json!({
1055            "type": "object",
1056            "properties": {
1057                "age": {
1058                    "type": "integer",
1059                    "minimum": 18,
1060                    "maximum": 65
1061                },
1062                "name": {
1063                    "type": "string",
1064                    "minLength": 5,
1065                    "maxLength": 20
1066                }
1067            }
1068        });
1069
1070        let result = generator.generate_from_json_schema(&schema).unwrap();
1071
1072        assert!(result.is_object());
1073        let obj = result.as_object().unwrap();
1074
1075        if let Some(age) = obj.get("age") {
1076            if let Some(age_num) = age.as_i64() {
1077                assert!(age_num >= 18);
1078                assert!(age_num <= 65);
1079            }
1080        }
1081    }
1082
1083    #[test]
1084    fn test_generate_from_json_schema_with_enum() {
1085        let mut generator = MockDataGenerator::new();
1086
1087        let schema = json!({
1088            "type": "object",
1089            "properties": {
1090                "status": {
1091                    "type": "string",
1092                    "enum": ["active", "inactive", "pending"]
1093                }
1094            }
1095        });
1096
1097        let result = generator.generate_from_json_schema(&schema).unwrap();
1098
1099        assert!(result.is_object());
1100        let obj = result.as_object().unwrap();
1101
1102        if let Some(status) = obj.get("status") {
1103            if let Some(status_str) = status.as_str() {
1104                assert!(["active", "inactive", "pending"].contains(&status_str));
1105            }
1106        }
1107    }
1108}