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