use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
ops, str,
};
use crate::error::{ErrorKind, Result};
#[derive(Clone)]
pub struct Guid(Repr);
pub trait IsValidGuid {
fn is_valid_guid(&self) -> bool;
}
#[derive(Clone)]
enum Repr {
Valid([u8; 12]),
Invalid(Box<str>),
}
pub const ROOT_GUID: Guid = Guid(Repr::Valid(*b"root________"));
pub const TOOLBAR_GUID: Guid = Guid(Repr::Valid(*b"toolbar_____"));
pub const MENU_GUID: Guid = Guid(Repr::Valid(*b"menu________"));
pub const UNFILED_GUID: Guid = Guid(Repr::Valid(*b"unfiled_____"));
pub const MOBILE_GUID: Guid = Guid(Repr::Valid(*b"mobile______"));
pub const TAGS_GUID: Guid = Guid(Repr::Valid(*b"tags________"));
const VALID_GUID_BYTES: [u8; 255] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
impl Guid {
pub fn from_utf8(b: &[u8]) -> Result<Guid> {
let repr = if b.is_valid_guid() {
let mut bytes = [0u8; 12];
bytes.copy_from_slice(b);
Repr::Valid(bytes)
} else {
match str::from_utf8(b) {
Ok(s) => Repr::Invalid(s.into()),
Err(err) => return Err(err.into()),
}
};
Ok(Guid(repr))
}
pub fn from_utf16(b: &[u16]) -> Result<Guid> {
let repr = if b.is_valid_guid() {
let mut bytes = [0u8; 12];
for (index, &byte) in b.iter().enumerate() {
if byte > u16::from(u8::max_value()) {
return Err(ErrorKind::InvalidByte(byte).into());
}
bytes[index] = byte as u8;
}
Repr::Valid(bytes)
} else {
match String::from_utf16(b) {
Ok(s) => Repr::Invalid(s.into()),
Err(err) => return Err(err.into()),
}
};
Ok(Guid(repr))
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
match self.0 {
Repr::Valid(ref bytes) => bytes,
Repr::Invalid(ref s) => s.as_bytes(),
}
}
#[inline]
pub fn as_str(&self) -> &str {
match self.0 {
Repr::Valid(ref bytes) => str::from_utf8(bytes).unwrap(),
Repr::Invalid(ref s) => s,
}
}
#[inline]
pub fn is_built_in_root(&self) -> bool {
self == TOOLBAR_GUID
|| self == MENU_GUID
|| self == UNFILED_GUID
|| self == MOBILE_GUID
|| self == TAGS_GUID
}
}
impl IsValidGuid for Guid {
#[inline]
fn is_valid_guid(&self) -> bool {
match self.0 {
Repr::Valid(_) => true,
Repr::Invalid(_) => false,
}
}
}
impl<T: Copy + Into<usize>> IsValidGuid for [T] {
#[inline]
fn is_valid_guid(&self) -> bool {
self.len() == 12
&& self
.iter()
.all(|&byte| VALID_GUID_BYTES.get(byte.into()).map_or(false, |&b| b == 1))
}
}
impl From<String> for Guid {
#[inline]
fn from(s: String) -> Guid {
Guid::from(s.as_str())
}
}
impl<'a> From<&'a str> for Guid {
#[inline]
fn from(s: &'a str) -> Guid {
let repr = if s.as_bytes().is_valid_guid() {
assert!(s.is_char_boundary(12));
let mut bytes = [0u8; 12];
bytes.copy_from_slice(s.as_bytes());
Repr::Valid(bytes)
} else {
Repr::Invalid(s.into())
};
Guid(repr)
}
}
impl AsRef<str> for Guid {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for Guid {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ops::Deref for Guid {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl Ord for Guid {
fn cmp(&self, other: &Guid) -> Ordering {
self.as_bytes().cmp(other.as_bytes())
}
}
impl PartialOrd for Guid {
fn partial_cmp(&self, other: &Guid) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<str> for Guid {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<'a> PartialEq<&'a str> for Guid {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self == *other
}
}
impl PartialEq for Guid {
#[inline]
fn eq(&self, other: &Guid) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<'a> PartialEq<Guid> for &'a Guid {
#[inline]
fn eq(&self, other: &Guid) -> bool {
*self == other
}
}
impl Eq for Guid {}
impl Hash for Guid {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
impl fmt::Debug for Guid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Guid({:?})", self.as_str())
}
}
impl fmt::Display for Guid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_valid() {
let valid_guids = &[
"bookmarkAAAA",
"menu________",
"__folderBB__",
"queryAAAAAAA",
];
for s in valid_guids {
assert!(s.as_bytes().is_valid_guid(), "{:?} should validate", s);
assert!(Guid::from(*s).is_valid_guid());
}
let invalid_guids = &["bookmarkAAA", "folder!", "b@dgu1d!"];
for s in invalid_guids {
assert!(!s.as_bytes().is_valid_guid(), "{:?} should not validate", s);
assert!(!Guid::from(*s).is_valid_guid());
}
let invalid_guid_bytes: &[[u8; 12]] =
&[[113, 117, 101, 114, 121, 65, 225, 193, 65, 65, 65, 65]];
for bytes in invalid_guid_bytes {
assert!(!bytes.is_valid_guid(), "{:?} should not validate", bytes);
Guid::from_utf8(bytes).expect_err("Should not make GUID from invalid UTF-8");
}
}
}