use bstr::{BStr, BString, ByteSlice}; use std::cmp::Ordering; use std::ops::Deref; #[cfg_attr(test, derive(Debug))] #[derive(Clone)] pub enum EncodedString { Utf8(String), Unknown(BString), } impl EncodedString { pub fn as_bstr(&self) -> &BStr { match self { EncodedString::Utf8(v) => v.as_str().into(), EncodedString::Unknown(v) => v.as_bstr(), } } pub fn cmp_ref(&self, other: EncodedStringRef<'_>) -> Ordering { match (self, other) { (EncodedString::Utf8(a), EncodedStringRef::Utf8(b)) => { let a = a.chars().map(|c| c.to_ascii_lowercase()); let b = b.chars().map(|c| c.to_ascii_lowercase()); a.cmp(b) } (EncodedString::Unknown(a), EncodedStringRef::Unknown(b)) => a.deref().as_bstr().cmp(b), (EncodedString::Utf8(a), EncodedStringRef::Unknown(b)) => a.as_bytes().cmp(b.as_ref()), (EncodedString::Unknown(a), EncodedStringRef::Utf8(b)) => a.deref().as_bytes().cmp(b.as_bytes()), } } } #[cfg_attr(test, derive(Debug))] #[derive(Clone, Copy)] pub enum EncodedStringRef<'a> { Utf8(&'a str), Unknown(&'a BStr), } impl<'a> From<&'a BStr> for EncodedStringRef<'a> { fn from(v: &'a BStr) -> Self { match v.to_str() { Ok(v) => EncodedStringRef::Utf8(v), Err(_) => EncodedStringRef::Unknown(v), } } } impl<'a> From> for EncodedString { fn from(v: EncodedStringRef<'a>) -> Self { match v { EncodedStringRef::Utf8(v) => EncodedString::Utf8(v.to_owned()), EncodedStringRef::Unknown(v) => EncodedString::Unknown(v.to_owned()), } } } impl<'a> From<&'a BStr> for EncodedString { fn from(v: &'a BStr) -> Self { match v.to_str() { Ok(v) => EncodedString::Utf8(v.to_owned()), Err(_) => EncodedString::Unknown(v.to_owned()), } } } #[cfg(test)] mod encoded_string { use std::cmp::Ordering; use crate::snapshot::util::{EncodedString, EncodedStringRef}; #[test] fn basic_ascii_case_folding() { assert_eq!( EncodedString::Utf8("FooBar".into()).cmp_ref(EncodedStringRef::Utf8("foobar")), Ordering::Equal ); } #[test] fn no_advanced_unicode_folding() { assert_ne!( EncodedString::Utf8("Masse".into()).cmp_ref(EncodedStringRef::Utf8("Maße")), Ordering::Equal ); } #[test] fn unknown_encoding_pairs_do_not_try_to_ignore_cases() { assert_ne!( EncodedString::Utf8("Foo".into()).cmp_ref(EncodedStringRef::Unknown("foo".into())), Ordering::Equal ); assert_ne!( EncodedString::Unknown("Foo".into()).cmp_ref(EncodedStringRef::Utf8("foo")), Ordering::Equal ); assert_ne!( EncodedString::Unknown("Foo".into()).cmp_ref(EncodedStringRef::Unknown("foo".into())), Ordering::Equal ); } }