use core::str::Utf8Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Utf8ValidationLevel {
None,
Lenient,
Strict,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NormalizationForm {
NFD,
NFC,
NFKD,
NFKC,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Utf8Config {
pub validation_level: Utf8ValidationLevel,
pub normalization: Option<NormalizationForm>,
pub case_mapping: bool,
pub grapheme_cluster: bool,
}
impl Default for Utf8Config {
fn default() -> Self {
Self {
validation_level: Utf8ValidationLevel::Strict,
normalization: None,
case_mapping: false,
grapheme_cluster: false,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Utf8Result<T> {
Ok(T),
ValidationError(Utf8Error),
Error(&'static str),
}
pub struct Utf8Processor {
config: Utf8Config,
}
impl Utf8Processor {
pub fn new(config: Utf8Config) -> Self {
Self { config }
}
pub fn validate(&self, input: &[u8]) -> Utf8Result<()> {
match self.config.validation_level {
Utf8ValidationLevel::None => Utf8Result::Ok(()),
Utf8ValidationLevel::Lenient => {
let mut i = 0;
while i < input.len() {
let (len, valid) = self.utf8_char_length(&input[i..]);
if !valid {
}
i += if len > 0 { len } else { 1 };
}
Utf8Result::Ok(())
}
Utf8ValidationLevel::Strict => {
match core::str::from_utf8(input) {
Ok(_) => Utf8Result::Ok(()),
Err(e) => Utf8Result::ValidationError(e),
}
}
}
}
pub fn char_length(&self, input: &[u8]) -> usize {
let mut count = 0;
let mut i = 0;
while i < input.len() {
let (len, valid) = self.utf8_char_length(&input[i..]);
if valid {
count += 1;
}
i += if len > 0 { len } else { 1 };
}
count
}
fn utf8_char_length(&self, input: &[u8]) -> (usize, bool) {
if input.is_empty() {
return (0, false);
}
let first = input[0];
if first < 0x80 {
(1, true)
} else if first < 0xC0 {
(1, false)
} else if first < 0xE0 {
if input.len() >= 2 && (input[1] & 0xC0) == 0x80 {
(2, true)
} else {
(1, false)
}
} else if first < 0xF0 {
if input.len() >= 3 && (input[1] & 0xC0) == 0x80 && (input[2] & 0xC0) == 0x80 {
(3, true)
} else {
(1, false)
}
} else if first < 0xF8 {
if input.len() >= 4
&& (input[1] & 0xC0) == 0x80
&& (input[2] & 0xC0) == 0x80
&& (input[3] & 0xC0) == 0x80
{
(4, true)
} else {
(1, false)
}
} else {
(1, false)
}
}
pub fn to_string<'a>(&self, input: &'a [u8]) -> Option<&'a str> {
match self.config.validation_level {
Utf8ValidationLevel::Strict => core::str::from_utf8(input).ok(),
Utf8ValidationLevel::Lenient => {
core::str::from_utf8(input).ok()
}
Utf8ValidationLevel::None => {
core::str::from_utf8(input).ok()
}
}
}
pub fn compare(&self, a: &[u8], b: &[u8]) -> core::cmp::Ordering {
let a_len = a.iter().position(|&c| c == 0).unwrap_or(a.len());
let b_len = b.iter().position(|&c| c == 0).unwrap_or(b.len());
if let (Some(a_str), Some(b_str)) = (
core::str::from_utf8(&a[..a_len]).ok(),
core::str::from_utf8(&b[..b_len]).ok(),
) {
a_str.cmp(b_str)
} else {
a[..a_len].cmp(&b[..b_len])
}
}
pub fn starts_with(&self, input: &[u8], prefix: &[u8]) -> bool {
let input_len = input.iter().position(|&c| c == 0).unwrap_or(input.len());
let prefix_len = prefix.iter().position(|&c| c == 0).unwrap_or(prefix.len());
if prefix_len > input_len {
return false;
}
for i in 0..prefix_len {
if input[i] != prefix[i] {
return false;
}
}
true
}
pub fn contains(&self, input: &[u8], substring: &[u8]) -> bool {
let input_len = input.iter().position(|&c| c == 0).unwrap_or(input.len());
let substring_len = substring
.iter()
.position(|&c| c == 0)
.unwrap_or(substring.len());
if substring_len == 0 {
return true;
}
if substring_len > input_len {
return false;
}
for i in 0..=input_len - substring_len {
let mut match_found = true;
for j in 0..substring_len {
if input[i + j] != substring[j] {
match_found = false;
break;
}
}
if match_found {
return true;
}
}
false
}
}
impl Default for Utf8Processor {
fn default() -> Self {
Self {
config: Utf8Config::default(),
}
}
}
pub static GLOBAL_UTF8_PROCESSOR: Utf8Processor = Utf8Processor {
config: Utf8Config {
validation_level: Utf8ValidationLevel::Strict,
normalization: None,
case_mapping: false,
grapheme_cluster: false,
},
};
pub fn get_global_utf8_processor() -> &'static Utf8Processor {
&GLOBAL_UTF8_PROCESSOR
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_utf8_validation() {
let processor = Utf8Processor::default();
let valid_utf8 = "Hello 世界 👋".as_bytes();
assert_eq!(processor.validate(valid_utf8), Utf8Result::Ok(()));
let ascii = "Hello World".as_bytes();
assert_eq!(processor.validate(ascii), Utf8Result::Ok(()));
}
#[test]
fn test_char_length() {
let processor = Utf8Processor::default();
let ascii = "Hello".as_bytes();
assert_eq!(processor.char_length(ascii), 5);
let utf8 = "Hello 世界".as_bytes();
assert_eq!(processor.char_length(utf8), 8);
let emoji = "👋 世界".as_bytes();
assert_eq!(processor.char_length(emoji), 4); }
#[test]
fn test_compare() {
let processor = Utf8Processor::default();
let a = "apple".as_bytes();
let b = "banana".as_bytes();
let c = "apple".as_bytes();
assert_eq!(processor.compare(a, b), core::cmp::Ordering::Less);
assert_eq!(processor.compare(b, a), core::cmp::Ordering::Greater);
assert_eq!(processor.compare(a, c), core::cmp::Ordering::Equal);
}
#[test]
fn test_starts_with() {
let processor = Utf8Processor::default();
let input = "Hello World".as_bytes();
let prefix1 = "Hello".as_bytes();
let prefix2 = "World".as_bytes();
assert!(processor.starts_with(input, prefix1));
assert!(!processor.starts_with(input, prefix2));
}
#[test]
fn test_contains() {
let processor = Utf8Processor::default();
let input = "Hello World".as_bytes();
let substr1 = "World".as_bytes();
let substr2 = "Test".as_bytes();
assert!(processor.contains(input, substr1));
assert!(!processor.contains(input, substr2));
}
}