diff --git a/id/src/builder.rs b/id/src/builder.rs deleted file mode 100644 index 678f383..0000000 --- a/id/src/builder.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub struct IdBuilder { - -} \ No newline at end of file diff --git a/id/src/chunk.rs b/id/src/chunk.rs deleted file mode 100644 index 5e7c37c..0000000 --- a/id/src/chunk.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use rand::Rng; - -use crate::error::Error; - -static CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz"; - -static BASE: u16 = 33; -static SQUARED_BASE: u16 = 1089; -static CUBED_BASE: u16 = 35937; - -pub static NIL: IdChunk = IdChunk(0); -pub static MAX: IdChunk = IdChunk(CUBED_BASE-1); - -fn char_to_u8(c: char) -> Option { - 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 { - 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 { - if int >= CUBED_BASE { - return Err(Error::OutsideOfRange(int)) - } - 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 IdChunk(u16); - -impl IdChunk { - pub fn new(rng: &mut R) -> Self { - Self(rng.gen_range(0..CUBED_BASE)) - - } -} - -impl Display for IdChunk { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() - } -} - -impl FromStr for IdChunk { - type Err = Error; - - fn from_str(s: &str) -> Result { - todo!() - } -} - -impl TryFrom for IdChunk { - type Error = Error; - - fn try_from(value: u16) -> Result { - todo!() - } -} - -impl From for u16 { - fn from(value: IdChunk) -> Self { - value.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - fn assert_eq_chunk(s: &str, i: u16) { - assert_eq!(s, &u16_to_string(i).unwrap()); - assert_eq!(i, str_to_u16(s).unwrap()) - - } - #[test] - fn test_nil() { - assert_eq_chunk("000", 0); - } - #[test] - fn test_max() { - assert_eq_chunk("zzz", CUBED_BASE-1); - } - fn assert_random(rand: &mut R) - where - R: Rng - { - let chunk = IdChunk::new(rand); - let s = chunk.to_string(); - assert_eq!(chunk,IdChunk::from_str(&s).unwrap()) - } - fn random_x10() { - let mut rng = rand::thread_rng(); - for _ in 0..10 { - assert_random(&mut rng); - } - - } -} \ No newline at end of file diff --git a/id/src/config.rs b/id/src/config.rs deleted file mode 100644 index f2fe151..0000000 --- a/id/src/config.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub struct Config { - pub enable_nil: bool, - pub enable_max: bool, -} \ No newline at end of file diff --git a/id/src/double.rs b/id/src/double.rs index e69de29..8a36076 100644 --- a/id/src/double.rs +++ b/id/src/double.rs @@ -0,0 +1,129 @@ +use std::{fmt::Display, str::FromStr}; + +use rand::{distributions::Standard, prelude::Distribution, Rng}; + +use crate::{Error, Id, SingleId}; + +#[derive(Debug, Clone, PartialEq)] +pub struct DoubleId{ + inner: (SingleId, SingleId) +} + +impl DoubleId { + #[cfg(test)] + pub fn validate(&self) -> bool { + self.inner.0.validate() && self.inner.1.validate() && (u32::from(self.clone()) < Self::SIZE) + } +} + + +impl Id for DoubleId{ + type SizeType = u32; + const SIZE: Self::SizeType = (SingleId::SIZE as u32).pow(2); + /// ``` + /// use caretta_id::{Id, DoubleId}; + /// use std::str::FromStr; + /// + /// assert_eq!(DoubleId::NIL, DoubleId::from_str("000-000").unwrap()); + /// assert_eq!(DoubleId::NIL, DoubleId::try_from(0).unwrap()); + /// ``` + const NIL: Self = Self{ + inner: (SingleId::NIL, SingleId::NIL) + }; + + /// ``` + /// use caretta_id::{Id, DoubleId}; + /// use std::str::FromStr; + /// + /// assert_eq!(DoubleId::MAX, DoubleId::from_str("zzz-zzz").unwrap()); + /// assert_eq!(DoubleId::MAX, DoubleId::try_from(1291467968).unwrap()); + /// ``` + const MAX: Self = Self{ + inner: (SingleId::MAX, SingleId::MAX) + }; +} + +impl Display for DoubleId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}", self.inner.0, self.inner.1) + } +} + +impl FromStr for DoubleId { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Self { + inner : match s.len() { + 7 => { + match s.chars().nth(3).unwrap() { + '-' => { + Ok((SingleId::from_str(&s[0..3])?, SingleId::from_str(&s[4..7])?)) + }, + x => { + Err(Error::InvalidDelimiter(x)) + } + } + + } + 6 => { + Ok((SingleId::from_str(&s[0..3])?,SingleId::from_str(&s[3..6])?)) + } + _ => Err(Error::InvalidLength(s.to_string())) + }? + }) + } +} + + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> DoubleId { + DoubleId { + inner: (rng.r#gen(), rng.r#gen()) + } + } +} + +impl TryFrom for DoubleId { + type Error = Error; + + fn try_from(value: u32) -> Result { + if value < Self::SIZE { + Ok(Self{ + inner: ( + SingleId::try_from(u16::try_from(value/(SingleId::SIZE as u32)).unwrap())?, + SingleId::try_from(u16::try_from(value % (SingleId::SIZE as u32)).unwrap())? + )}) + } else { + Err(Error::OutsideOfRange(value as u64)) + } + } +} + +impl From for u32 { + fn from(value: DoubleId) -> Self { + u32::from(u16::from(value.inner.0)) * u32::from(SingleId::SIZE) + u32::from(u16::from(value.inner.1)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_random(rand: &mut R) + where + R: Rng + { + let chunk: SingleId = rand.r#gen(); + let s = chunk.to_string(); + assert_eq!(chunk,SingleId::from_str(&s).unwrap()) + } + #[test] + fn random_x10() { + let mut rng = rand::thread_rng(); + for _ in 0..10 { + assert_random(&mut rng); + } + + } +} \ No newline at end of file diff --git a/id/src/error.rs b/id/src/error.rs index 3c8af2d..7b6ad6a 100644 --- a/id/src/error.rs +++ b/id/src/error.rs @@ -1,8 +1,12 @@ #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Outside of range: {0}")] - OutsideOfRange(u16), + OutsideOfRange(u64), #[error("Invalid chunk: {0}")] InvalidChunk(String), + #[error("Invalid delemeter: {0}")] + InvalidDelimiter(char), + #[error("Invalid length: {0}")] + InvalidLength(String) } diff --git a/id/src/lib.rs b/id/src/lib.rs index 795d0b5..97b4870 100644 --- a/id/src/lib.rs +++ b/id/src/lib.rs @@ -1,6 +1,21 @@ -mod chunk; +mod single; mod double; mod error; -mod single; mod triple; +use rand::Rng; +pub use single::*; +pub use double::*; +pub use triple::*; +pub use error::*; + + +const DOUBLE_ID_SIZE: u32 = (SingleId::SIZE as u32).pow(2); +const TRIPLE_ID_SIZE: u64 = (SingleId::SIZE as u64).pow(3); + +pub trait Id { + type SizeType; + const NIL: Self; + const MAX: Self; + const SIZE: Self::SizeType; +} \ No newline at end of file diff --git a/id/src/single.rs b/id/src/single.rs index e69de29..67cf56a 100644 --- a/id/src/single.rs +++ b/id/src/single.rs @@ -0,0 +1,210 @@ +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 { + 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 { + 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 { + if int >= CUBED_BASE { + return Err(Error::OutsideOfRange(int as u64)) + } + 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 SingleId { + #[cfg(test)] + pub fn validate(&self) -> bool { + self.inner < Self::SIZE + } +} + +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 + }; + + +} + +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 { + Ok(Self{ + inner: str_to_u16(s)? + }) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> SingleId { + SingleId { + inner: rng.gen_range(0..SingleId::SIZE) + } + } +} + +impl TryFrom for SingleId { + type Error = Error; + + fn try_from(value: u16) -> Result { + if value < Self::SIZE { + Ok(Self{inner: value}) + } else { + Err(Error::OutsideOfRange(value as u64)) + } + } +} + +impl From for u16 { + fn from(value: SingleId) -> Self { + value.inner + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_random(rand: &mut R) + where + R: Rng + { + let chunk: SingleId = rand.r#gen(); + assert!(chunk.validate()); + let s = chunk.to_string(); + assert_eq!(chunk,SingleId::from_str(&s).unwrap()); + let i = u16::from(chunk.clone()); + 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); + } + + } +} \ No newline at end of file diff --git a/id/src/triple.rs b/id/src/triple.rs index e69de29..cb71dbf 100644 --- a/id/src/triple.rs +++ b/id/src/triple.rs @@ -0,0 +1,6 @@ +use crate::{Error, SingleId}; + +pub struct TripleId { + inner: (SingleId, SingleId, SingleId) +} +