use regex_lite::Regex;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Span {
pub start: usize,
pub end: usize,
pub type_: String,
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "Rule[]")]
pub type RuleArray;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "rule_type", rename_all = "camelCase")]
pub enum Rule {
#[serde(rename = "pii.email")]
Email,
#[serde(rename = "pii.ssn")]
Ssn,
#[serde(rename = "pii.credit_card")]
CreditCard,
#[serde(rename = "pii.phone")]
Phone,
#[serde(rename = "pii.id.somali_nid")]
SomaliNid,
#[serde(rename_all = "camelCase")]
Custom { type_: String, regex: String },
}
impl Rule {
fn get_type(&self) -> String {
match self {
Rule::Email => "pii.email".to_string(),
Rule::Ssn => "pii.ssn".to_string(),
Rule::CreditCard => "pii.credit_card".to_string(),
Rule::Phone => "pii.phone".to_string(),
Rule::SomaliNid => "pii.id.somali_nid".to_string(),
Rule::Custom { type_, .. } => type_.clone(),
}
}
fn get_regex(&self) -> Regex {
match self {
Rule::Email => {
Regex::new(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}").unwrap()
}
Rule::Ssn => Regex::new(r"\b\d{3}[-]?\d{2}[-]?\d{4}\b").unwrap(),
Rule::CreditCard => {
Regex::new(r"\b(?:\d[ -]*?){13,16}\b").unwrap() }
Rule::Phone => Regex::new(r"\b(?:\+\d{1,3}[-\s]?)?\(?\d{3}\)?[-\s]?\d{3}[-\s]?\d{4}\b").unwrap(),
Rule::SomaliNid => Regex::new(r"\b[A-Z]{2}\d{7}\b").unwrap(), Rule::Custom { regex, .. } => Regex::new(regex).unwrap_or_else(|_| Regex::new(r"a^").unwrap()), }
}
}
#[wasm_bindgen]
pub fn find_pii(text: &str, rules_js: RuleArray) -> Result<JsValue, JsValue> {
let rules: Vec<Rule> = serde_wasm_bindgen::from_value(rules_js.into())?;
let mut spans = Vec::new();
for rule in rules {
let rule_type = rule.get_type();
let regex = rule.get_regex();
for mat in regex.find_iter(text) {
spans.push(Span {
start: mat.start(),
end: mat.end(),
type_: rule_type.clone(),
});
}
}
spans.sort_by_key(|span| span.start);
Ok(serde_wasm_bindgen::to_value(&spans)?)
}
fn is_valid_credit_card(number: &str) -> bool {
let digits: Vec<u32> = number
.chars()
.filter(|c| c.is_digit(10))
.map(|c| c.to_digit(10).unwrap())
.collect();
if digits.len() < 13 || digits.len() > 19 {
return false;
}
let mut sum = 0;
let mut double = false;
for &digit in digits.iter().rev() {
let mut value = digit;
if double {
value *= 2;
if value > 9 {
value -= 9;
}
}
sum += value;
double = !double;
}
sum % 10 == 0
}
#[cfg(test)]
mod tests;