mod constant;
mod fake;
mod hash;
mod mask;
mod null;
mod shuffle;
mod skip;
pub use constant::ConstantStrategy;
pub use fake::FakeStrategy;
pub use hash::HashStrategy;
pub use mask::MaskStrategy;
pub use null::NullStrategy;
#[allow(unused_imports)] pub use shuffle::ShuffleStrategy;
#[allow(unused_imports)] pub use skip::SkipStrategy;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(tag = "strategy", rename_all = "snake_case")]
pub enum StrategyKind {
Null,
Constant {
value: String,
},
Hash {
#[serde(default)]
preserve_domain: bool,
},
Mask {
pattern: String,
},
Shuffle,
Fake {
generator: String,
},
#[default]
Skip,
}
impl StrategyKind {
pub fn validate(&self) -> anyhow::Result<()> {
match self {
StrategyKind::Null => Ok(()),
StrategyKind::Constant { value } => {
if value.is_empty() {
anyhow::bail!("Constant strategy requires a non-empty value");
}
Ok(())
}
StrategyKind::Hash { .. } => Ok(()),
StrategyKind::Mask { pattern } => {
if pattern.is_empty() {
anyhow::bail!("Mask strategy requires a non-empty pattern");
}
for c in pattern.chars() {
if !matches!(c, '*' | 'X' | '#' | '-' | ' ' | '.' | '@' | '(' | ')') {
}
}
Ok(())
}
StrategyKind::Shuffle => Ok(()),
StrategyKind::Fake { generator } => {
if !is_valid_generator(generator) {
anyhow::bail!("Unknown fake generator: {}. Use: email, name, first_name, last_name, phone, address, city, zip, company, ip, uuid, date, etc.", generator);
}
Ok(())
}
StrategyKind::Skip => Ok(()),
}
}
}
fn is_valid_generator(name: &str) -> bool {
matches!(
name.to_lowercase().as_str(),
"email"
| "safe_email"
| "name"
| "first_name"
| "last_name"
| "full_name"
| "phone"
| "phone_number"
| "address"
| "street_address"
| "city"
| "state"
| "zip"
| "zip_code"
| "postal_code"
| "country"
| "company"
| "company_name"
| "job_title"
| "username"
| "user_name"
| "url"
| "ip"
| "ip_address"
| "ipv4"
| "ipv6"
| "uuid"
| "date"
| "date_time"
| "datetime"
| "time"
| "credit_card"
| "iban"
| "lorem"
| "paragraph"
| "sentence"
| "word"
| "ssn"
)
}
#[derive(Debug, Clone)]
pub enum RedactValue {
Null,
String(String),
Integer(i64),
Bytes(Vec<u8>),
}
impl RedactValue {
pub fn is_null(&self) -> bool {
matches!(self, RedactValue::Null)
}
pub fn as_str(&self) -> Option<&str> {
match self {
RedactValue::String(s) => Some(s),
_ => None,
}
}
}
pub trait Strategy: Send + Sync {
fn apply(&self, value: &RedactValue, rng: &mut dyn rand::RngCore) -> RedactValue;
fn kind(&self) -> StrategyKind;
}