1use crate::faker::EnhancedFaker;
8use crate::schema::{FieldDefinition, SchemaDefinition};
9use crate::{Error, Result};
10use serde_json::{json, Value};
11use std::collections::HashMap;
12use tracing::{debug, info, warn};
13
14#[derive(Debug, Clone)]
16pub struct MockGeneratorConfig {
17 pub realistic_mode: bool,
19 pub default_array_size: usize,
21 pub max_array_size: usize,
23 pub include_optional_fields: bool,
25 pub field_mappings: HashMap<String, String>,
27 pub validate_generated_data: bool,
29}
30
31impl Default for MockGeneratorConfig {
32 fn default() -> Self {
33 Self {
34 realistic_mode: true,
35 default_array_size: 3,
36 max_array_size: 10,
37 include_optional_fields: true,
38 field_mappings: HashMap::new(),
39 validate_generated_data: true,
40 }
41 }
42}
43
44impl MockGeneratorConfig {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn realistic_mode(mut self, enabled: bool) -> Self {
52 self.realistic_mode = enabled;
53 self
54 }
55
56 pub fn default_array_size(mut self, size: usize) -> Self {
58 self.default_array_size = size;
59 self
60 }
61
62 pub fn max_array_size(mut self, size: usize) -> Self {
64 self.max_array_size = size;
65 self
66 }
67
68 pub fn include_optional_fields(mut self, include: bool) -> Self {
70 self.include_optional_fields = include;
71 self
72 }
73
74 pub fn field_mapping(mut self, field_name: String, faker_type: String) -> Self {
76 self.field_mappings.insert(field_name, faker_type);
77 self
78 }
79
80 pub fn validate_generated_data(mut self, validate: bool) -> Self {
82 self.validate_generated_data = validate;
83 self
84 }
85}
86
87#[derive(Debug)]
89pub struct MockDataGenerator {
90 config: MockGeneratorConfig,
92 faker: EnhancedFaker,
94 #[allow(dead_code)]
96 schema_registry: HashMap<String, SchemaDefinition>,
97 field_patterns: HashMap<String, String>,
99}
100
101impl MockDataGenerator {
102 pub fn new() -> Self {
104 Self::with_config(MockGeneratorConfig::new())
105 }
106
107 pub fn with_config(config: MockGeneratorConfig) -> Self {
109 let mut generator = Self {
110 config,
111 faker: EnhancedFaker::new(),
112 schema_registry: HashMap::new(),
113 field_patterns: Self::create_field_patterns(),
114 };
115
116 generator.initialize_common_schemas();
118 generator
119 }
120
121 pub fn generate_from_openapi_spec(&mut self, spec: &Value) -> Result<MockDataResult> {
123 info!("Generating mock data from OpenAPI specification");
124
125 let openapi_spec = self.parse_openapi_spec(spec)?;
127
128 let schemas = self.extract_schemas_from_spec(spec)?;
130
131 let mut generated_data = HashMap::new();
133 let mut warnings = Vec::new();
134
135 for (schema_name, schema_def) in schemas {
136 debug!("Generating data for schema: {}", schema_name);
137
138 match self.generate_schema_data(&schema_def) {
139 Ok(data) => {
140 generated_data.insert(schema_name, data);
141 }
142 Err(e) => {
143 let warning =
144 format!("Failed to generate data for schema '{}': {}", schema_name, e);
145 warn!("{}", warning);
146 warnings.push(warning);
147 }
148 }
149 }
150
151 let mut mock_responses = HashMap::new();
153 for (path, path_item) in &openapi_spec.paths {
154 for (method, operation) in path_item.operations() {
155 let endpoint_key = format!("{} {}", method.to_uppercase(), path);
156
157 if let Some(response_data) = self.generate_endpoint_response(operation)? {
159 mock_responses.insert(endpoint_key, response_data);
160 }
161 }
162 }
163
164 Ok(MockDataResult {
165 schemas: generated_data,
166 responses: mock_responses,
167 warnings,
168 spec_info: openapi_spec.info,
169 })
170 }
171
172 pub fn generate_from_json_schema(&mut self, schema: &Value) -> Result<Value> {
174 debug!("Generating mock data from JSON Schema");
175
176 let schema_def = SchemaDefinition::from_json_schema(schema)?;
178
179 self.generate_schema_data(&schema_def)
181 }
182
183 fn generate_schema_data(&mut self, schema: &SchemaDefinition) -> Result<Value> {
185 let mut object = serde_json::Map::new();
186
187 for field in &schema.fields {
188 if !field.required && !self.config.include_optional_fields {
190 continue;
191 }
192
193 let faker_type = self.determine_faker_type(field);
195
196 let value = self.generate_field_value(field, &faker_type)?;
198
199 if self.config.validate_generated_data {
201 field.validate_value(&value)?;
202 }
203
204 object.insert(field.name.clone(), value);
205 }
206
207 Ok(Value::Object(object))
208 }
209
210 fn generate_endpoint_response(
212 &mut self,
213 operation: &openapiv3::Operation,
214 ) -> Result<Option<MockResponse>> {
215 let response_schema = self.find_best_response_schema(operation)?;
217
218 if let Some(schema) = response_schema {
219 let mock_data = self.generate_from_json_schema(&schema)?;
220
221 Ok(Some(MockResponse {
222 status: 200, headers: HashMap::new(),
224 body: mock_data,
225 }))
226 } else {
227 Ok(None)
228 }
229 }
230
231 fn find_best_response_schema(&self, operation: &openapiv3::Operation) -> Result<Option<Value>> {
233 let responses = &operation.responses;
234
235 if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(200)) {
237 if let Some(schema) = self.extract_response_schema(response)? {
238 return Ok(Some(schema));
239 }
240 }
241
242 if let Some(response) = responses.responses.get(&openapiv3::StatusCode::Code(201)) {
244 if let Some(schema) = self.extract_response_schema(response)? {
245 return Ok(Some(schema));
246 }
247 }
248
249 for (code, response) in &responses.responses {
251 if let openapiv3::StatusCode::Code(status_code) = code {
252 if *status_code >= 200 && *status_code < 300 {
253 if let Some(schema) = self.extract_response_schema(response)? {
254 return Ok(Some(schema));
255 }
256 }
257 }
258 }
259
260 Ok(None)
261 }
262
263 fn extract_response_schema(
265 &self,
266 response: &openapiv3::ReferenceOr<openapiv3::Response>,
267 ) -> Result<Option<Value>> {
268 match response {
269 openapiv3::ReferenceOr::Item(response) => {
270 let content = &response.content;
271 if let Some(json_content) = content.get("application/json") {
273 if let Some(schema) = &json_content.schema {
274 return Ok(Some(serde_json::to_value(schema)?));
275 }
276 }
277
278 for (_, media_type) in content {
280 if let Some(schema) = &media_type.schema {
281 return Ok(Some(serde_json::to_value(schema)?));
282 }
283 }
284
285 Ok(None)
286 }
287 openapiv3::ReferenceOr::Reference { .. } => {
288 Ok(None)
290 }
291 }
292 }
293
294 fn determine_faker_type(&self, field: &FieldDefinition) -> String {
296 let field_name = field.name.to_lowercase();
297
298 if let Some(mapped_type) = self.config.field_mappings.get(&field_name) {
300 return mapped_type.clone();
301 }
302
303 for (pattern, faker_type) in &self.field_patterns {
305 if field_name.contains(pattern) {
306 return faker_type.clone();
307 }
308 }
309
310 field.field_type.clone()
312 }
313
314 fn generate_field_value(&mut self, field: &FieldDefinition, faker_type: &str) -> Result<Value> {
316 if let Some(template) = &field.faker_template {
318 return Ok(self.faker.generate_by_type(template));
319 }
320
321 let value = self.faker.generate_by_type(faker_type);
323
324 self.apply_constraints(&value, field)
326 }
327
328 fn apply_constraints(&mut self, value: &Value, field: &FieldDefinition) -> Result<Value> {
330 let mut constrained_value = value.clone();
331
332 if let Value::Number(num) = value {
334 if let Some(minimum) = field.constraints.get("minimum") {
335 if let Some(min_val) = minimum.as_f64() {
336 if num.as_f64().unwrap_or(0.0) < min_val {
337 constrained_value = json!(min_val);
338 }
339 }
340 }
341
342 if let Some(maximum) = field.constraints.get("maximum") {
343 if let Some(max_val) = maximum.as_f64() {
344 if num.as_f64().unwrap_or(0.0) > max_val {
345 constrained_value = json!(max_val);
346 }
347 }
348 }
349 }
350
351 if let Value::String(s) = value {
353 let mut constrained_string = s.clone();
354
355 if let Some(min_length) = field.constraints.get("minLength") {
357 if let Some(min_len) = min_length.as_u64() {
358 if constrained_string.len() < min_len as usize {
359 let padding_needed = min_len as usize - constrained_string.len();
361 let padding = self.faker.string(padding_needed);
362 constrained_string = format!("{}{}", constrained_string, padding);
363 }
364 }
365 }
366
367 if let Some(max_length) = field.constraints.get("maxLength") {
368 if let Some(max_len) = max_length.as_u64() {
369 if constrained_string.len() > max_len as usize {
370 constrained_string.truncate(max_len as usize);
371 }
372 }
373 }
374
375 constrained_value = json!(constrained_string);
376 }
377
378 if let Some(enum_values) = field.constraints.get("enum") {
380 if let Some(enum_array) = enum_values.as_array() {
381 if !enum_array.is_empty() {
382 if let Some(random_value) = self.faker.random_element(enum_array) {
383 constrained_value = random_value.clone();
384 }
385 }
386 }
387 }
388
389 Ok(constrained_value)
390 }
391
392 fn parse_openapi_spec(&self, spec: &Value) -> Result<OpenApiSpec> {
394 let spec_obj = spec
397 .as_object()
398 .ok_or_else(|| Error::generic("Invalid OpenAPI specification"))?;
399
400 let info = spec_obj
401 .get("info")
402 .ok_or_else(|| Error::generic("Missing 'info' section in OpenAPI spec"))?;
403
404 let title = info.get("title").and_then(|t| t.as_str()).unwrap_or("Unknown API").to_string();
405
406 let version = info.get("version").and_then(|v| v.as_str()).unwrap_or("1.0.0").to_string();
407
408 let description = info.get("description").and_then(|d| d.as_str()).map(|s| s.to_string());
409
410 Ok(OpenApiSpec {
411 info: OpenApiInfo {
412 title,
413 version,
414 description,
415 },
416 paths: HashMap::new(), })
418 }
419
420 fn extract_schemas_from_spec(
422 &mut self,
423 spec: &Value,
424 ) -> Result<HashMap<String, SchemaDefinition>> {
425 let mut schemas = HashMap::new();
426
427 if let Some(components) = spec.get("components") {
429 if let Some(schemas_section) = components.get("schemas") {
430 if let Some(schema_obj) = schemas_section.as_object() {
431 for (name, schema_def) in schema_obj {
432 let schema = SchemaDefinition::from_json_schema(schema_def)?;
433 schemas.insert(name.clone(), schema);
434 }
435 }
436 }
437 }
438
439 if let Some(paths) = spec.get("paths") {
441 if let Some(paths_obj) = paths.as_object() {
442 for (path, path_item) in paths_obj {
443 if let Some(path_obj) = path_item.as_object() {
444 for (method, operation) in path_obj {
445 if let Some(op_obj) = operation.as_object() {
446 if let Some(request_body) = op_obj.get("requestBody") {
448 if let Some(content) = request_body.get("content") {
449 if let Some(json_content) = content.get("application/json")
450 {
451 if let Some(schema) = json_content.get("schema") {
452 let schema_name = format!(
453 "{}_{}_request",
454 path.replace("/", "_").trim_start_matches("_"),
455 method
456 );
457 let schema_def =
458 SchemaDefinition::from_json_schema(schema)?;
459 schemas.insert(schema_name, schema_def);
460 }
461 }
462 }
463 }
464
465 if let Some(responses) = op_obj.get("responses") {
467 if let Some(resp_obj) = responses.as_object() {
468 for (status_code, response) in resp_obj {
469 if let Some(content) = response.get("content") {
470 if let Some(json_content) =
471 content.get("application/json")
472 {
473 if let Some(schema) = json_content.get("schema")
474 {
475 let schema_name = format!(
476 "{}_{}_response_{}",
477 path.replace("/", "_")
478 .trim_start_matches("_"),
479 method,
480 status_code
481 );
482 let schema_def =
483 SchemaDefinition::from_json_schema(
484 schema,
485 )?;
486 schemas.insert(schema_name, schema_def);
487 }
488 }
489 }
490 }
491 }
492 }
493 }
494 }
495 }
496 }
497 }
498 }
499
500 Ok(schemas)
501 }
502
503 fn create_field_patterns() -> HashMap<String, String> {
505 let mut patterns = HashMap::new();
506
507 patterns.insert("email".to_string(), "email".to_string());
509 patterns.insert("mail".to_string(), "email".to_string());
510
511 patterns.insert("name".to_string(), "name".to_string());
513 patterns.insert("firstname".to_string(), "name".to_string());
514 patterns.insert("lastname".to_string(), "name".to_string());
515 patterns.insert("username".to_string(), "name".to_string());
516
517 patterns.insert("phone".to_string(), "phone".to_string());
519 patterns.insert("mobile".to_string(), "phone".to_string());
520 patterns.insert("telephone".to_string(), "phone".to_string());
521
522 patterns.insert("address".to_string(), "address".to_string());
524 patterns.insert("street".to_string(), "address".to_string());
525 patterns.insert("city".to_string(), "string".to_string());
526 patterns.insert("state".to_string(), "string".to_string());
527 patterns.insert("zip".to_string(), "string".to_string());
528 patterns.insert("postal".to_string(), "string".to_string());
529
530 patterns.insert("company".to_string(), "company".to_string());
532 patterns.insert("organization".to_string(), "company".to_string());
533 patterns.insert("corp".to_string(), "company".to_string());
534
535 patterns.insert("url".to_string(), "url".to_string());
537 patterns.insert("website".to_string(), "url".to_string());
538 patterns.insert("link".to_string(), "url".to_string());
539
540 patterns.insert("date".to_string(), "date".to_string());
542 patterns.insert("created".to_string(), "date".to_string());
543 patterns.insert("updated".to_string(), "date".to_string());
544 patterns.insert("timestamp".to_string(), "date".to_string());
545
546 patterns.insert("id".to_string(), "uuid".to_string());
548 patterns.insert("uuid".to_string(), "uuid".to_string());
549 patterns.insert("guid".to_string(), "uuid".to_string());
550
551 patterns.insert("ip".to_string(), "ip".to_string());
553 patterns.insert("ipv4".to_string(), "ip".to_string());
554 patterns.insert("ipv6".to_string(), "ip".to_string());
555
556 patterns
557 }
558
559 fn initialize_common_schemas(&mut self) {
561 }
564}
565
566impl Default for MockDataGenerator {
567 fn default() -> Self {
568 Self::new()
569 }
570}
571
572#[derive(Debug, Clone, serde::Serialize)]
574pub struct MockDataResult {
575 pub schemas: HashMap<String, Value>,
577 pub responses: HashMap<String, MockResponse>,
579 pub warnings: Vec<String>,
581 pub spec_info: OpenApiInfo,
583}
584
585#[derive(Debug, Clone, serde::Serialize)]
587pub struct MockResponse {
588 pub status: u16,
590 pub headers: HashMap<String, String>,
592 pub body: Value,
594}
595
596#[derive(Debug, Clone, serde::Serialize)]
598pub struct OpenApiInfo {
599 pub title: String,
601 pub version: String,
603 pub description: Option<String>,
605}
606
607#[derive(Debug)]
609struct OpenApiSpec {
610 info: OpenApiInfo,
611 paths: HashMap<String, PathItem>,
612}
613
614#[derive(Debug)]
616struct PathItem {
617 operations: HashMap<String, openapiv3::Operation>,
618}
619
620impl PathItem {
621 fn operations(&self) -> &HashMap<String, openapiv3::Operation> {
622 &self.operations
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn test_mock_generator_config_default() {
632 let config = MockGeneratorConfig::default();
633
634 assert!(config.realistic_mode);
635 assert_eq!(config.default_array_size, 3);
636 assert_eq!(config.max_array_size, 10);
637 assert!(config.include_optional_fields);
638 assert!(config.validate_generated_data);
639 }
640
641 #[test]
642 fn test_mock_generator_config_custom() {
643 let config = MockGeneratorConfig::new()
644 .realistic_mode(false)
645 .default_array_size(5)
646 .max_array_size(20)
647 .include_optional_fields(false)
648 .field_mapping("email".to_string(), "email".to_string())
649 .validate_generated_data(false);
650
651 assert!(!config.realistic_mode);
652 assert_eq!(config.default_array_size, 5);
653 assert_eq!(config.max_array_size, 20);
654 assert!(!config.include_optional_fields);
655 assert!(!config.validate_generated_data);
656 assert!(config.field_mappings.contains_key("email"));
657 }
658
659 #[test]
660 fn test_mock_data_generator_new() {
661 let generator = MockDataGenerator::new();
662
663 assert!(generator.config.realistic_mode);
664 assert!(!generator.field_patterns.is_empty());
665 }
666
667 #[test]
668 fn test_mock_data_generator_with_config() {
669 let config = MockGeneratorConfig::new().realistic_mode(false).default_array_size(10);
670
671 let generator = MockDataGenerator::with_config(config);
672
673 assert!(!generator.config.realistic_mode);
674 assert_eq!(generator.config.default_array_size, 10);
675 }
676
677 #[test]
678 fn test_determine_faker_type_custom_mapping() {
679 let mut config = MockGeneratorConfig::new();
680 config.field_mappings.insert("user_email".to_string(), "email".to_string());
681
682 let generator = MockDataGenerator::with_config(config);
683
684 let field = FieldDefinition::new("user_email".to_string(), "string".to_string());
685 let faker_type = generator.determine_faker_type(&field);
686
687 assert_eq!(faker_type, "email");
688 }
689
690 #[test]
691 fn test_determine_faker_type_pattern_matching() {
692 let generator = MockDataGenerator::new();
693
694 let field = FieldDefinition::new("email_address".to_string(), "string".to_string());
695 let faker_type = generator.determine_faker_type(&field);
696
697 assert_eq!(faker_type, "email");
698 }
699
700 #[test]
701 fn test_determine_faker_type_fallback() {
702 let generator = MockDataGenerator::new();
703
704 let field = FieldDefinition::new("unknown_field".to_string(), "integer".to_string());
705 let faker_type = generator.determine_faker_type(&field);
706
707 assert_eq!(faker_type, "integer");
708 }
709
710 #[test]
711 fn test_field_patterns_creation() {
712 let patterns = MockDataGenerator::create_field_patterns();
713
714 assert!(patterns.contains_key("email"));
715 assert!(patterns.contains_key("name"));
716 assert!(patterns.contains_key("phone"));
717 assert!(patterns.contains_key("address"));
718 assert!(patterns.contains_key("company"));
719 assert!(patterns.contains_key("url"));
720 assert!(patterns.contains_key("date"));
721 assert!(patterns.contains_key("id"));
722 assert!(patterns.contains_key("ip"));
723 }
724
725 #[test]
726 fn test_generate_from_json_schema_simple() {
727 let mut generator = MockDataGenerator::new();
728
729 let schema = json!({
730 "type": "object",
731 "properties": {
732 "name": { "type": "string" },
733 "age": { "type": "integer" },
734 "email": { "type": "string" }
735 },
736 "required": ["name", "age"]
737 });
738
739 let result = generator.generate_from_json_schema(&schema).unwrap();
740
741 assert!(result.is_object());
742 let obj = result.as_object().unwrap();
743 assert!(obj.contains_key("name"));
744 assert!(obj.contains_key("age"));
745 assert!(obj.contains_key("email"));
746 }
747
748 #[test]
749 fn test_generate_from_json_schema_with_constraints() {
750 let mut generator = MockDataGenerator::new();
751
752 let schema = json!({
753 "type": "object",
754 "properties": {
755 "age": {
756 "type": "integer",
757 "minimum": 18,
758 "maximum": 65
759 },
760 "name": {
761 "type": "string",
762 "minLength": 5,
763 "maxLength": 20
764 }
765 }
766 });
767
768 let result = generator.generate_from_json_schema(&schema).unwrap();
769
770 assert!(result.is_object());
771 let obj = result.as_object().unwrap();
772
773 if let Some(age) = obj.get("age") {
774 if let Some(age_num) = age.as_i64() {
775 assert!(age_num >= 18);
776 assert!(age_num <= 65);
777 }
778 }
779 }
780
781 #[test]
782 fn test_generate_from_json_schema_with_enum() {
783 let mut generator = MockDataGenerator::new();
784
785 let schema = json!({
786 "type": "object",
787 "properties": {
788 "status": {
789 "type": "string",
790 "enum": ["active", "inactive", "pending"]
791 }
792 }
793 });
794
795 let result = generator.generate_from_json_schema(&schema).unwrap();
796
797 assert!(result.is_object());
798 let obj = result.as_object().unwrap();
799
800 if let Some(status) = obj.get("status") {
801 if let Some(status_str) = status.as_str() {
802 assert!(["active", "inactive", "pending"].contains(&status_str));
803 }
804 }
805 }
806}