mockforge_data/
domains.rs

1//! Domain-specific data generators
2//!
3//! This module provides specialized data generators for various domains
4//! including finance, IoT, healthcare, and more.
5
6use mockforge_core::Result;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9use serde_json::{json, Value};
10use std::str::FromStr;
11
12/// Domain type for specialized data generation
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum Domain {
16    /// Financial data (transactions, accounts, currencies)
17    Finance,
18    /// Internet of Things data (sensors, devices, telemetry)
19    Iot,
20    /// Healthcare data (patients, diagnoses, medications)
21    Healthcare,
22    /// E-commerce data (products, orders, customers)
23    Ecommerce,
24    /// Social media data (users, posts, comments)
25    Social,
26    /// General purpose domain
27    General,
28}
29
30impl Domain {
31    /// Parse domain from string (deprecated - use FromStr trait)
32    #[deprecated(
33        since = "0.1.4",
34        note = "Use str::parse() or FromStr::from_str() instead"
35    )]
36    pub fn parse(s: &str) -> Option<Self> {
37        s.parse().ok()
38    }
39
40    /// Get domain name as string
41    pub fn as_str(&self) -> &'static str {
42        match self {
43            Self::Finance => "finance",
44            Self::Iot => "iot",
45            Self::Healthcare => "healthcare",
46            Self::Ecommerce => "ecommerce",
47            Self::Social => "social",
48            Self::General => "general",
49        }
50    }
51}
52
53/// Error type for domain parsing
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ParseDomainError {
56    invalid_domain: String,
57}
58
59impl std::fmt::Display for ParseDomainError {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(
62            f,
63            "Invalid domain: '{}'. Valid domains are: finance, iot, healthcare, ecommerce, social, general",
64            self.invalid_domain
65        )
66    }
67}
68
69impl std::error::Error for ParseDomainError {}
70
71impl FromStr for Domain {
72    type Err = ParseDomainError;
73
74    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
75        match s.to_lowercase().as_str() {
76            "finance" => Ok(Self::Finance),
77            "iot" => Ok(Self::Iot),
78            "healthcare" => Ok(Self::Healthcare),
79            "ecommerce" | "e-commerce" => Ok(Self::Ecommerce),
80            "social" => Ok(Self::Social),
81            "general" => Ok(Self::General),
82            _ => Err(ParseDomainError {
83                invalid_domain: s.to_string(),
84            }),
85        }
86    }
87}
88
89/// Domain-specific data generator
90#[derive(Debug)]
91pub struct DomainGenerator {
92    domain: Domain,
93    /// Optional persona traits to influence generation
94    persona_traits: Option<std::collections::HashMap<String, String>>,
95}
96
97impl DomainGenerator {
98    /// Create a new domain generator
99    pub fn new(domain: Domain) -> Self {
100        Self {
101            domain,
102            persona_traits: None,
103        }
104    }
105
106    /// Create a new domain generator with persona traits
107    pub fn with_traits(domain: Domain, traits: std::collections::HashMap<String, String>) -> Self {
108        Self {
109            domain,
110            persona_traits: Some(traits),
111        }
112    }
113
114    /// Set persona traits for this generator
115    pub fn set_traits(&mut self, traits: std::collections::HashMap<String, String>) {
116        self.persona_traits = Some(traits);
117    }
118
119    /// Get persona traits
120    pub fn get_trait(&self, name: &str) -> Option<&String> {
121        self.persona_traits.as_ref().and_then(|traits| traits.get(name))
122    }
123
124    /// Generate data for a specific field type in the domain
125    pub fn generate(&self, field_type: &str) -> Result<Value> {
126        match self.domain {
127            Domain::Finance => self.generate_finance(field_type),
128            Domain::Iot => self.generate_iot(field_type),
129            Domain::Healthcare => self.generate_healthcare(field_type),
130            Domain::Ecommerce => self.generate_ecommerce(field_type),
131            Domain::Social => self.generate_social(field_type),
132            Domain::General => self.generate_general(field_type),
133        }
134    }
135
136    /// Generate financial data
137    fn generate_finance(&self, field_type: &str) -> Result<Value> {
138        let mut rng = rand::rng();
139
140        let value = match field_type {
141            "account_number" => json!(format!("ACC{:010}", rng.random_range(0..9999999999i64))),
142            "routing_number" => json!(format!("{:09}", rng.random_range(100000000..999999999))),
143            "iban" => json!(format!(
144                "GB{:02}{:04}{:08}{:08}",
145                rng.random_range(10..99),
146                rng.random_range(1000..9999),
147                rng.random_range(10000000..99999999),
148                rng.random_range(10000000..99999999)
149            )),
150            "swift" | "bic" => json!(format!(
151                "{}BANK{}",
152                ["GB", "US", "DE", "FR"][rng.random_range(0..4)],
153                rng.random_range(100..999)
154            )),
155            "amount" | "balance" => {
156                let amount = rng.random_range(10.0..10000.0);
157                json!(format!("{:.2}", amount))
158            }
159            "currency" => json!(["USD", "EUR", "GBP", "JPY", "CNY"][rng.random_range(0..5)]),
160            "transaction_id" => json!(format!("TXN{:016x}", rng.random::<u64>())),
161            "card_number" => json!(format!(
162                "4{}",
163                (0..15).map(|_| rng.random_range(0..10).to_string()).collect::<String>()
164            )),
165            "cvv" => json!(format!("{:03}", rng.random_range(100..999))),
166            "expiry" => {
167                let month = rng.random_range(1..=12);
168                let year = rng.random_range(25..35);
169                json!(format!("{:02}/{:02}", month, year))
170            }
171            "stock_symbol" => {
172                json!(["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA", "META"][rng.random_range(0..6)])
173            }
174            "price" => json!(rng.random_range(10.0..5000.0)),
175            _ => json!(format!("finance_{}", field_type)),
176        };
177
178        Ok(value)
179    }
180
181    /// Generate IoT data
182    fn generate_iot(&self, field_type: &str) -> Result<Value> {
183        let mut rng = rand::rng();
184
185        let value = match field_type {
186            "device_id" => json!(format!("device-{:08x}", rng.random::<u32>())),
187            "sensor_id" => json!(format!("sensor-{:06}", rng.random_range(100000..999999))),
188            "temperature" => json!(rng.random_range(-20.0..50.0)),
189            "humidity" => json!(rng.random_range(0.0..100.0)),
190            "pressure" => json!(rng.random_range(900.0..1100.0)),
191            "voltage" => json!(rng.random_range(0.0..5.0)),
192            "current" => json!(rng.random_range(0.0..10.0)),
193            "power" => json!(rng.random_range(0.0..1000.0)),
194            "rssi" | "signal_strength" => json!(rng.random_range(-90..-30)),
195            "battery_level" => json!(rng.random_range(0..=100)),
196            "latitude" => json!(rng.random_range(-90.0..90.0)),
197            "longitude" => json!(rng.random_range(-180.0..180.0)),
198            "altitude" => json!(rng.random_range(0.0..5000.0)),
199            "status" => {
200                json!(["online", "offline", "error", "maintenance"][rng.random_range(0..4)])
201            }
202            "firmware_version" => json!(format!(
203                "{}.{}.{}",
204                rng.random_range(1..5),
205                rng.random_range(0..10),
206                rng.random_range(0..100)
207            )),
208            "mac_address" => json!(format!(
209                "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
210                rng.random::<u8>(),
211                rng.random::<u8>(),
212                rng.random::<u8>(),
213                rng.random::<u8>(),
214                rng.random::<u8>(),
215                rng.random::<u8>()
216            )),
217            "ip_address" => json!(format!(
218                "{}.{}.{}.{}",
219                rng.random_range(1..255),
220                rng.random_range(0..255),
221                rng.random_range(0..255),
222                rng.random_range(1..255)
223            )),
224            _ => json!(format!("iot_{}", field_type)),
225        };
226
227        Ok(value)
228    }
229
230    /// Generate healthcare data
231    fn generate_healthcare(&self, field_type: &str) -> Result<Value> {
232        let mut rng = rand::rng();
233
234        let value = match field_type {
235            "patient_id" => json!(format!("P{:08}", rng.random_range(10000000..99999999))),
236            "mrn" | "medical_record_number" => {
237                json!(format!("MRN{:010}", rng.random_range(0..9999999999i64)))
238            }
239            "diagnosis_code" | "icd10" => json!(format!(
240                "{}{:02}.{}",
241                ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'][rng.random_range(0..10)],
242                rng.random_range(0..99),
243                rng.random_range(0..9)
244            )),
245            "procedure_code" | "cpt" => json!(format!("{:05}", rng.random_range(10000..99999))),
246            "npi" | "provider_id" => {
247                json!(format!("{:010}", rng.random_range(1000000000..9999999999i64)))
248            }
249            "blood_pressure" => {
250                json!(format!("{}/{}", rng.random_range(90..180), rng.random_range(60..120)))
251            }
252            "heart_rate" | "pulse" => json!(rng.random_range(60..100)),
253            "respiratory_rate" => json!(rng.random_range(12..20)),
254            "temperature" => json!(format!("{:.1}", rng.random_range(36.0..38.5))),
255            "blood_glucose" => json!(rng.random_range(70..140)),
256            "oxygen_saturation" => json!(rng.random_range(95..100)),
257            "bmi" => json!(format!("{:.1}", rng.random_range(18.0..35.0))),
258            "blood_type" => {
259                json!(["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"][rng.random_range(0..8)])
260            }
261            "medication" => json!(
262                [
263                    "Aspirin",
264                    "Ibuprofen",
265                    "Metformin",
266                    "Lisinopril",
267                    "Atorvastatin"
268                ][rng.random_range(0..5)]
269            ),
270            "dosage" => json!(format!("{}mg", [50, 100, 250, 500, 1000][rng.random_range(0..5)])),
271            "allergy" => {
272                json!(["Penicillin", "Peanuts", "Latex", "Sulfa", "None"][rng.random_range(0..5)])
273            }
274            _ => json!(format!("healthcare_{}", field_type)),
275        };
276
277        Ok(value)
278    }
279
280    /// Generate e-commerce data
281    fn generate_ecommerce(&self, field_type: &str) -> Result<Value> {
282        let mut rng = rand::rng();
283
284        let value = match field_type {
285            "order_id" => json!(format!("ORD-{:010}", rng.random_range(0..9999999999i64))),
286            "product_id" | "sku" => {
287                json!(format!("SKU{:08}", rng.random_range(10000000..99999999)))
288            }
289            "product_name" => json!(
290                ["Laptop", "Phone", "Headphones", "Mouse", "Keyboard"][rng.random_range(0..5)]
291            ),
292            "category" => json!(
293                ["Electronics", "Clothing", "Books", "Home", "Sports"][rng.random_range(0..5)]
294            ),
295            "price" => json!(rng.random_range(9.99..999.99)),
296            "quantity" => json!(rng.random_range(1..10)),
297            "discount" => json!(rng.random_range(0.0..50.0)),
298            "rating" => json!(rng.random_range(1.0..5.0)),
299            "review_count" => json!(rng.random_range(0..1000)),
300            "in_stock" => json!(rng.random_bool(0.8)),
301            "shipping_method" => {
302                json!(["Standard", "Express", "Overnight"][rng.random_range(0..3)])
303            }
304            "tracking_number" => json!(format!("1Z{:016}", rng.random::<u64>())),
305            "order_status" => {
306                json!(["pending", "processing", "shipped", "delivered"][rng.random_range(0..4)])
307            }
308            _ => json!(format!("ecommerce_{}", field_type)),
309        };
310
311        Ok(value)
312    }
313
314    /// Generate social media data
315    fn generate_social(&self, field_type: &str) -> Result<Value> {
316        let mut rng = rand::rng();
317
318        let value = match field_type {
319            "user_id" => json!(format!("user{:08}", rng.random_range(10000000..99999999))),
320            "post_id" => json!(format!("post_{:016x}", rng.random::<u64>())),
321            "comment_id" => json!(format!("cmt_{:012x}", rng.random::<u64>())),
322            "username" => json!(format!("user{}", rng.random_range(1000..9999))),
323            "display_name" => json!(
324                ["Alice Smith", "Bob Johnson", "Carol White", "David Brown"]
325                    [rng.random_range(0..4)]
326            ),
327            "bio" => json!("Passionate about technology and innovation"),
328            "follower_count" => json!(rng.random_range(0..100000)),
329            "following_count" => json!(rng.random_range(0..5000)),
330            "post_count" => json!(rng.random_range(0..10000)),
331            "likes" => json!(rng.random_range(0..10000)),
332            "shares" => json!(rng.random_range(0..1000)),
333            "comments" => json!(rng.random_range(0..500)),
334            "hashtag" => json!(format!(
335                "#{}",
336                ["tech", "life", "coding", "ai", "ml"][rng.random_range(0..5)]
337            )),
338            "verified" => json!(rng.random_bool(0.1)),
339            _ => json!(format!("social_{}", field_type)),
340        };
341
342        Ok(value)
343    }
344
345    /// Generate general data
346    fn generate_general(&self, field_type: &str) -> Result<Value> {
347        use crate::faker::EnhancedFaker;
348        let mut faker = EnhancedFaker::new();
349        Ok(faker.generate_by_type(field_type))
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    fn test_domain_from_str() {
359        assert_eq!("finance".parse::<Domain>().unwrap(), Domain::Finance);
360        assert_eq!("iot".parse::<Domain>().unwrap(), Domain::Iot);
361        assert_eq!("healthcare".parse::<Domain>().unwrap(), Domain::Healthcare);
362        assert_eq!("ecommerce".parse::<Domain>().unwrap(), Domain::Ecommerce);
363        assert_eq!("e-commerce".parse::<Domain>().unwrap(), Domain::Ecommerce);
364        assert_eq!("social".parse::<Domain>().unwrap(), Domain::Social);
365        assert_eq!("general".parse::<Domain>().unwrap(), Domain::General);
366
367        // Case insensitive
368        assert_eq!("FINANCE".parse::<Domain>().unwrap(), Domain::Finance);
369        assert_eq!("Finance".parse::<Domain>().unwrap(), Domain::Finance);
370
371        // Test invalid domain returns error
372        assert!("invalid".parse::<Domain>().is_err());
373    }
374
375    #[test]
376    fn test_domain_from_str_error() {
377        let result = "invalid".parse::<Domain>();
378        assert!(result.is_err());
379
380        let err = result.unwrap_err();
381        assert_eq!(err.invalid_domain, "invalid");
382        assert!(err.to_string().contains("Invalid domain"));
383        assert!(err.to_string().contains("finance, iot, healthcare"));
384    }
385
386    #[test]
387    fn test_domain_as_str() {
388        assert_eq!(Domain::Finance.as_str(), "finance");
389        assert_eq!(Domain::Iot.as_str(), "iot");
390        assert_eq!(Domain::Healthcare.as_str(), "healthcare");
391    }
392
393    #[test]
394    fn test_generate_finance() {
395        let generator = DomainGenerator::new(Domain::Finance);
396        let result = generator.generate("amount");
397        assert!(result.is_ok());
398        assert!(result.unwrap().is_string());
399    }
400
401    #[test]
402    fn test_generate_iot() {
403        let generator = DomainGenerator::new(Domain::Iot);
404        let result = generator.generate("temperature");
405        assert!(result.is_ok());
406        assert!(result.unwrap().is_number());
407    }
408
409    #[test]
410    fn test_generate_healthcare() {
411        let generator = DomainGenerator::new(Domain::Healthcare);
412        let result = generator.generate("patient_id");
413        assert!(result.is_ok());
414        assert!(result.unwrap().is_string());
415    }
416
417    #[test]
418    fn test_generate_ecommerce() {
419        let generator = DomainGenerator::new(Domain::Ecommerce);
420        let result = generator.generate("order_id");
421        assert!(result.is_ok());
422        assert!(result.unwrap().is_string());
423    }
424
425    #[test]
426    fn test_generate_social() {
427        let generator = DomainGenerator::new(Domain::Social);
428        let result = generator.generate("user_id");
429        assert!(result.is_ok());
430        assert!(result.unwrap().is_string());
431    }
432
433    #[test]
434    fn test_all_finance_fields() {
435        let generator = DomainGenerator::new(Domain::Finance);
436        let fields = vec!["account_number", "amount", "currency", "transaction_id"];
437
438        for field in fields {
439            let result = generator.generate(field);
440            assert!(result.is_ok(), "Failed to generate finance field: {}", field);
441        }
442    }
443
444    #[test]
445    fn test_all_iot_fields() {
446        let generator = DomainGenerator::new(Domain::Iot);
447        let fields = vec!["device_id", "temperature", "humidity", "battery_level"];
448
449        for field in fields {
450            let result = generator.generate(field);
451            assert!(result.is_ok(), "Failed to generate IoT field: {}", field);
452        }
453    }
454
455    #[test]
456    fn test_all_healthcare_fields() {
457        let generator = DomainGenerator::new(Domain::Healthcare);
458        let fields = vec!["patient_id", "blood_pressure", "heart_rate", "blood_type"];
459
460        for field in fields {
461            let result = generator.generate(field);
462            assert!(result.is_ok(), "Failed to generate healthcare field: {}", field);
463        }
464    }
465}