Skip to main content

sql_splitter/redactor/strategy/
fake.rs

1//! Fake strategy - generate realistic fake data.
2
3use super::{RedactValue, Strategy, StrategyKind};
4use fake::faker::address::en::{CityName, StateName, StreetName, ZipCode};
5use fake::faker::company::en::CompanyName;
6use fake::faker::internet::en::{SafeEmail, Username};
7use fake::faker::lorem::en::{Paragraph, Sentence, Word};
8use fake::faker::name::en::{FirstName, LastName, Name};
9use fake::faker::phone_number::en::PhoneNumber;
10use fake::Fake;
11
12/// Strategy that generates fake data
13#[derive(Debug, Clone)]
14pub struct FakeStrategy {
15    generator: String,
16    locale: String,
17}
18
19impl FakeStrategy {
20    pub fn new(generator: String, locale: String) -> Self {
21        Self { generator, locale }
22    }
23
24    /// Generate a fake value based on the generator type
25    fn generate(&self, rng: &mut dyn rand::RngCore) -> String {
26        // Convert rng to StdRng for fake crate compatibility
27        let mut seed = [0u8; 32];
28        rng.fill_bytes(&mut seed);
29        let mut fake_rng = rand::rngs::StdRng::from_seed(seed);
30
31        match self.generator.to_lowercase().as_str() {
32            // Name generators
33            "name" | "full_name" => Name().fake_with_rng(&mut fake_rng),
34            "first_name" => FirstName().fake_with_rng(&mut fake_rng),
35            "last_name" => LastName().fake_with_rng(&mut fake_rng),
36
37            // Contact generators
38            "email" | "safe_email" => SafeEmail().fake_with_rng(&mut fake_rng),
39            "phone" | "phone_number" => PhoneNumber().fake_with_rng(&mut fake_rng),
40            "username" | "user_name" => Username().fake_with_rng(&mut fake_rng),
41
42            // Address generators
43            "address" | "street_address" => {
44                let street: String = StreetName().fake_with_rng(&mut fake_rng);
45                let city: String = CityName().fake_with_rng(&mut fake_rng);
46                let state: String = StateName().fake_with_rng(&mut fake_rng);
47                let zip: String = ZipCode().fake_with_rng(&mut fake_rng);
48                format!("{}, {}, {} {}", street, city, state, zip)
49            }
50            "street" | "street_name" => StreetName().fake_with_rng(&mut fake_rng),
51            "city" => CityName().fake_with_rng(&mut fake_rng),
52            "state" => StateName().fake_with_rng(&mut fake_rng),
53            "zip" | "zip_code" | "postal_code" => ZipCode().fake_with_rng(&mut fake_rng),
54            "country" => "United States".to_string(), // Simplified for now
55
56            // Business generators
57            "company" | "company_name" => CompanyName().fake_with_rng(&mut fake_rng),
58            "job_title" => {
59                // Simplified job title generator
60                let titles = [
61                    "Software Engineer",
62                    "Product Manager",
63                    "Data Analyst",
64                    "Designer",
65                    "Marketing Manager",
66                    "Sales Representative",
67                    "Customer Support",
68                    "Operations Manager",
69                ];
70                let idx = fake_rng.random_range(0..titles.len());
71                titles[idx].to_string()
72            }
73
74            // Internet generators
75            "url" => format!(
76                "https://example{}.com/{}",
77                fake_rng.random_range(1..1000),
78                Word().fake_with_rng::<String, _>(&mut fake_rng)
79            ),
80            "ip" | "ip_address" | "ipv4" => {
81                format!(
82                    "{}.{}.{}.{}",
83                    fake_rng.random_range(1..255),
84                    fake_rng.random_range(0..255),
85                    fake_rng.random_range(0..255),
86                    fake_rng.random_range(1..255)
87                )
88            }
89            "ipv6" => {
90                format!(
91                    "{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}",
92                    fake_rng.random_range(0..0xFFFF_u16),
93                    fake_rng.random_range(0..0xFFFF_u16),
94                    fake_rng.random_range(0..0xFFFF_u16),
95                    fake_rng.random_range(0..0xFFFF_u16),
96                    fake_rng.random_range(0..0xFFFF_u16),
97                    fake_rng.random_range(0..0xFFFF_u16),
98                    fake_rng.random_range(0..0xFFFF_u16),
99                    fake_rng.random_range(0..0xFFFF_u16)
100                )
101            }
102
103            // Identifier generators
104            "uuid" => {
105                format!(
106                    "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
107                    fake_rng.random::<u32>(),
108                    fake_rng.random::<u16>(),
109                    (fake_rng.random::<u16>() & 0x0FFF) | 0x4000, // Version 4
110                    (fake_rng.random::<u16>() & 0x3FFF) | 0x8000, // Variant
111                    fake_rng.random::<u64>() & 0xFFFFFFFFFFFF_u64
112                )
113            }
114
115            // Date/time generators
116            "date" => {
117                let year = fake_rng.random_range(1970..2024);
118                let month = fake_rng.random_range(1..=12);
119                let day = fake_rng.random_range(1..=28);
120                format!("{:04}-{:02}-{:02}", year, month, day)
121            }
122            "datetime" | "date_time" => {
123                let year = fake_rng.random_range(1970..2024);
124                let month = fake_rng.random_range(1..=12);
125                let day = fake_rng.random_range(1..=28);
126                let hour = fake_rng.random_range(0..24);
127                let minute = fake_rng.random_range(0..60);
128                let second = fake_rng.random_range(0..60);
129                format!(
130                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
131                    year, month, day, hour, minute, second
132                )
133            }
134            "time" => {
135                let hour = fake_rng.random_range(0..24);
136                let minute = fake_rng.random_range(0..60);
137                let second = fake_rng.random_range(0..60);
138                format!("{:02}:{:02}:{:02}", hour, minute, second)
139            }
140
141            // Financial generators
142            "credit_card" => {
143                // Generate a fake credit card number (Luhn-valid would be complex, simplified)
144                format!(
145                    "{:04}-{:04}-{:04}-{:04}",
146                    fake_rng.random_range(1000..9999),
147                    fake_rng.random_range(1000..9999),
148                    fake_rng.random_range(1000..9999),
149                    fake_rng.random_range(1000..9999)
150                )
151            }
152            "iban" => {
153                // Simplified IBAN (not valid, but looks realistic)
154                format!(
155                    "DE{:02}{:04}{:04}{:04}{:04}{:02}",
156                    fake_rng.random_range(10..99),
157                    fake_rng.random_range(1000..9999),
158                    fake_rng.random_range(1000..9999),
159                    fake_rng.random_range(1000..9999),
160                    fake_rng.random_range(1000..9999),
161                    fake_rng.random_range(10..99)
162                )
163            }
164
165            // SSN generator
166            "ssn" => {
167                format!(
168                    "{:03}-{:02}-{:04}",
169                    fake_rng.random_range(100..999),
170                    fake_rng.random_range(10..99),
171                    fake_rng.random_range(1000..9999)
172                )
173            }
174
175            // Text generators
176            "lorem" | "paragraph" => Paragraph(3..5).fake_with_rng(&mut fake_rng),
177            "sentence" => Sentence(5..10).fake_with_rng(&mut fake_rng),
178            "word" => Word().fake_with_rng(&mut fake_rng),
179
180            // Default: return a generic fake string
181            _ => format!("FAKE_{}", fake_rng.random_range(10000..99999)),
182        }
183    }
184}
185
186impl Strategy for FakeStrategy {
187    fn apply(&self, value: &RedactValue, rng: &mut dyn rand::RngCore) -> RedactValue {
188        match value {
189            RedactValue::Null => RedactValue::Null,
190            _ => RedactValue::String(self.generate(rng)),
191        }
192    }
193
194    fn kind(&self) -> StrategyKind {
195        StrategyKind::Fake {
196            generator: self.generator.clone(),
197        }
198    }
199}
200
201use rand::{Rng, SeedableRng};
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_fake_name() {
209        let strategy = FakeStrategy::new("name".to_string(), "en".to_string());
210        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
211
212        let result = strategy.apply(&RedactValue::String("John Doe".to_string()), &mut rng);
213        match result {
214            RedactValue::String(s) => {
215                assert!(!s.is_empty());
216                assert!(s.contains(' ')); // Full name has space
217            }
218            _ => panic!("Expected String"),
219        }
220    }
221
222    #[test]
223    fn test_fake_email() {
224        let strategy = FakeStrategy::new("email".to_string(), "en".to_string());
225        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
226
227        let result = strategy.apply(
228            &RedactValue::String("real@example.com".to_string()),
229            &mut rng,
230        );
231        match result {
232            RedactValue::String(s) => {
233                assert!(s.contains('@'));
234            }
235            _ => panic!("Expected String"),
236        }
237    }
238
239    #[test]
240    fn test_fake_phone() {
241        let strategy = FakeStrategy::new("phone".to_string(), "en".to_string());
242        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
243
244        let result = strategy.apply(&RedactValue::String("555-123-4567".to_string()), &mut rng);
245        match result {
246            RedactValue::String(s) => {
247                assert!(!s.is_empty());
248            }
249            _ => panic!("Expected String"),
250        }
251    }
252
253    #[test]
254    fn test_fake_uuid() {
255        let strategy = FakeStrategy::new("uuid".to_string(), "en".to_string());
256        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
257
258        let result = strategy.apply(
259            &RedactValue::String("550e8400-e29b-41d4-a716-446655440000".to_string()),
260            &mut rng,
261        );
262        match result {
263            RedactValue::String(s) => {
264                // UUID format: 8-4-4-4-12
265                assert!(s.contains('-'));
266                assert_eq!(s.len(), 36);
267            }
268            _ => panic!("Expected String"),
269        }
270    }
271
272    #[test]
273    fn test_fake_null() {
274        let strategy = FakeStrategy::new("name".to_string(), "en".to_string());
275        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
276
277        let result = strategy.apply(&RedactValue::Null, &mut rng);
278        assert!(matches!(result, RedactValue::Null));
279    }
280
281    #[test]
282    fn test_fake_date() {
283        let strategy = FakeStrategy::new("date".to_string(), "en".to_string());
284        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
285
286        let result = strategy.apply(&RedactValue::String("2024-01-15".to_string()), &mut rng);
287        match result {
288            RedactValue::String(s) => {
289                // YYYY-MM-DD format
290                assert_eq!(s.len(), 10);
291                assert!(s.contains('-'));
292            }
293            _ => panic!("Expected String"),
294        }
295    }
296
297    #[test]
298    fn test_fake_credit_card() {
299        let strategy = FakeStrategy::new("credit_card".to_string(), "en".to_string());
300        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
301
302        let result = strategy.apply(
303            &RedactValue::String("4532-0151-1283-0366".to_string()),
304            &mut rng,
305        );
306        match result {
307            RedactValue::String(s) => {
308                // XXXX-XXXX-XXXX-XXXX format
309                assert_eq!(s.len(), 19);
310                assert_eq!(s.matches('-').count(), 3);
311            }
312            _ => panic!("Expected String"),
313        }
314    }
315}