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 {
153 if let Some(ref mut rng) = self.rng {
154 rng.random_range(min..=max)
155 } else {
156 rand::rng().random_range(min..=max)
157 }
158 }
159
160 pub fn generate_value_for_field(
162 &mut self,
163 field: &FieldDescriptor,
164 service_name: &str,
165 method_name: &str,
166 depth: usize,
167 ) -> Value {
168 if depth >= self.config.max_depth {
169 return self.generate_depth_limit_value(field);
170 }
171
172 let field_name = field.name().to_lowercase();
173
174 debug!(
175 "Generating smart mock value for field: {} (type: {:?}) in service: {}, method: {}",
176 field.name(),
177 field.kind(),
178 service_name,
179 method_name
180 );
181
182 if let Some(override_value) = self.config.field_overrides.get(&field_name) {
184 return self.parse_override_value(override_value, field);
185 }
186
187 if let Some(profile) = self.config.service_profiles.get(service_name) {
189 if let Some(mapping) = profile.field_mappings.get(&field_name) {
190 return self.parse_override_value(mapping, field);
191 }
192 }
193
194 if self.config.field_name_inference {
196 if let Some(value) = self.infer_value_from_field_name(&field_name, field) {
197 return value;
198 }
199 }
200
201 #[cfg(feature = "data-faker")]
203 if self.config.use_faker && self.faker.is_some() {
204 if let Some(value) = self.generate_with_faker_safe(field) {
205 return value;
206 }
207 }
208
209 self.generate_basic_value_for_type(field, depth)
211 }
212
213 fn infer_value_from_field_name(
215 &mut self,
216 field_name: &str,
217 field: &FieldDescriptor,
218 ) -> Option<Value> {
219 match field.kind() {
220 Kind::String => {
221 let mock_value = match field_name {
222 name if name.contains("email") => {
224 #[cfg(feature = "data-faker")]
226 if let Some(faker) = &mut self.faker {
227 faker.email()
228 } else {
229 format!("user{}@example.com", self.next_sequence())
230 }
231 #[cfg(not(feature = "data-faker"))]
232 format!("user{}@example.com", self.next_sequence())
233 }
234 name if name.contains("name") && name.contains("first") => {
235 #[cfg(feature = "data-faker")]
236 if let Some(_faker) = &mut self.faker {
237 FirstName().fake()
238 } else {
239 "John".to_string()
240 }
241 #[cfg(not(feature = "data-faker"))]
242 "John".to_string()
243 }
244 name if name.contains("name") && name.contains("last") => {
245 #[cfg(feature = "data-faker")]
246 if let Some(_faker) = &mut self.faker {
247 LastName().fake()
248 } else {
249 "Doe".to_string()
250 }
251 #[cfg(not(feature = "data-faker"))]
252 "Doe".to_string()
253 }
254 name if name.contains("username") => format!("user{}", self.next_sequence()),
255 name if name.contains("full_name") || name.contains("display_name") => {
256 #[cfg(feature = "data-faker")]
257 if let Some(faker) = &mut self.faker {
258 faker.name()
259 } else {
260 "John Doe".to_string()
261 }
262 #[cfg(not(feature = "data-faker"))]
263 "John Doe".to_string()
264 }
265
266 name if name.contains("phone") => "+1-555-123-4567".to_string(),
268 name if name.contains("address") => "123 Main Street".to_string(),
269 name if name.contains("city") => "San Francisco".to_string(),
270 name if name.contains("state") || name.contains("region") => {
271 "California".to_string()
272 }
273 name if name.contains("country") => "United States".to_string(),
274 name if name.contains("zip") || name.contains("postal") => "94102".to_string(),
275
276 name if name.contains("title") => "Sample Product".to_string(),
278 name if name.contains("description") => {
279 "This is a sample product description".to_string()
280 }
281 name if name.contains("sku") || name.contains("product_id") => {
282 format!("SKU{:06}", self.next_sequence())
283 }
284 name if name.contains("category") => "Electronics".to_string(),
285 name if name.contains("brand") => "MockForge".to_string(),
286
287 name if name.contains("url") || name.contains("link") => {
289 "https://example.com".to_string()
290 }
291 name if name.contains("token") => {
292 format!("token_{}", self.generate_random_string(16))
293 }
294 name if name.contains("uuid") || name.contains("guid") => self.generate_uuid(),
295 name if name.contains("hash") => self.generate_random_string(32),
296 name if name.contains("version") => "1.0.0".to_string(),
297 name if name.contains("status") => "active".to_string(),
298
299 _ => return None,
301 };
302 Some(Value::String(mock_value))
303 }
304 Kind::Int32 | Kind::Int64 => {
305 let mock_value = match field_name {
306 name if name.contains("id") || name.contains("identifier") => {
308 self.next_sequence()
309 }
310
311 name if name.contains("count") || name.contains("quantity") => {
313 (self.next_random::<u32>() % 100 + 1) as u64
314 }
315 name if name.contains("age") => (self.next_random::<u32>() % 80 + 18) as u64,
316 name if name.contains("year") => (self.next_random::<u32>() % 30 + 1995) as u64,
317
318 name if name.contains("width")
320 || name.contains("height")
321 || name.contains("length") =>
322 {
323 (self.next_random::<u32>() % 1000 + 100) as u64
324 }
325 name if name.contains("weight") => {
326 (self.next_random::<u32>() % 1000 + 1) as u64
327 }
328
329 _ => return None,
330 };
331
332 Some(match field.kind() {
333 Kind::Int32 => Value::I32(mock_value as i32),
334 Kind::Int64 => Value::I64(mock_value as i64),
335 _ => unreachable!(),
336 })
337 }
338 Kind::Double | Kind::Float => {
339 let mock_value = match field_name {
340 name if name.contains("price") || name.contains("cost") => {
341 self.next_random::<f64>() * 1000.0 + 10.0
342 }
343 name if name.contains("rate") || name.contains("percentage") => {
344 self.next_random::<f64>() * 100.0
345 }
346 name if name.contains("latitude") => self.next_random::<f64>() * 180.0 - 90.0,
347 name if name.contains("longitude") => self.next_random::<f64>() * 360.0 - 180.0,
348 _ => return None,
349 };
350
351 Some(match field.kind() {
352 Kind::Double => Value::F64(mock_value),
353 Kind::Float => Value::F32(mock_value as f32),
354 _ => unreachable!(),
355 })
356 }
357 Kind::Bool => {
358 let mock_value = match field_name {
359 name if name.contains("active") || name.contains("enabled") => true,
360 name if name.contains("verified") || name.contains("confirmed") => true,
361 name if name.contains("deleted") || name.contains("archived") => false,
362 _ => self.next_random::<bool>(),
363 };
364 Some(Value::Bool(mock_value))
365 }
366 _ => None,
367 }
368 }
369
370 #[cfg(feature = "data-faker")]
372 fn generate_with_faker_safe(&mut self, field: &FieldDescriptor) -> Option<Value> {
373 let faker = self.faker.as_mut()?;
374 let field_name = field.name().to_lowercase();
375
376 match field.kind() {
377 Kind::String => {
378 let fake_data = match field_name.as_str() {
379 name if name.contains("email") => faker.email(),
381
382 name if name.contains("first") && name.contains("name") => FirstName().fake(),
384 name if name.contains("last") && name.contains("name") => LastName().fake(),
385 name if name.contains("name")
386 && !name.contains("file")
387 && !name.contains("path") =>
388 {
389 faker.name()
390 }
391
392 name if name.contains("phone") || name.contains("mobile") => faker.phone(),
394 name if name.contains("address") => faker.address(),
395
396 name if name.contains("company") || name.contains("organization") => {
398 faker.company()
399 }
400
401 name if name.contains("url") || name.contains("website") => faker.url(),
403 name if name.contains("ip") => faker.ip_address(),
404
405 name if name.contains("uuid") || name.contains("guid") => faker.uuid(),
407
408 name if name.contains("date")
410 || name.contains("time")
411 || name.contains("created")
412 || name.contains("updated") =>
413 {
414 faker.date_iso()
415 }
416
417 name if name.contains("color") || name.contains("colour") => faker.color(),
419
420 _ => return None,
422 };
423
424 Some(Value::String(fake_data))
425 }
426
427 Kind::Int32 | Kind::Int64 => {
428 let fake_value = match field_name.as_str() {
429 name if name.contains("age") => faker.int_range(18, 90),
431
432 name if name.contains("year") => faker.int_range(1990, 2024),
434
435 name if name.contains("count")
437 || name.contains("quantity")
438 || name.contains("amount") =>
439 {
440 faker.int_range(1, 1000)
441 }
442
443 name if name.contains("port") => faker.int_range(1024, 65535),
445
446 name if name.contains("id") || name.contains("identifier") => {
448 self.next_sequence() as i64
449 }
450 _ => faker.int_range(1, 100),
451 };
452
453 Some(match field.kind() {
454 Kind::Int32 => Value::I32(fake_value as i32),
455 Kind::Int64 => Value::I64(fake_value),
456 _ => unreachable!(),
457 })
458 }
459
460 Kind::Double | Kind::Float => {
461 let fake_value = match field_name.as_str() {
462 name if name.contains("price")
464 || name.contains("cost")
465 || name.contains("amount") =>
466 {
467 faker.float_range(1.0, 1000.0)
468 }
469
470 name if name.contains("rate") || name.contains("percent") => {
472 faker.float_range(0.0, 100.0)
473 }
474
475 name if name.contains("latitude") || name.contains("lat") => {
477 faker.float_range(-90.0, 90.0)
478 }
479 name if name.contains("longitude")
480 || name.contains("lng")
481 || name.contains("lon") =>
482 {
483 faker.float_range(-180.0, 180.0)
484 }
485
486 _ => faker.float_range(0.0, 100.0),
488 };
489
490 Some(match field.kind() {
491 Kind::Double => Value::F64(fake_value),
492 Kind::Float => Value::F32(fake_value as f32),
493 _ => unreachable!(),
494 })
495 }
496
497 Kind::Bool => {
498 let probability = match field_name.as_str() {
499 name if name.contains("active")
501 || name.contains("enabled")
502 || name.contains("verified") =>
503 {
504 0.8
505 }
506
507 name if name.contains("deleted")
509 || name.contains("archived")
510 || name.contains("disabled") =>
511 {
512 0.2
513 }
514
515 _ => 0.5,
517 };
518
519 Some(Value::Bool(faker.boolean(probability)))
520 }
521
522 _ => None,
523 }
524 }
525
526 #[cfg(not(feature = "data-faker"))]
527 fn generate_with_faker_safe(&mut self, _field: &FieldDescriptor) -> Option<Value> {
528 None
529 }
530
531 fn generate_basic_value_for_type(&mut self, field: &FieldDescriptor, depth: usize) -> Value {
533 match field.kind() {
534 Kind::String => Value::String(format!("mock_{}", field.name())),
535 Kind::Int32 => Value::I32(self.next_sequence() as i32),
536 Kind::Int64 => Value::I64(self.next_sequence() as i64),
537 Kind::Uint32 => Value::U32(self.next_sequence() as u32),
538 Kind::Uint64 => Value::U64(self.next_sequence()),
539 Kind::Sint32 => Value::I32(self.next_sequence() as i32),
540 Kind::Sint64 => Value::I64(self.next_sequence() as i64),
541 Kind::Fixed32 => Value::U32(self.next_sequence() as u32),
542 Kind::Fixed64 => Value::U64(self.next_sequence()),
543 Kind::Sfixed32 => Value::I32(self.next_sequence() as i32),
544 Kind::Sfixed64 => Value::I64(self.next_sequence() as i64),
545 Kind::Bool => Value::Bool(self.next_sequence().is_multiple_of(2)),
546 Kind::Double => Value::F64(self.next_random::<f64>() * 100.0),
547 Kind::Float => Value::F32(self.next_random::<f32>() * 100.0),
548 Kind::Bytes => {
549 Value::Bytes(format!("bytes_{}", self.next_sequence()).into_bytes().into())
550 }
551 Kind::Enum(enum_descriptor) => {
552 if let Some(first_value) = enum_descriptor.values().next() {
554 Value::EnumNumber(first_value.number())
555 } else {
556 Value::EnumNumber(0)
557 }
558 }
559 Kind::Message(message_descriptor) => {
560 self.generate_mock_message(&message_descriptor, depth + 1)
561 }
562 }
563 }
564
565 fn generate_mock_message(&mut self, descriptor: &MessageDescriptor, depth: usize) -> Value {
567 let mut message = prost_reflect::DynamicMessage::new(descriptor.clone());
568
569 if depth >= self.config.max_depth {
570 return Value::Message(message);
571 }
572
573 for field in descriptor.fields() {
574 let value = self.generate_basic_value_for_type(&field, depth);
575 message.set_field(&field, value);
576 }
577
578 Value::Message(message)
579 }
580
581 fn generate_depth_limit_value(&self, field: &FieldDescriptor) -> Value {
583 match field.kind() {
584 Kind::String => Value::String(format!("depth_limit_reached_{}", field.name())),
585 Kind::Int32 => Value::I32(999),
586 Kind::Int64 => Value::I64(999),
587 _ => Value::String("depth_limit".to_string()),
588 }
589 }
590
591 fn parse_override_value(&self, override_value: &str, field: &FieldDescriptor) -> Value {
593 match field.kind() {
594 Kind::String => Value::String(override_value.to_string()),
595 Kind::Int32 => Value::I32(override_value.parse().unwrap_or(0)),
596 Kind::Int64 => Value::I64(override_value.parse().unwrap_or(0)),
597 Kind::Bool => Value::Bool(override_value.parse().unwrap_or(false)),
598 Kind::Double => Value::F64(override_value.parse().unwrap_or(0.0)),
599 Kind::Float => Value::F32(override_value.parse().unwrap_or(0.0)),
600 _ => Value::String(override_value.to_string()),
601 }
602 }
603
604 pub fn next_sequence(&mut self) -> u64 {
606 let current = self.sequence_counter;
607 self.sequence_counter += 1;
608 current
609 }
610
611 pub fn generate_message(&mut self, descriptor: &MessageDescriptor) -> DynamicMessage {
618 match self.generate_mock_message(descriptor, 0) {
619 Value::Message(msg) => msg,
620 _ => unreachable!(
623 "generate_mock_message should always return a Message Value - this indicates a bug"
624 ),
625 }
626 }
627
628 pub fn generate_random_string(&mut self, length: usize) -> String {
630 use rand::distr::Alphanumeric;
631 use rand::{rng, Rng};
632
633 if let Some(ref mut rng) = self.rng {
634 rng.sample_iter(&Alphanumeric).take(length).map(char::from).collect()
635 } else {
636 rng().sample_iter(&Alphanumeric).take(length).map(char::from).collect()
637 }
638 }
639
640 pub fn generate_uuid(&mut self) -> String {
642 format!(
643 "{:08x}-{:04x}-{:04x}-{:04x}-{:12x}",
644 self.next_random::<u32>(),
645 self.next_random::<u16>(),
646 self.next_random::<u16>(),
647 self.next_random::<u16>(),
648 self.next_random::<u64>() & 0xffffffffffff,
649 )
650 }
651
652 pub fn config(&self) -> &SmartMockConfig {
654 &self.config
655 }
656
657 #[cfg(feature = "data-faker")]
659 pub fn is_faker_enabled(&self) -> bool {
660 self.config.use_faker && self.faker.is_some()
661 }
662
663 #[cfg(not(feature = "data-faker"))]
664 pub fn is_faker_enabled(&self) -> bool {
665 false
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672
673 #[test]
674 fn test_smart_mock_generator() {
675 let config = SmartMockConfig::default();
676 let mut generator = SmartMockGenerator::new(config);
677
678 assert_eq!(generator.next_sequence(), 1);
680 assert_eq!(generator.next_sequence(), 2);
681
682 let uuid = generator.generate_uuid();
684 assert!(uuid.contains('-'));
685 assert_eq!(uuid.matches('-').count(), 4);
686 }
687
688 #[test]
689 fn test_field_name_inference() {
690 let config = SmartMockConfig::default();
691 let mut generator = SmartMockGenerator::new(config);
692
693 assert!(generator.generate_random_string(10).len() == 10);
696 }
697
698 #[test]
699 fn test_deterministic_seeding() {
700 let config1 = SmartMockConfig {
702 seed: Some(12345),
703 deterministic: true,
704 ..Default::default()
705 };
706 let config2 = SmartMockConfig {
707 seed: Some(12345),
708 deterministic: true,
709 ..Default::default()
710 };
711
712 let mut gen1 = SmartMockGenerator::new(config1);
713 let mut gen2 = SmartMockGenerator::new(config2);
714
715 assert_eq!(gen1.generate_uuid(), gen2.generate_uuid());
717 assert_eq!(gen1.generate_random_string(10), gen2.generate_random_string(10));
718
719 let config3 = SmartMockConfig {
721 seed: Some(54321),
722 deterministic: true,
723 ..Default::default()
724 };
725 let mut gen3 = SmartMockGenerator::new(config3);
726
727 gen1.reset();
729 gen3.reset();
730
731 assert_ne!(gen1.generate_uuid(), gen3.generate_uuid());
733 }
734
735 #[test]
736 fn test_new_with_seed() {
737 let config = SmartMockConfig::default();
738 let mut gen1 = SmartMockGenerator::new_with_seed(config.clone(), 999);
739 let mut gen2 = SmartMockGenerator::new_with_seed(config, 999);
740
741 assert!(gen1.config.deterministic);
743 assert!(gen2.config.deterministic);
744 assert_eq!(gen1.config.seed, Some(999));
745 assert_eq!(gen2.config.seed, Some(999));
746
747 let uuid1 = gen1.generate_uuid();
748 let uuid2 = gen2.generate_uuid();
749 assert_eq!(uuid1, uuid2);
750 }
751
752 #[test]
753 fn test_generator_reset() {
754 let config = SmartMockConfig {
755 seed: Some(777),
756 deterministic: true,
757 ..Default::default()
758 };
759
760 let mut generator = SmartMockGenerator::new(config);
761
762 let uuid1 = generator.generate_uuid();
764 let seq1 = generator.next_sequence();
765 let seq2 = generator.next_sequence();
766
767 assert_eq!(seq1, 1);
768 assert_eq!(seq2, 2);
769
770 generator.reset();
772 let uuid2 = generator.generate_uuid();
773 let seq3 = generator.next_sequence();
774 let seq4 = generator.next_sequence();
775
776 assert_eq!(uuid1, uuid2); assert_eq!(seq3, 1); assert_eq!(seq4, 2);
779 }
780
781 #[test]
782 fn test_deterministic_vs_non_deterministic() {
783 let mut gen_random = SmartMockGenerator::new(SmartMockConfig::default());
785
786 let mut gen_deterministic =
788 SmartMockGenerator::new_with_seed(SmartMockConfig::default(), 42);
789
790 let det_uuid1 = gen_deterministic.generate_uuid();
792 let det_uuid2 = gen_deterministic.generate_uuid();
793
794 gen_deterministic.reset();
795 let det_uuid1_repeat = gen_deterministic.generate_uuid();
796 let det_uuid2_repeat = gen_deterministic.generate_uuid();
797
798 assert_eq!(det_uuid1, det_uuid1_repeat);
800 assert_eq!(det_uuid2, det_uuid2_repeat);
801
802 let _random_uuid = gen_random.generate_uuid();
804 }
805
806 #[cfg(feature = "data-faker")]
807 #[test]
808 fn test_faker_integration() {
809 let config = SmartMockConfig {
810 use_faker: true,
811 ..Default::default()
812 };
813 let mut generator = SmartMockGenerator::new(config);
814
815 assert!(generator.faker.is_some());
817
818 assert_eq!(generator.next_sequence(), 1);
820 assert_eq!(generator.next_sequence(), 2);
821 }
822}