use std::sync::Arc;
use arrow::array::{Array, ArrayRef, StringBuilder};
use crate::rng::Rng;
pub fn corrupt_email(arr: &dyn arrow::array::Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let strategies: Vec<usize> = (0..n).map(|_| rng2.next_usize(3)).collect();
let mut builder = StringBuilder::with_capacity(n, n * 24);
for (i, &strategy) in strategies.iter().enumerate().take(n) {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
let at_pos = s.find('@');
if at_pos.is_none() {
builder.append_value(s);
continue;
}
let at = at_pos.unwrap();
let local = &s[..at];
let domain = &s[at + 1..];
let result = match strategy {
0 => {
let dot_pos = domain.find('.');
if let Some(dot) = dot_pos {
let name = &domain[..dot];
let tld = &domain[dot..];
if name.len() > 2 {
let mut name_chars: Vec<char> = name.chars().collect();
let last = name_chars.len() - 1;
name_chars[last] = (rng2.next_usize(26) as u8 + 97) as char;
format!(
"{}@{}{}",
local,
name_chars.into_iter().collect::<String>(),
tld
)
} else {
s.to_string()
}
} else {
s.to_string()
}
}
1 => {
let alt_domain =
["yahoo.com", "outlook.com", "proton.me", "mail.com"][rng2.next_usize(4)];
format!("{}@{}", local, alt_domain)
}
_ => {
let prefix: String = local.chars().take(3).collect();
format!("{}***@{}", prefix, domain)
}
};
builder.append_value(&result);
}
*rng = rng2;
Arc::new(builder.finish())
}
pub fn corrupt_phone(arr: &dyn arrow::array::Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let strategies: Vec<usize> = (0..n).map(|_| rng2.next_usize(10)).collect();
let mut builder = StringBuilder::with_capacity(n, n * 20);
for (i, &strategy) in strategies.iter().enumerate().take(n) {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
let digits: Vec<char> = s.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() < 6 {
builder.append_value(s);
continue;
}
let result = match strategy {
0..=3 => {
let mut out = String::with_capacity(digits.len() + digits.len() / 2);
for (j, &d) in digits.iter().enumerate() {
if j > 0 && j % 2 == 0 {
out.push(' ');
}
out.push(d);
}
out
}
4..=6 => {
let mut corrupted: Vec<char> = digits.clone();
let n_corrupt = rng2.next_usize(3) + 1;
for _ in 0..n_corrupt {
if corrupted.is_empty() {
break;
}
let pos = rng2.next_usize(corrupted.len());
corrupted[pos] = (rng2.next_usize(10) as u8 + 48) as char;
}
format!("+1 {}", corrupted.into_iter().collect::<String>())
}
_ => {
let mut out = String::from("+1 (");
for (j, &d) in digits.iter().enumerate() {
if j == 2 {
out.push_str(") ");
}
if j == 5 {
out.push('-');
}
out.push(d);
}
out
}
};
builder.append_value(&result);
}
*rng = rng2;
Arc::new(builder.finish())
}
pub fn corrupt_national_id(arr: &dyn arrow::array::Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let mut builder = StringBuilder::with_capacity(n, n * 16);
for i in 0..n {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
if s.is_empty() {
builder.append_value(s);
continue;
}
let mut chars: Vec<char> = s.chars().collect();
let pos = rng2.next_usize(chars.len());
let c = chars[pos];
if c.is_ascii_digit() {
let d = c as u8 - 48;
let delta = rng2.next_usize(9) as u8 + 1;
chars[pos] = ((d + delta) % 10 + 48) as char;
} else if c.is_ascii_alphabetic() {
chars[pos] =
(rng2.next_usize(26) as u8 + if c.is_ascii_uppercase() { 65 } else { 97 }) as char;
}
builder.append_value(chars.into_iter().collect::<String>());
}
*rng = rng2;
Arc::new(builder.finish())
}
pub fn corrupt_siren(arr: &dyn arrow::array::Array, rng: &mut Rng) -> ArrayRef {
use arrow::array::AsArray;
let src = arr.as_string::<i32>();
let n = src.len();
let mut rng2 = rng.fork();
let mut builder = StringBuilder::with_capacity(n, n * 16);
for i in 0..n {
if src.is_null(i) {
builder.append_null();
continue;
}
let s = src.value(i);
let digits: Vec<char> = s.chars().filter(|c| c.is_ascii_digit()).collect();
let non_digit_ok = s.chars().all(|c| c.is_ascii_digit() || c == ' ');
if digits.len() == 9 && non_digit_ok {
let mut result: Vec<char> = s.chars().collect();
let mut digit_count = 0;
for j in (0..result.len()).rev() {
if result[j].is_ascii_digit() {
digit_count += 1;
if digit_count == 9 {
result[j] = (rng2.next_usize(10) as u8 + 48) as char;
break;
}
}
}
builder.append_value(result.into_iter().collect::<String>());
} else {
builder.append_value(s);
}
}
*rng = rng2;
Arc::new(builder.finish())
}
#[cfg(test)]
mod tests {
use super::*;
use arrow::array::{Array, AsArray, StringArray};
fn test_rng() -> Rng {
Rng::new(42)
}
fn make_arr(vals: &[&str]) -> ArrayRef {
Arc::new(StringArray::from(vals.to_vec()))
}
#[test]
fn test_corrupt_email() {
let arr = make_arr(&["john@gmail.com", "jane@hotmail.com", "test@yahoo.com"]);
let mut rng = test_rng();
let result = corrupt_email(&*arr, &mut rng);
assert_eq!(result.len(), 3);
}
#[test]
fn test_corrupt_phone() {
let arr = make_arr(&["+1-555-123-4567", "+1-555-987-6543"]);
let mut rng = test_rng();
let result = corrupt_phone(&*arr, &mut rng);
assert_eq!(result.len(), 2);
}
#[test]
fn test_corrupt_national_id() {
let arr = make_arr(&["123-45-6789", "ABC-12-DEF"]);
let mut rng = test_rng();
let result = corrupt_national_id(&*arr, &mut rng);
assert_eq!(result.len(), 2);
}
#[test]
fn test_corrupt_national_id_empty_stays_empty_not_null() {
let arr = make_arr(&["123-45-6789", ""]);
let mut rng = test_rng();
let result = corrupt_national_id(&*arr, &mut rng);
let s = result.as_string::<i32>();
assert!(!s.is_null(1), "empty input must not become null");
assert_eq!(s.value(1), "");
}
#[test]
fn test_corrupt_siren() {
let arr = make_arr(&["123456789", "987654321"]);
let mut rng = test_rng();
let result = corrupt_siren(&*arr, &mut rng);
assert_eq!(result.len(), 2);
}
#[test]
fn test_deterministic() {
let arr = make_arr(&["john@gmail.com", "jane@hotmail.com"]);
let a = corrupt_email(&*arr, &mut Rng::new(42));
let b = corrupt_email(&*arr, &mut Rng::new(42));
let sa = a.as_string::<i32>();
let sb = b.as_string::<i32>();
assert_eq!(sa.value(0), sb.value(0));
}
}