caretta-sync/id/src/single.rs

212 lines
4.9 KiB
Rust
Raw Normal View History

2025-09-13 08:39:06 +09:00
use std::{fmt::Display, str::FromStr};
use rand::{distributions::{uniform::UniformInt, Standard}, prelude::Distribution, Rng};
use crate::{error::Error, Id};
const CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz";
const BASE: u16 = 33;
const SQUARED_BASE: u16 = BASE.pow(2);
const CUBED_BASE: u16 = BASE.pow(3);
fn char_to_u8(c: char) -> Option<u8> {
Some(match c {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' => 10,
'b' => 11,
'c' => 12,
'd' => 13,
'e' => 14,
'f' => 15,
'g' => 16,
'h' => 17,
'i' => 1,
'j' => 18,
'k' => 19,
'l' => 1,
'm' => 20,
'n' => 21,
'o' => 0,
'p' => 22,
'q' => 23,
'r' => 24,
's' => 25,
't' => 26,
'u' => 27,
'v' => 28,
'w' => 29,
'x' => 30,
'y' => 31,
'z' => 32,
'A' => 10,
'B' => 11,
'C' => 12,
'D' => 13,
'E' => 14,
'F' => 15,
'G' => 16,
'H' => 17,
'I' => 1,
'J' => 18,
'K' => 19,
'L' => 1,
'M' => 20,
'N' => 21,
'O' => 0,
'P' => 22,
'Q' => 23,
'R' => 24,
'S' => 25,
'T' => 26,
'U' => 27,
'V' => 28,
'W' => 29,
'X' => 30,
'Y' => 31,
'Z' => 32,
_ => return None
})
}
fn str_to_u16(s: &str) -> Result<u16, Error> {
if s.len() != 3 {
return Err(Error::InvalidChunk(format!("Chunk '{}' is not 3 characters", s)))
}
let mut buf : [u16;3] = [0;3];
for (i, c) in s.chars().enumerate() {
buf[i] = BASE.pow((2 - i) as u32) * (char_to_u8(c).ok_or(Error::InvalidChunk(format!("Invalid char: {}", c)))? as u16);
}
Ok(buf.iter().sum())
}
fn u16_to_string(int: u16) -> Result<String, Error> {
if int >= CUBED_BASE {
2025-09-13 12:29:59 +09:00
return Err(Error::OutsideOfRange{
expected: CUBED_BASE as usize,
found: int as usize
})
2025-09-13 08:39:06 +09:00
}
let first_char = char::from(CHARACTERS[usize::try_from(int / SQUARED_BASE).unwrap()]);
let second_char = char::from(CHARACTERS[usize::try_from((int % SQUARED_BASE)/ BASE).unwrap()]);
let third_char = char::from(CHARACTERS[usize::try_from(int % BASE).unwrap()]);
Ok(format!("{}{}{}", first_char, second_char, third_char))
}
#[derive(Clone, Debug, PartialEq)]
pub struct SingleId{
inner: u16
}
impl Id for SingleId {
type SizeType = u16;
const SIZE: Self::SizeType = CUBED_BASE;
/// ```
/// use caretta_id::{SingleId, Id};
/// use std::str::FromStr;
///
/// assert_eq!(SingleId::NIL, SingleId::from_str("000").unwrap());
/// assert_eq!(SingleId::NIL, SingleId::try_from(0).unwrap());
/// ```
const NIL: SingleId = SingleId{
inner: 0
};
/// ```
/// use caretta_id::{Id, SingleId};
/// use std::str::FromStr;
///
/// assert_eq!(SingleId::MAX, SingleId::from_str("zzz").unwrap());
/// assert_eq!(SingleId::MAX, SingleId::try_from(35936).unwrap());
/// ```
const MAX: SingleId = SingleId{
inner: Self::SIZE-1
};
2025-09-13 12:29:59 +09:00
#[cfg(test)]
fn is_valid(&self) -> bool {
self.inner < Self::SIZE
}
2025-09-13 08:39:06 +09:00
}
impl Display for SingleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", u16_to_string(self.inner).unwrap())
}
}
impl FromStr for SingleId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self{
inner: str_to_u16(s)?
})
}
}
impl Distribution<SingleId> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> SingleId {
SingleId {
inner: rng.gen_range(0..SingleId::SIZE)
}
}
}
impl TryFrom<u16> for SingleId {
type Error = Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value < Self::SIZE {
Ok(Self{inner: value})
} else {
2025-09-13 12:29:59 +09:00
Err(Error::OutsideOfRange{
expected: Self::SIZE as usize,
found: value as usize
})
2025-09-13 08:39:06 +09:00
}
}
}
impl From<&SingleId> for u16 {
fn from(value: &SingleId) -> Self {
2025-09-13 08:39:06 +09:00
value.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_random<R>(rand: &mut R)
where
R: Rng
{
let chunk: SingleId = rand.r#gen();
2025-09-13 12:29:59 +09:00
assert!(chunk.is_valid());
2025-09-13 08:39:06 +09:00
let s = chunk.to_string();
assert_eq!(chunk,SingleId::from_str(&s).unwrap());
let i = u16::from(&chunk);
2025-09-13 08:39:06 +09:00
assert_eq!(chunk, SingleId::try_from(i).unwrap());
}
#[test]
fn random_x10() {
let mut rng = rand::thread_rng();
for _ in 0..10 {
assert_random(&mut rng);
}
}
}