1use prost_reflect::{DynamicMessage, FieldDescriptor, Kind, MessageDescriptor, Value};
8use rand::rngs::StdRng;
9use rand::{Rng, SeedableRng};
10use std::collections::HashMap;
11use tracing::debug;
12
13#[cfg(feature = "data-faker")]
14use mockforge_data::faker::EnhancedFaker;
15
16#[cfg(feature = "data-faker")]
18use fake::{
19 faker::name::en::{FirstName, LastName},
20 Fake,
21};
22
23#[derive(Debug, Clone)]
25pub struct SmartMockConfig {
26 pub field_name_inference: bool,
28 pub use_faker: bool,
30 pub field_overrides: HashMap<String, String>,
32 pub service_profiles: HashMap<String, ServiceProfile>,
34 pub max_depth: usize,
36 pub seed: Option<u64>,
38 pub deterministic: bool,
40}
41
42impl Default for SmartMockConfig {
43 fn default() -> Self {
44 Self {
45 field_name_inference: true,
46 use_faker: true,
47 field_overrides: HashMap::new(),
48 service_profiles: HashMap::new(),
49 max_depth: 5,
50 seed: None,
51 deterministic: false,
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct ServiceProfile {
59 pub field_mappings: HashMap<String, String>,
61 pub data_patterns: Vec<DataPattern>,
63 pub sequential_ids: bool,
65}
66
67#[derive(Debug, Clone)]
69pub enum DataPattern {
70 User,
72 Product,
74 Financial,
76 Location,
78 Custom(HashMap<String, String>),
80}
81
82pub struct SmartMockGenerator {
84 config: SmartMockConfig,
85 sequence_counter: u64,
87 #[cfg(feature = "data-faker")]
89 faker: Option<EnhancedFaker>,
90 rng: Option<StdRng>,
92}
93
94impl SmartMockGenerator {
95 pub fn new(config: SmartMockConfig) -> Self {
97 #[cfg(feature = "data-faker")]
98 let faker = if config.use_faker {
99 Some(EnhancedFaker::new())
100 } else {
101 None
102 };
103
104 let rng = if config.deterministic {
106 let seed = config.seed.unwrap_or(42); Some(StdRng::seed_from_u64(seed))
108 } else {
109 None
110 };
111
112 Self {
113 config,
114 sequence_counter: 1,
115 #[cfg(feature = "data-faker")]
116 faker,
117 rng,
118 }
119 }
120
121 pub fn new_with_seed(mut config: SmartMockConfig, seed: u64) -> Self {
123 config.seed = Some(seed);
124 config.deterministic = true;
125 Self::new(config)
126 }
127
128 pub fn reset(&mut self) {
130 self.sequence_counter = 1;
131 if let Some(seed) = self.config.seed {
132 self.rng = Some(StdRng::seed_from_u64(seed));
133 }
134 }
135
136 fn next_random<T>(&mut self) -> T
138 where
139 rand::distr::StandardUniform: rand::distr::Distribution<T>,
140 {
141 if let Some(ref mut rng) = self.rng {
142 rng.random()
143 } else {
144 rand::random()
145 }
146 }
147
148 #[allow(dead_code)] fn next_random_range(&mut self, min: i64, max: i64) -> i64 {
151 if let Some(ref mut rng) = self.rng {
152 rng.random_range(min..=max)
153 } else {
154 rand::rng().random_range(min..=max)
155 }
156 }
157
158 pub fn generate_value_for_field(
160 &mut self,
161 field: &FieldDescriptor,
162 service_name: &str,
163 method_name: &str,
164 depth: usize,
165 ) -> Value {
166 if depth >= self.config.max_depth {
167 return self.generate_depth_limit_value(field);
168 }
169
170 let field_name = field.name().to_lowercase();
171
172 debug!(
173 "Generating smart mock value for field: {} (type: {:?}) in service: {}, method: {}",
174 field.name(),
175 field.kind(),
176 service_name,
177 method_name
178 );
179
180 if let Some(override_value) = self.config.field_overrides.get(&field_name) {
182 return self.parse_override_value(override_value, field);
183 }
184
185 if let Some(profile) = self.config.service_profiles.get(service_name) {
187 if let Some(mapping) = profile.field_mappings.get(&field_name) {
188 return self.parse_override_value(mapping, field);
189 }
190 }
191
192 if self.config.field_name_inference {
194 if let Some(value) = self.infer_value_from_field_name(&field_name, field) {
195 return value;
196 }
197 }
198
199 #[cfg(feature = "data-faker")]
201 if self.config.use_faker && self.faker.is_some() {
202 if let Some(value) = self.generate_with_faker_safe(field) {
203 return value;
204 }
205 }
206
207 self.generate_basic_value_for_type(field, depth)
209 }
210
211 fn infer_value_from_field_name(
213 &mut self,
214 field_name: &str,
215 field: &FieldDescriptor,
216 ) -> Option<Value> {
217 match field.kind() {
218 Kind::String => {
219 let mock_value = match field_name {
220 name if name.contains("email") => {
222 #[cfg(feature = "data-faker")]
224 if let Some(faker) = &mut self.faker {
225 faker.email()
226 } else {
227 format!("user{}@example.com", self.next_sequence())
228 }
229 #[cfg(not(feature = "data-faker"))]
230 format!("user{}@example.com", self.next_sequence())
231 }
232 name if name.contains("name") && name.contains("first") => {
233 #[cfg(feature = "data-faker")]
234 if let Some(_faker) = &mut self.faker {
235 FirstName().fake()
236 } else {
237 "John".to_string()
238 }
239 #[cfg(not(feature = "data-faker"))]
240 "John".to_string()
241 }
242 name if name.contains("name") && name.contains("last") => {
243 #[cfg(feature = "data-faker")]
244 if let Some(_faker) = &mut self.faker {
245 LastName().fake()
246 } else {
247 "Doe".to_string()
248 }
249 #[cfg(not(feature = "data-faker"))]
250 "Doe".to_string()
251 }
252 name if name.contains("username") => format!("user{}", self.next_sequence()),
253 name if name.contains("full_name") || name.contains("display_name") => {
254 #[cfg(feature = "data-faker")]
255 if let Some(faker) = &mut self.faker {
256 faker.name()
257 } else {
258 "John Doe".to_string()
259 }
260 #[cfg(not(feature = "data-faker"))]
261 "John Doe".to_string()
262 }
263
264 name if name.contains("phone") => "+1-555-123-4567".to_string(),
266 name if name.contains("address") => "123 Main Street".to_string(),
267 name if name.contains("city") => "San Francisco".to_string(),
268 name if name.contains("state") || name.contains("region") => {
269 "California".to_string()
270 }
271 name if name.contains("country") => "United States".to_string(),
272 name if name.contains("zip") || name.contains("postal") => "94102".to_string(),
273
274 name if name.contains("title") => "Sample Product".to_string(),
276 name if name.contains("description") => {
277 "This is a sample product description".to_string()
278 }
279 name if name.contains("sku") || name.contains("product_id") => {
280 format!("SKU{:06}", self.next_sequence())
281 }
282 name if name.contains("category") => "Electronics".to_string(),
283 name if name.contains("brand") => "MockForge".to_string(),
284
285 name if name.contains("url") || name.contains("link") => {
287 "https://example.com".to_string()
288 }
289 name if name.contains("token") => {
290 format!("token_{}", self.generate_random_string(16))
291 }
292 name if name.contains("uuid") || name.contains("guid") => self.generate_uuid(),
293 name if name.contains("hash") => self.generate_random_string(32),
294 name if name.contains("version") => "1.0.0".to_string(),
295 name if name.contains("status") => "active".to_string(),
296
297 _ => return None,
299 };
300 Some(Value::String(mock_value))
301 }
302 Kind::Int32 | Kind::Int64 => {
303 let mock_value = match field_name {
304 name if name.contains("id") || name.contains("identifier") => {
306 self.next_sequence()
307 }
308
309 name if name.contains("count") || name.contains("quantity") => {
311 (self.next_random::<u32>() % 100 + 1) as u64
312 }
313 name if name.contains("age") => (self.next_random::<u32>() % 80 + 18) as u64,
314 name if name.contains("year") => (self.next_random::<u32>() % 30 + 1995) as u64,
315
316 name if name.contains("width")
318 || name.contains("height")
319 || name.contains("length") =>
320 {
321 (self.next_random::<u32>() % 1000 + 100) as u64
322 }
323 name if name.contains("weight") => {
324 (self.next_random::<u32>() % 1000 + 1) as u64
325 }
326
327 _ => return None,
328 };
329
330 Some(match field.kind() {
331 Kind::Int32 => Value::I32(mock_value as i32),
332 Kind::Int64 => Value::I64(mock_value as i64),
333 _ => unreachable!(),
334 })
335 }
336 Kind::Double | Kind::Float => {
337 let mock_value = match field_name {
338 name if name.contains("price") || name.contains("cost") => {
339 self.next_random::<f64>() * 1000.0 + 10.0
340 }
341 name if name.contains("rate") || name.contains("percentage") => {
342 self.next_random::<f64>() * 100.0
343 }
344 name if name.contains("latitude") => self.next_random::<f64>() * 180.0 - 90.0,
345 name if name.contains("longitude") => self.next_random::<f64>() * 360.0 - 180.0,
346 _ => return None,
347 };
348
349 Some(match field.kind() {
350 Kind::Double => Value::F64(mock_value),
351 Kind::Float => Value::F32(mock_value as f32),
352 _ => unreachable!(),
353 })
354 }
355 Kind::Bool => {
356 let mock_value = match field_name {
357 name if name.contains("active") || name.contains("enabled") => true,
358 name if name.contains("verified") || name.contains("confirmed") => true,
359 name if name.contains("deleted") || name.contains("archived") => false,
360 _ => self.next_random::<bool>(),
361 };
362 Some(Value::Bool(mock_value))
363 }
364 _ => None,
365 }
366 }
367
368 #[cfg(feature = "data-faker")]
370 fn generate_with_faker_safe(&mut self, field: &FieldDescriptor) -> Option<Value> {
371 let faker = self.faker.as_mut()?;
372 let field_name = field.name().to_lowercase();
373
374 match field.kind() {
375 Kind::String => {
376 let fake_data = match field_name.as_str() {
377 name if name.contains("email") => faker.email(),
379
380 name if name.contains("first") && name.contains("name") => FirstName().fake(),
382 name if name.contains("last") && name.contains("name") => LastName().fake(),
383 name if name.contains("name")
384 && !name.contains("file")
385 && !name.contains("path") =>
386 {
387 faker.name()
388 }
389
390 name if name.contains("phone") || name.contains("mobile") => faker.phone(),
392 name if name.contains("address") => faker.address(),
393
394 name if name.contains("company") || name.contains("organization") => {
396 faker.company()
397 }
398
399 name if name.contains("url") || name.contains("website") => faker.url(),
401 name if name.contains("ip") => faker.ip_address(),
402
403 name if name.contains("uuid") || name.contains("guid") => faker.uuid(),
405
406 name if name.contains("date")
408 || name.contains("time")
409 || name.contains("created")
410 || name.contains("updated") =>
411 {
412 faker.date_iso()
413 }
414
415 name if name.contains("color") || name.contains("colour") => faker.color(),
417
418 _ => return None,
420 };
421
422 Some(Value::String(fake_data))
423 }
424
425 Kind::Int32 | Kind::Int64 => {
426 let fake_value = match field_name.as_str() {
427 name if name.contains("age") => faker.int_range(18, 90),
429
430 name if name.contains("year") => faker.int_range(1990, 2024),
432
433 name if name.contains("count")
435 || name.contains("quantity")
436 || name.contains("amount") =>
437 {
438 faker.int_range(1, 1000)
439 }
440
441 name if name.contains("port") => faker.int_range(1024, 65535),
443
444 name if name.contains("id") || name.contains("identifier") => {
446 self.next_sequence() as i64
447 }
448 _ => faker.int_range(1, 100),
449 };
450
451 Some(match field.kind() {
452 Kind::Int32 => Value::I32(fake_value as i32),
453 Kind::Int64 => Value::I64(fake_value),
454 _ => unreachable!(),
455 })
456 }
457
458 Kind::Double | Kind::Float => {
459 let fake_value = match field_name.as_str() {
460 name if name.contains("price")
462 || name.contains("cost")
463 || name.contains("amount") =>
464 {
465 faker.float_range(1.0, 1000.0)
466 }
467
468 name if name.contains("rate") || name.contains("percent") => {
470 faker.float_range(0.0, 100.0)
471 }
472
473 name if name.contains("latitude") || name.contains("lat") => {
475 faker.float_range(-90.0, 90.0)
476 }
477 name if name.contains("longitude")
478 || name.contains("lng")
479 || name.contains("lon") =>
480 {
481 faker.float_range(-180.0, 180.0)
482 }
483
484 _ => faker.float_range(0.0, 100.0),
486 };
487
488 Some(match field.kind() {
489 Kind::Double => Value::F64(fake_value),
490 Kind::Float => Value::F32(fake_value as f32),
491 _ => unreachable!(),
492 })
493 }
494
495 Kind::Bool => {
496 let probability = match field_name.as_str() {
497 name if name.contains("active")
499 || name.contains("enabled")
500 || name.contains("verified") =>
501 {
502 0.8
503 }
504
505 name if name.contains("deleted")
507 || name.contains("archived")
508 || name.contains("disabled") =>
509 {
510 0.2
511 }
512
513 _ => 0.5,
515 };
516
517 Some(Value::Bool(faker.boolean(probability)))
518 }
519
520 _ => None,
521 }
522 }
523
524 #[cfg(not(feature = "data-faker"))]
525 fn generate_with_faker_safe(&mut self, _field: &FieldDescriptor) -> Option<Value> {
526 None
527 }
528
529 fn generate_basic_value_for_type(&mut self, field: &FieldDescriptor, depth: usize) -> Value {
531 match field.kind() {
532 Kind::String => Value::String(format!("mock_{}", field.name())),
533 Kind::Int32 => Value::I32(self.next_sequence() as i32),
534 Kind::Int64 => Value::I64(self.next_sequence() as i64),
535 Kind::Uint32 => Value::U32(self.next_sequence() as u32),
536 Kind::Uint64 => Value::U64(self.next_sequence()),
537 Kind::Sint32 => Value::I32(self.next_sequence() as i32),
538 Kind::Sint64 => Value::I64(self.next_sequence() as i64),
539 Kind::Fixed32 => Value::U32(self.next_sequence() as u32),
540 Kind::Fixed64 => Value::U64(self.next_sequence()),
541 Kind::Sfixed32 => Value::I32(self.next_sequence() as i32),
542 Kind::Sfixed64 => Value::I64(self.next_sequence() as i64),
543 Kind::Bool => Value::Bool(self.next_sequence().is_multiple_of(2)),
544 Kind::Double => Value::F64(self.next_random::<f64>() * 100.0),
545 Kind::Float => Value::F32(self.next_random::<f32>() * 100.0),
546 Kind::Bytes => {
547 Value::Bytes(format!("bytes_{}", self.next_sequence()).into_bytes().into())
548 }
549 Kind::Enum(enum_descriptor) => {
550 if let Some(first_value) = enum_descriptor.values().next() {
552 Value::EnumNumber(first_value.number())
553 } else {
554 Value::EnumNumber(0)
555 }
556 }
557 Kind::Message(message_descriptor) => {
558 self.generate_mock_message(&message_descriptor, depth + 1)
559 }
560 }
561 }
562
563 fn generate_mock_message(&mut self, descriptor: &MessageDescriptor, depth: usize) -> Value {
565 let mut message = prost_reflect::DynamicMessage::new(descriptor.clone());
566
567 if depth >= self.config.max_depth {
568 return Value::Message(message);
569 }
570
571 for field in descriptor.fields() {
572 let value = self.generate_basic_value_for_type(&field, depth);
573 message.set_field(&field, value);
574 }
575
576 Value::Message(message)
577 }
578
579 fn generate_depth_limit_value(&self, field: &FieldDescriptor) -> Value {
581 match field.kind() {
582 Kind::String => Value::String(format!("depth_limit_reached_{}", field.name())),
583 Kind::Int32 => Value::I32(999),
584 Kind::Int64 => Value::I64(999),
585 _ => Value::String("depth_limit".to_string()),
586 }
587 }
588
589 fn parse_override_value(&self, override_value: &str, field: &FieldDescriptor) -> Value {
591 match field.kind() {
592 Kind::String => Value::String(override_value.to_string()),
593 Kind::Int32 => Value::I32(override_value.parse().unwrap_or(0)),
594 Kind::Int64 => Value::I64(override_value.parse().unwrap_or(0)),
595 Kind::Bool => Value::Bool(override_value.parse().unwrap_or(false)),
596 Kind::Double => Value::F64(override_value.parse().unwrap_or(0.0)),
597 Kind::Float => Value::F32(override_value.parse().unwrap_or(0.0)),
598 _ => Value::String(override_value.to_string()),
599 }
600 }
601
602 pub fn next_sequence(&mut self) -> u64 {
604 let current = self.sequence_counter;
605 self.sequence_counter += 1;
606 current
607 }
608
609 pub fn generate_message(&mut self, descriptor: &MessageDescriptor) -> DynamicMessage {
611 match self.generate_mock_message(descriptor, 0) {
612 Value::Message(msg) => msg,
613 _ => panic!("generate_mock_message should always return a Message Value"),
614 }
615 }
616
617 pub fn generate_random_string(&mut self, length: usize) -> String {
619 use rand::distr::Alphanumeric;
620 use rand::{rng, Rng};
621
622 if let Some(ref mut rng) = self.rng {
623 rng.sample_iter(&Alphanumeric).take(length).map(char::from).collect()
624 } else {
625 rng().sample_iter(&Alphanumeric).take(length).map(char::from).collect()
626 }
627 }
628
629 pub fn generate_uuid(&mut self) -> String {
631 format!(
632 "{:08x}-{:04x}-{:04x}-{:04x}-{:12x}",
633 self.next_random::<u32>(),
634 self.next_random::<u16>(),
635 self.next_random::<u16>(),
636 self.next_random::<u16>(),
637 self.next_random::<u64>() & 0xffffffffffff,
638 )
639 }
640
641 pub fn config(&self) -> &SmartMockConfig {
643 &self.config
644 }
645
646 #[cfg(feature = "data-faker")]
648 pub fn is_faker_enabled(&self) -> bool {
649 self.config.use_faker && self.faker.is_some()
650 }
651
652 #[cfg(not(feature = "data-faker"))]
653 pub fn is_faker_enabled(&self) -> bool {
654 false
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn test_smart_mock_generator() {
664 let config = SmartMockConfig::default();
665 let mut generator = SmartMockGenerator::new(config);
666
667 assert_eq!(generator.next_sequence(), 1);
669 assert_eq!(generator.next_sequence(), 2);
670
671 let uuid = generator.generate_uuid();
673 assert!(uuid.contains('-'));
674 assert_eq!(uuid.matches('-').count(), 4);
675 }
676
677 #[test]
678 fn test_field_name_inference() {
679 let config = SmartMockConfig::default();
680 let mut generator = SmartMockGenerator::new(config);
681
682 assert!(generator.generate_random_string(10).len() == 10);
685 }
686
687 #[test]
688 fn test_deterministic_seeding() {
689 let config1 = SmartMockConfig {
691 seed: Some(12345),
692 deterministic: true,
693 ..Default::default()
694 };
695 let config2 = SmartMockConfig {
696 seed: Some(12345),
697 deterministic: true,
698 ..Default::default()
699 };
700
701 let mut gen1 = SmartMockGenerator::new(config1);
702 let mut gen2 = SmartMockGenerator::new(config2);
703
704 assert_eq!(gen1.generate_uuid(), gen2.generate_uuid());
706 assert_eq!(gen1.generate_random_string(10), gen2.generate_random_string(10));
707
708 let config3 = SmartMockConfig {
710 seed: Some(54321),
711 deterministic: true,
712 ..Default::default()
713 };
714 let mut gen3 = SmartMockGenerator::new(config3);
715
716 gen1.reset();
718 gen3.reset();
719
720 assert_ne!(gen1.generate_uuid(), gen3.generate_uuid());
722 }
723
724 #[test]
725 fn test_new_with_seed() {
726 let config = SmartMockConfig::default();
727 let mut gen1 = SmartMockGenerator::new_with_seed(config.clone(), 999);
728 let mut gen2 = SmartMockGenerator::new_with_seed(config, 999);
729
730 assert!(gen1.config.deterministic);
732 assert!(gen2.config.deterministic);
733 assert_eq!(gen1.config.seed, Some(999));
734 assert_eq!(gen2.config.seed, Some(999));
735
736 let uuid1 = gen1.generate_uuid();
737 let uuid2 = gen2.generate_uuid();
738 assert_eq!(uuid1, uuid2);
739 }
740
741 #[test]
742 fn test_generator_reset() {
743 let config = SmartMockConfig {
744 seed: Some(777),
745 deterministic: true,
746 ..Default::default()
747 };
748
749 let mut generator = SmartMockGenerator::new(config);
750
751 let uuid1 = generator.generate_uuid();
753 let seq1 = generator.next_sequence();
754 let seq2 = generator.next_sequence();
755
756 assert_eq!(seq1, 1);
757 assert_eq!(seq2, 2);
758
759 generator.reset();
761 let uuid2 = generator.generate_uuid();
762 let seq3 = generator.next_sequence();
763 let seq4 = generator.next_sequence();
764
765 assert_eq!(uuid1, uuid2); assert_eq!(seq3, 1); assert_eq!(seq4, 2);
768 }
769
770 #[test]
771 fn test_deterministic_vs_non_deterministic() {
772 let mut gen_random = SmartMockGenerator::new(SmartMockConfig::default());
774
775 let mut gen_deterministic =
777 SmartMockGenerator::new_with_seed(SmartMockConfig::default(), 42);
778
779 let det_uuid1 = gen_deterministic.generate_uuid();
781 let det_uuid2 = gen_deterministic.generate_uuid();
782
783 gen_deterministic.reset();
784 let det_uuid1_repeat = gen_deterministic.generate_uuid();
785 let det_uuid2_repeat = gen_deterministic.generate_uuid();
786
787 assert_eq!(det_uuid1, det_uuid1_repeat);
789 assert_eq!(det_uuid2, det_uuid2_repeat);
790
791 let _random_uuid = gen_random.generate_uuid();
793 }
794
795 #[cfg(feature = "data-faker")]
796 #[test]
797 fn test_faker_integration() {
798 let config = SmartMockConfig {
799 use_faker: true,
800 ..Default::default()
801 };
802 let mut generator = SmartMockGenerator::new(config);
803
804 assert!(generator.faker.is_some());
806
807 assert_eq!(generator.next_sequence(), 1);
809 assert_eq!(generator.next_sequence(), 2);
810 }
811}