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.strip_prefix("#/components/schemas/").unwrap();
374            if let Some(components) = spec.get("components") {
375                if let Some(schemas) = components.get("schemas") {
376                    if let Some(schema) = schemas.get(schema_name) {
377                        return Ok(Some(schema.clone()));
378                    }
379                }
380            }
381        }
382        Ok(None)
383    }
384
385    /// Determine the best faker type for a field based on its name and type
386    fn determine_faker_type(&self, field: &FieldDefinition) -> String {
387        let field_name = field.name.to_lowercase();
388
389        // Check custom field mappings first
390        if let Some(mapped_type) = self.config.field_mappings.get(&field_name) {
391            return mapped_type.clone();
392        }
393
394        // Use field name patterns for intelligent mapping
395        // Find the longest matching pattern to prioritize more specific matches
396        // Also prioritize certain patterns (like "email" over "address")
397        let mut best_match: Option<(&String, &String)> = None;
398        let priority_patterns = ["email", "mail"]; // Patterns that should take precedence
399
400        for (pattern, faker_type) in &self.field_patterns {
401            if field_name.contains(pattern) {
402                // Check if this is a priority pattern
403                let is_priority = priority_patterns.contains(&pattern.as_str());
404
405                if let Some((best_pattern, _best_faker_type)) = best_match {
406                    let best_is_priority = priority_patterns.contains(&best_pattern.as_str());
407
408                    // Priority patterns always win, or longer patterns win
409                    if is_priority && !best_is_priority {
410                        best_match = Some((pattern, faker_type));
411                    } else if !is_priority && best_is_priority {
412                        // Keep the priority match
413                    } else if pattern.len() > best_pattern.len() {
414                        best_match = Some((pattern, faker_type));
415                    }
416                } else {
417                    best_match = Some((pattern, faker_type));
418                }
419            }
420        }
421
422        if let Some((_, faker_type)) = best_match {
423            return faker_type.clone();
424        }
425
426        // Fall back to field type
427        field.field_type.clone()
428    }
429
430    /// Generate a value for a specific field
431    fn generate_field_value(&mut self, field: &FieldDefinition, faker_type: &str) -> Result<Value> {
432        // Note: Automatic persona-based generation from field names would require
433        // entity ID values from request context (path params, query params, body).
434        // For now, use explicit generate_with_persona() for persona-based generation.
435        // Automatic detection can be enhanced in the future when request context is available.
436
437        // Use faker template if provided
438        if let Some(template) = &field.faker_template {
439            return Ok(self.faker.generate_by_type(template));
440        }
441
442        // Handle array generation specially
443        if field.field_type == "array" {
444            return self.generate_array_value(field);
445        }
446
447        // Handle nested object generation specially
448        if field.field_type == "object" && field.constraints.contains_key("properties") {
449            return self.generate_object_value(field);
450        }
451
452        // Generate based on determined faker type
453        let value = self.faker.generate_by_type(faker_type);
454
455        // Apply constraints if present
456        self.apply_constraints(&value, field)
457    }
458
459    /// Generate an array value for a field
460    fn generate_array_value(&mut self, field: &FieldDefinition) -> Result<Value> {
461        // Determine array size from constraints or use defaults
462        let min_items =
463            field.constraints.get("minItems").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
464        let max_items = field
465            .constraints
466            .get("maxItems")
467            .and_then(|v| v.as_u64())
468            .unwrap_or(self.config.max_array_size as u64) as usize;
469
470        // Use default array size if no constraints
471        let array_size = if min_items > 0 || max_items < self.config.max_array_size {
472            // Use a size within the constraints
473            let size = if min_items > 0 {
474                min_items.max(self.config.default_array_size)
475            } else {
476                self.config.default_array_size
477            };
478            size.min(max_items.max(min_items))
479        } else {
480            self.config.default_array_size
481        };
482
483        // Generate array of items
484        let mut array = Vec::new();
485
486        // Check if we have a full items schema (for objects, nested arrays, etc.)
487        if let Some(items_schema) = field.constraints.get("itemsSchema") {
488            // Generate items from the schema recursively
489            let items_schema_def = SchemaDefinition::from_json_schema(items_schema)?;
490            for _ in 0..array_size {
491                let item = self.generate_schema_data(&items_schema_def)?;
492                array.push(item);
493            }
494        } else {
495            // Simple type - use faker
496            let items_type =
497                field.constraints.get("itemsType").and_then(|v| v.as_str()).unwrap_or("string");
498
499            for _ in 0..array_size {
500                let item = self.faker.generate_by_type(items_type);
501                array.push(item);
502            }
503        }
504
505        Ok(Value::Array(array))
506    }
507
508    /// Generate an object value for a field with nested properties
509    fn generate_object_value(&mut self, field: &FieldDefinition) -> Result<Value> {
510        // Get nested properties from constraints
511        let properties = field
512            .constraints
513            .get("properties")
514            .ok_or_else(|| Error::generic("Object field missing properties constraint"))?;
515
516        // Get required fields if present
517        let required_fields: Vec<String> = field
518            .constraints
519            .get("required")
520            .and_then(|v| v.as_array())
521            .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
522            .unwrap_or_default();
523
524        // Create a nested schema from the properties
525        let nested_schema = SchemaDefinition::from_json_schema(&json!({
526            "type": "object",
527            "properties": properties,
528            "required": required_fields
529        }))?;
530
531        // Generate the nested object recursively
532        self.generate_schema_data(&nested_schema)
533    }
534
535    /// Generate data with explicit persona support
536    ///
537    /// Generates data for a schema using a specific entity ID and domain.
538    /// This ensures the same entity ID always generates the same data pattern.
539    /// If backstories are enabled, automatically generates backstories for personas.
540    pub fn generate_with_persona(
541        &mut self,
542        entity_id: &str,
543        domain: Domain,
544        schema: &SchemaDefinition,
545    ) -> Result<Value> {
546        // Ensure consistency store is available
547        let store = self.consistency_store.as_ref().ok_or_else(|| {
548            Error::generic("Persona support not enabled. Use with_persona_support() to create generator with persona support.")
549        })?;
550
551        // Generate backstory if enabled
552        if self.config.enable_backstories {
553            self.ensure_persona_backstory(store, entity_id, domain)?;
554        }
555
556        let mut object = serde_json::Map::new();
557
558        for field in &schema.fields {
559            // Skip optional fields if configured to do so
560            if !field.required && !self.config.include_optional_fields {
561                continue;
562            }
563
564            // Determine the best faker type for this field
565            let faker_type = self.determine_faker_type(field);
566
567            // Generate value using persona-based generation
568            let value = match store.generate_consistent_value(entity_id, &faker_type, Some(domain))
569            {
570                Ok(v) => v,
571                Err(_) => {
572                    // Fallback to regular generation
573                    self.faker.generate_by_type(&faker_type)
574                }
575            };
576
577            // Validate the generated value if configured
578            if self.config.validate_generated_data {
579                field.validate_value(&value)?;
580            }
581
582            object.insert(field.name.clone(), value);
583        }
584
585        Ok(Value::Object(object))
586    }
587
588    /// Ensure a persona has a backstory, generating one if needed
589    ///
590    /// Checks if the persona has a backstory, and if not, generates one
591    /// using the PersonaTemplateRegistry and BackstoryGenerator.
592    fn ensure_persona_backstory(
593        &self,
594        store: &ConsistencyStore,
595        entity_id: &str,
596        domain: Domain,
597    ) -> Result<()> {
598        let persona_registry = store.persona_registry();
599        let persona = store.get_entity_persona(entity_id, Some(domain));
600
601        // If persona already has a backstory, no need to generate
602        if persona.has_backstory() {
603            return Ok(());
604        }
605
606        // Generate traits using template if persona doesn't have traits
607        let mut persona_mut = persona.clone();
608        if persona_mut.traits.is_empty() {
609            let template_registry = PersonaTemplateRegistry::new();
610            template_registry.apply_template_to_persona(&mut persona_mut)?;
611        }
612
613        // Generate backstory using BackstoryGenerator
614        let backstory_generator = BackstoryGenerator::new();
615        match backstory_generator.generate_backstory(&persona_mut) {
616            Ok(backstory) => {
617                // Update persona in registry with traits and backstory
618                let mut traits = HashMap::new();
619                for (key, value) in &persona_mut.traits {
620                    traits.insert(key.clone(), value.clone());
621                }
622
623                // Update traits first
624                if !traits.is_empty() {
625                    persona_registry.update_persona(entity_id, traits)?;
626                }
627
628                // Update backstory
629                persona_registry.update_persona_backstory(entity_id, backstory)?;
630            }
631            Err(e) => {
632                warn!("Failed to generate backstory for persona {}: {}", entity_id, e);
633            }
634        }
635
636        Ok(())
637    }
638
639    /// Apply constraints to a generated value
640    fn apply_constraints(&mut self, value: &Value, field: &FieldDefinition) -> Result<Value> {
641        let mut constrained_value = value.clone();
642
643        // Apply numeric constraints
644        if let Value::Number(num) = value {
645            // Check if field type is integer to preserve integer type when applying constraints
646            let is_integer_field = field.field_type == "int" || field.field_type == "integer";
647
648            if let Some(minimum) = field.constraints.get("minimum") {
649                if let Some(min_val) = minimum.as_f64() {
650                    if num.as_f64().unwrap_or(0.0) < min_val {
651                        // Preserve integer type if field is integer
652                        if is_integer_field {
653                            constrained_value = json!(min_val as i64);
654                        } else {
655                            constrained_value = json!(min_val);
656                        }
657                    }
658                }
659            }
660
661            if let Some(maximum) = field.constraints.get("maximum") {
662                if let Some(max_val) = maximum.as_f64() {
663                    if num.as_f64().unwrap_or(0.0) > max_val {
664                        // Preserve integer type if field is integer
665                        if is_integer_field {
666                            constrained_value = json!(max_val as i64);
667                        } else {
668                            constrained_value = json!(max_val);
669                        }
670                    }
671                }
672            }
673        }
674
675        // Apply string constraints
676        if let Value::String(s) = value {
677            let mut constrained_string = s.clone();
678
679            // Apply min/max length constraints
680            if let Some(min_length) = field.constraints.get("minLength") {
681                if let Some(min_len) = min_length.as_u64() {
682                    if constrained_string.len() < min_len as usize {
683                        // Pad with random characters
684                        let padding_needed = min_len as usize - constrained_string.len();
685                        let padding = self.faker.string(padding_needed);
686                        constrained_string = format!("{}{}", constrained_string, padding);
687                    }
688                }
689            }
690
691            if let Some(max_length) = field.constraints.get("maxLength") {
692                if let Some(max_len) = max_length.as_u64() {
693                    if constrained_string.len() > max_len as usize {
694                        constrained_string.truncate(max_len as usize);
695                    }
696                }
697            }
698
699            constrained_value = json!(constrained_string);
700        }
701
702        // Apply enum constraints
703        if let Some(enum_values) = field.constraints.get("enum") {
704            if let Some(enum_array) = enum_values.as_array() {
705                if !enum_array.is_empty() {
706                    if let Some(random_value) = self.faker.random_element(enum_array) {
707                        constrained_value = random_value.clone();
708                    }
709                }
710            }
711        }
712
713        Ok(constrained_value)
714    }
715
716    /// Parse OpenAPI specification info section
717    fn parse_openapi_spec_info(&self, spec: &Value) -> Result<OpenApiInfo> {
718        let spec_obj = spec
719            .as_object()
720            .ok_or_else(|| Error::generic("Invalid OpenAPI specification"))?;
721
722        let info = spec_obj
723            .get("info")
724            .ok_or_else(|| Error::generic("Missing 'info' section in OpenAPI spec"))?;
725
726        let title = info.get("title").and_then(|t| t.as_str()).unwrap_or("Unknown API").to_string();
727
728        let version = info.get("version").and_then(|v| v.as_str()).unwrap_or("1.0.0").to_string();
729
730        let description = info.get("description").and_then(|d| d.as_str()).map(|s| s.to_string());
731
732        Ok(OpenApiInfo {
733            title,
734            version,
735            description,
736        })
737    }
738
739    /// Extract schemas from OpenAPI specification
740    fn extract_schemas_from_spec(
741        &mut self,
742        spec: &Value,
743    ) -> Result<HashMap<String, SchemaDefinition>> {
744        let mut schemas = HashMap::new();
745
746        // Extract component schemas
747        if let Some(components) = spec.get("components") {
748            if let Some(schemas_section) = components.get("schemas") {
749                if let Some(schema_obj) = schemas_section.as_object() {
750                    for (name, schema_def) in schema_obj {
751                        let schema = SchemaDefinition::from_json_schema(schema_def)?;
752                        schemas.insert(name.clone(), schema);
753                    }
754                }
755            }
756        }
757
758        // Extract schemas from paths
759        if let Some(paths) = spec.get("paths") {
760            if let Some(paths_obj) = paths.as_object() {
761                for (path, path_item) in paths_obj {
762                    if let Some(path_obj) = path_item.as_object() {
763                        for (method, operation) in path_obj {
764                            if let Some(op_obj) = operation.as_object() {
765                                // Extract request body schemas
766                                if let Some(request_body) = op_obj.get("requestBody") {
767                                    if let Some(content) = request_body.get("content") {
768                                        if let Some(json_content) = content.get("application/json")
769                                        {
770                                            if let Some(schema) = json_content.get("schema") {
771                                                let schema_name = format!(
772                                                    "{}_{}_request",
773                                                    path.replace("/", "_").trim_start_matches("_"),
774                                                    method
775                                                );
776                                                let schema_def =
777                                                    SchemaDefinition::from_json_schema(schema)?;
778                                                schemas.insert(schema_name, schema_def);
779                                            }
780                                        }
781                                    }
782                                }
783
784                                // Extract response schemas
785                                if let Some(responses) = op_obj.get("responses") {
786                                    if let Some(resp_obj) = responses.as_object() {
787                                        for (status_code, response) in resp_obj {
788                                            if let Some(content) = response.get("content") {
789                                                if let Some(json_content) =
790                                                    content.get("application/json")
791                                                {
792                                                    if let Some(schema) = json_content.get("schema")
793                                                    {
794                                                        let schema_name = format!(
795                                                            "{}_{}_response_{}",
796                                                            path.replace("/", "_")
797                                                                .trim_start_matches("_"),
798                                                            method,
799                                                            status_code
800                                                        );
801                                                        let schema_def =
802                                                            SchemaDefinition::from_json_schema(
803                                                                schema,
804                                                            )?;
805                                                        schemas.insert(schema_name, schema_def);
806                                                    }
807                                                }
808                                            }
809                                        }
810                                    }
811                                }
812                            }
813                        }
814                    }
815                }
816            }
817        }
818
819        Ok(schemas)
820    }
821
822    /// Create field name patterns for intelligent mapping
823    fn create_field_patterns() -> HashMap<String, String> {
824        let mut patterns = HashMap::new();
825
826        // Email patterns
827        patterns.insert("email".to_string(), "email".to_string());
828        patterns.insert("mail".to_string(), "email".to_string());
829
830        // Name patterns
831        patterns.insert("name".to_string(), "name".to_string());
832        patterns.insert("firstname".to_string(), "name".to_string());
833        patterns.insert("lastname".to_string(), "name".to_string());
834        patterns.insert("username".to_string(), "name".to_string());
835
836        // Phone patterns
837        patterns.insert("phone".to_string(), "phone".to_string());
838        patterns.insert("mobile".to_string(), "phone".to_string());
839        patterns.insert("telephone".to_string(), "phone".to_string());
840
841        // Address patterns
842        patterns.insert("address".to_string(), "address".to_string());
843        patterns.insert("street".to_string(), "address".to_string());
844        patterns.insert("city".to_string(), "string".to_string());
845        patterns.insert("state".to_string(), "string".to_string());
846        patterns.insert("zip".to_string(), "string".to_string());
847        patterns.insert("postal".to_string(), "string".to_string());
848
849        // Company patterns
850        patterns.insert("company".to_string(), "company".to_string());
851        patterns.insert("organization".to_string(), "company".to_string());
852        patterns.insert("corp".to_string(), "company".to_string());
853
854        // URL patterns
855        patterns.insert("url".to_string(), "url".to_string());
856        patterns.insert("website".to_string(), "url".to_string());
857        patterns.insert("link".to_string(), "url".to_string());
858
859        // Date patterns
860        patterns.insert("date".to_string(), "date".to_string());
861        patterns.insert("created".to_string(), "date".to_string());
862        patterns.insert("updated".to_string(), "date".to_string());
863        patterns.insert("timestamp".to_string(), "date".to_string());
864
865        // ID patterns
866        patterns.insert("id".to_string(), "uuid".to_string());
867        patterns.insert("uuid".to_string(), "uuid".to_string());
868        patterns.insert("guid".to_string(), "uuid".to_string());
869
870        // IP patterns
871        patterns.insert("ip".to_string(), "ip".to_string());
872        patterns.insert("ipv4".to_string(), "ip".to_string());
873        patterns.insert("ipv6".to_string(), "ip".to_string());
874
875        patterns
876    }
877
878    /// Initialize common schemas
879    fn initialize_common_schemas(&mut self) {
880        // Add common schema patterns here
881        // This could include User, Product, Order, etc.
882    }
883}
884
885impl Default for MockDataGenerator {
886    fn default() -> Self {
887        Self::new()
888    }
889}
890
891/// Result of mock data generation
892#[derive(Debug, Clone, serde::Serialize)]
893pub struct MockDataResult {
894    /// Generated data for each schema
895    pub schemas: HashMap<String, Value>,
896    /// Generated mock responses for each endpoint
897    pub responses: HashMap<String, MockResponse>,
898    /// Warnings encountered during generation
899    pub warnings: Vec<String>,
900    /// OpenAPI specification info
901    pub spec_info: OpenApiInfo,
902}
903
904/// Mock response data
905#[derive(Debug, Clone, serde::Serialize)]
906pub struct MockResponse {
907    /// HTTP status code
908    pub status: u16,
909    /// Response headers
910    pub headers: HashMap<String, String>,
911    /// Response body
912    pub body: Value,
913}
914
915/// OpenAPI specification info
916#[derive(Debug, Clone, serde::Serialize)]
917pub struct OpenApiInfo {
918    /// API title
919    pub title: String,
920    /// API version
921    pub version: String,
922    /// API description
923    pub description: Option<String>,
924}
925
926#[cfg(test)]
927mod tests {
928    use super::*;
929
930    #[test]
931    fn test_mock_generator_config_default() {
932        let config = MockGeneratorConfig::default();
933
934        assert!(config.realistic_mode);
935        assert_eq!(config.default_array_size, 3);
936        assert_eq!(config.max_array_size, 10);
937        assert!(config.include_optional_fields);
938        assert!(config.validate_generated_data);
939    }
940
941    #[test]
942    fn test_mock_generator_config_custom() {
943        let config = MockGeneratorConfig::new()
944            .realistic_mode(false)
945            .default_array_size(5)
946            .max_array_size(20)
947            .include_optional_fields(false)
948            .field_mapping("email".to_string(), "email".to_string())
949            .validate_generated_data(false);
950
951        assert!(!config.realistic_mode);
952        assert_eq!(config.default_array_size, 5);
953        assert_eq!(config.max_array_size, 20);
954        assert!(!config.include_optional_fields);
955        assert!(!config.validate_generated_data);
956        assert!(config.field_mappings.contains_key("email"));
957    }
958
959    #[test]
960    fn test_mock_data_generator_new() {
961        let generator = MockDataGenerator::new();
962
963        assert!(generator.config.realistic_mode);
964        assert!(!generator.field_patterns.is_empty());
965    }
966
967    #[test]
968    fn test_mock_data_generator_with_config() {
969        let config = MockGeneratorConfig::new().realistic_mode(false).default_array_size(10);
970
971        let generator = MockDataGenerator::with_config(config);
972
973        assert!(!generator.config.realistic_mode);
974        assert_eq!(generator.config.default_array_size, 10);
975    }
976
977    #[test]
978    fn test_determine_faker_type_custom_mapping() {
979        let mut config = MockGeneratorConfig::new();
980        config.field_mappings.insert("user_email".to_string(), "email".to_string());
981
982        let generator = MockDataGenerator::with_config(config);
983
984        let field = FieldDefinition::new("user_email".to_string(), "string".to_string());
985        let faker_type = generator.determine_faker_type(&field);
986
987        assert_eq!(faker_type, "email");
988    }
989
990    #[test]
991    fn test_determine_faker_type_pattern_matching() {
992        let generator = MockDataGenerator::new();
993
994        let field = FieldDefinition::new("email_address".to_string(), "string".to_string());
995        let faker_type = generator.determine_faker_type(&field);
996
997        assert_eq!(faker_type, "email");
998    }
999
1000    #[test]
1001    fn test_determine_faker_type_fallback() {
1002        let generator = MockDataGenerator::new();
1003
1004        let field = FieldDefinition::new("unknown_field".to_string(), "integer".to_string());
1005        let faker_type = generator.determine_faker_type(&field);
1006
1007        assert_eq!(faker_type, "integer");
1008    }
1009
1010    #[test]
1011    fn test_field_patterns_creation() {
1012        let patterns = MockDataGenerator::create_field_patterns();
1013
1014        assert!(patterns.contains_key("email"));
1015        assert!(patterns.contains_key("name"));
1016        assert!(patterns.contains_key("phone"));
1017        assert!(patterns.contains_key("address"));
1018        assert!(patterns.contains_key("company"));
1019        assert!(patterns.contains_key("url"));
1020        assert!(patterns.contains_key("date"));
1021        assert!(patterns.contains_key("id"));
1022        assert!(patterns.contains_key("ip"));
1023    }
1024
1025    #[test]
1026    fn test_generate_from_json_schema_simple() {
1027        let mut generator = MockDataGenerator::new();
1028
1029        let schema = json!({
1030            "type": "object",
1031            "properties": {
1032                "name": { "type": "string" },
1033                "age": { "type": "integer" },
1034                "email": { "type": "string" }
1035            },
1036            "required": ["name", "age"]
1037        });
1038
1039        let result = generator.generate_from_json_schema(&schema).unwrap();
1040
1041        assert!(result.is_object());
1042        let obj = result.as_object().unwrap();
1043        assert!(obj.contains_key("name"));
1044        assert!(obj.contains_key("age"));
1045        assert!(obj.contains_key("email"));
1046    }
1047
1048    #[test]
1049    fn test_generate_from_json_schema_with_constraints() {
1050        let mut generator = MockDataGenerator::new();
1051
1052        let schema = json!({
1053            "type": "object",
1054            "properties": {
1055                "age": {
1056                    "type": "integer",
1057                    "minimum": 18,
1058                    "maximum": 65
1059                },
1060                "name": {
1061                    "type": "string",
1062                    "minLength": 5,
1063                    "maxLength": 20
1064                }
1065            }
1066        });
1067
1068        let result = generator.generate_from_json_schema(&schema).unwrap();
1069
1070        assert!(result.is_object());
1071        let obj = result.as_object().unwrap();
1072
1073        if let Some(age) = obj.get("age") {
1074            if let Some(age_num) = age.as_i64() {
1075                assert!(age_num >= 18);
1076                assert!(age_num <= 65);
1077            }
1078        }
1079    }
1080
1081    #[test]
1082    fn test_generate_from_json_schema_with_enum() {
1083        let mut generator = MockDataGenerator::new();
1084
1085        let schema = json!({
1086            "type": "object",
1087            "properties": {
1088                "status": {
1089                    "type": "string",
1090                    "enum": ["active", "inactive", "pending"]
1091                }
1092            }
1093        });
1094
1095        let result = generator.generate_from_json_schema(&schema).unwrap();
1096
1097        assert!(result.is_object());
1098        let obj = result.as_object().unwrap();
1099
1100        if let Some(status) = obj.get("status") {
1101            if let Some(status_str) = status.as_str() {
1102                assert!(["active", "inactive", "pending"].contains(&status_str));
1103            }
1104        }
1105    }
1106}