use std::collections::{BTreeSet, HashSet};
pub type SanitizeResult = Result<String, String>;
#[derive(Default)]
pub struct Sanitizer {
extra_allowed_chars: Option<HashSet<char>>,
}
impl Sanitizer {
pub fn new() -> Self {
Self::default()
}
pub fn with_allowed_chars(mut self, chars: &[char]) -> Self {
let extra_allowed_chars = self
.extra_allowed_chars
.get_or_insert_with(|| HashSet::with_capacity(chars.len()));
for &c in chars {
extra_allowed_chars.insert(c);
}
self
}
pub(crate) fn is_allowed_char(&self, c: char) -> bool {
is_default_allowed_char(c)
|| self
.extra_allowed_chars
.as_ref()
.is_some_and(|chars| chars.contains(&c))
}
pub fn sanitize(&self, input: &str) -> SanitizeResult {
let mut invalid_chars = BTreeSet::new();
for c in input.chars() {
if !self.is_allowed_char(c) {
invalid_chars.insert(c);
}
}
if !invalid_chars.is_empty() {
let invalid_list: String = invalid_chars.into_iter().collect();
return Err(format!("Invalid characters found: {}", invalid_list));
}
Ok(input.to_string())
}
pub fn clean(&self, input: &str) -> String {
input.chars().filter(|&c| self.is_allowed_char(c)).collect()
}
pub fn is_valid(&self, input: &str) -> bool {
input.chars().all(|c| self.is_allowed_char(c))
}
}
fn is_default_allowed_char(c: char) -> bool {
c.is_whitespace()
|| c.is_ascii_alphanumeric()
|| matches!(
c,
'\u{0980}'
..='\u{09FF}' | '\u{0964}' | '\u{0965}' | '\u{200C}' | '\u{200D}' | ','
| '.'
| ':'
| ';'
| '!'
| '?'
| '('
| ')'
| '['
| ']'
| '{'
| '}'
| '"'
| '\''
| '`'
| '-'
| '_'
| '+'
| '='
| '/'
| '\\'
| '|'
| '@'
| '#'
| '$'
| '%'
| '^'
| '&'
| '*'
| '<'
| '>'
)
}