use crate::error::ParseError;
use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum Char {
_0 = b'0',
_1 = b'1',
_2 = b'2',
_3 = b'3',
_4 = b'4',
_5 = b'5',
_6 = b'6',
_7 = b'7',
_8 = b'8',
_9 = b'9',
A = b'A',
B = b'B',
C = b'C',
D = b'D',
E = b'E',
F = b'F',
G = b'G',
H = b'H',
I = b'I',
J = b'J',
K = b'K',
L = b'L',
M = b'M',
N = b'N',
O = b'O',
P = b'P',
Q = b'Q',
R = b'R',
S = b'S',
T = b'T',
U = b'U',
V = b'V',
W = b'W',
X = b'X',
Y = b'Y',
Z = b'Z',
}
impl Char {
#[inline]
#[must_use]
pub const fn new(byte: u8) -> Option<Self> {
Some(match byte {
b'0' => Self::_0,
b'1' => Self::_1,
b'2' => Self::_2,
b'3' => Self::_3,
b'4' => Self::_4,
b'5' => Self::_5,
b'6' => Self::_6,
b'7' => Self::_7,
b'8' => Self::_8,
b'9' => Self::_9,
b'A' => Self::A,
b'B' => Self::B,
b'C' => Self::C,
b'D' => Self::D,
b'E' => Self::E,
b'F' => Self::F,
b'G' => Self::G,
b'H' => Self::H,
b'I' => Self::I,
b'J' => Self::J,
b'K' => Self::K,
b'L' => Self::L,
b'M' => Self::M,
b'N' => Self::N,
b'O' => Self::O,
b'P' => Self::P,
b'Q' => Self::Q,
b'R' => Self::R,
b'S' => Self::S,
b'T' => Self::T,
b'U' => Self::U,
b'V' => Self::V,
b'W' => Self::W,
b'X' => Self::X,
b'Y' => Self::Y,
b'Z' => Self::Z,
_ => return None,
})
}
#[inline]
pub const fn new_array<const N: usize>(value: &[u8]) -> Result<[Char; N], ParseError> {
let mut result = [Self::Z; N];
let len = value.len();
if len != N {
return Err(ParseError::WrongLength {
expected: N as _,
got: len,
});
}
let mut i = 0;
while i < N {
if let Some(c) = Self::new(value[i]) {
result[i] = c;
} else {
return Err(ParseError::InvalidChar {
byte: value[i],
position: i,
});
}
i += 1;
}
Ok(result)
}
#[inline]
#[must_use]
pub const fn new_array_lossy<const N: usize>(value: &[u8], fallback: Self) -> [Char; N] {
let mut result = [fallback; N];
let len = value.len();
let mut i = 0;
while i < N && i < len {
if let Some(c) = Self::new(value[i]) {
result[i] = c;
}
i += 1;
}
result
}
#[inline]
#[must_use]
pub const fn as_byte(self) -> u8 {
self as u8
}
#[inline]
#[must_use]
pub const fn array_as_bytes<const N: usize>(array: &[Self; N]) -> &[u8; N] {
unsafe { &*((array as *const [Char; N]).cast()) }
}
#[inline]
#[must_use]
pub const fn slice_as_bytes(slice: &[Self]) -> &[u8] {
unsafe { &*(slice as *const [Char] as *const [u8]) }
}
#[inline]
#[must_use]
pub const fn array_as_str<const N: usize>(array: &[Self; N]) -> &str {
unsafe { core::str::from_utf8_unchecked(Self::array_as_bytes(array)) }
}
}
impl Ord for Char {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.as_byte().cmp(&other.as_byte())
}
}
impl PartialOrd for Char {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Char {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_byte().hash(state);
}
#[inline]
fn hash_slice<H: Hasher>(data: &[Self], state: &mut H) {
u8::hash_slice(Self::slice_as_bytes(data), state);
}
}
#[cfg(feature = "serde")]
pub(crate) mod de {
use super::{Char, ParseError};
use core::fmt;
use core::marker::PhantomData;
use serde::de::{self, Unexpected, Visitor};
pub(crate) struct ArrayVisitor<const N: usize>(PhantomData<[(); N]>);
impl<const N: usize> ArrayVisitor<N> {
#[inline]
pub const fn new() -> Self {
ArrayVisitor(PhantomData)
}
}
impl<'de, const N: usize> Visitor<'de> for ArrayVisitor<N> {
type Value = [Char; N];
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"an ascii string or byte array of length {N} containing only A-Z and 0-9"
)
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
Char::new_array(value.as_bytes())
.map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
}
fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
Char::new_array(&value).map_err(|e| match e {
ParseError::WrongLength { got, .. } => E::invalid_length(got, &self),
ParseError::InvalidChar { .. } => E::invalid_value(Unexpected::Bytes(value), &self),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt;
impl fmt::Debug for Char {
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str(Self::array_as_str(core::array::from_ref(self)))
}
}
#[test]
fn test_char() {
for c in u8::MIN..u8::MAX {
match c {
b'0'..=b'9' | b'A'..=b'Z' => assert_eq!(Char::new(c).unwrap().as_byte(), c),
_ => assert!(Char::new(c).is_none()),
}
}
}
#[test]
fn test_big_array() {
let value = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let arr = Char::new_array(value).unwrap();
assert_eq!(Char::array_as_bytes(&arr), value);
let value = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/";
if let ParseError::InvalidChar { byte, position } =
Char::new_array::<73>(value).unwrap_err()
{
assert_eq!(byte, b'/');
assert_eq!(position, 72);
} else {
unreachable!();
}
let value = b"/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if let ParseError::InvalidChar { byte, position } =
Char::new_array::<73>(value).unwrap_err()
{
assert_eq!(byte, b'/');
assert_eq!(position, 0);
} else {
unreachable!();
}
let value = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWX:YZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if let ParseError::InvalidChar { byte, position } =
Char::new_array::<73>(value).unwrap_err()
{
assert_eq!(byte, b':');
assert_eq!(position, 34);
} else {
unreachable!();
}
}
#[test]
fn test_zero_array() {
let value = b"";
let arr = Char::new_array(value).unwrap();
assert_eq!(Char::array_as_bytes(&arr), value);
}
#[test]
fn test_wrong_lengths() {
let value = b"01234";
assert_eq!(Char::new_array::<0>(value).unwrap_err().valid_up_to(), 0);
assert_eq!(Char::new_array::<2>(value).unwrap_err().valid_up_to(), 2);
assert_eq!(Char::new_array::<4>(value).unwrap_err().valid_up_to(), 4);
assert_eq!(Char::new_array::<6>(value).unwrap_err().valid_up_to(), 5);
assert_eq!(Char::new_array::<10>(value).unwrap_err().valid_up_to(), 5);
assert_eq!(Char::new_array::<255>(value).unwrap_err().valid_up_to(), 5);
}
#[test]
fn test_str() {
let value = "01234";
let arr = Char::new_array::<5>(value.as_bytes()).unwrap();
assert_eq!(Char::array_as_str(&arr), value);
}
}