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