1use mockforge_core::Result;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9use serde_json::{json, Value};
10use std::str::FromStr;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum Domain {
16 Finance,
18 Iot,
20 Healthcare,
22 Ecommerce,
24 Social,
26 General,
28}
29
30impl Domain {
31 #[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 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#[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#[derive(Debug)]
91pub struct DomainGenerator {
92 domain: Domain,
93 persona_traits: Option<std::collections::HashMap<String, String>>,
95}
96
97impl DomainGenerator {
98 pub fn new(domain: Domain) -> Self {
100 Self {
101 domain,
102 persona_traits: None,
103 }
104 }
105
106 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 pub fn set_traits(&mut self, traits: std::collections::HashMap<String, String>) {
116 self.persona_traits = Some(traits);
117 }
118
119 pub fn get_trait(&self, name: &str) -> Option<&String> {
121 self.persona_traits.as_ref().and_then(|traits| traits.get(name))
122 }
123
124 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 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 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 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 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 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 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 assert_eq!("FINANCE".parse::<Domain>().unwrap(), Domain::Finance);
369 assert_eq!("Finance".parse::<Domain>().unwrap(), Domain::Finance);
370
371 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}