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