Implement TripleId
This commit is contained in:
parent
6fb909cd07
commit
71e0d31d8d
6 changed files with 219 additions and 42 deletions
|
|
@ -2,21 +2,13 @@ use std::{fmt::Display, str::FromStr};
|
|||
|
||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||
|
||||
use crate::{Error, Id, SingleId};
|
||||
use crate::{utils::is_delimiter, 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);
|
||||
|
|
@ -41,6 +33,11 @@ impl Id for DoubleId{
|
|||
const MAX: Self = Self{
|
||||
inner: (SingleId::MAX, SingleId::MAX)
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_valid(&self) -> bool {
|
||||
self.inner.0.is_valid() && self.inner.1.is_valid() && (u32::from(self.clone()) < Self::SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DoubleId {
|
||||
|
|
@ -56,20 +53,25 @@ impl FromStr for DoubleId {
|
|||
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))
|
||||
}
|
||||
let delimiter = s[3..4].chars().next().unwrap();
|
||||
if is_delimiter(delimiter) {
|
||||
Ok((SingleId::from_str(&s[0..3])?,SingleId::from_str(&s[4..7])?))
|
||||
} else {
|
||||
Err(Error::InvalidDelimiter{
|
||||
found: vec![delimiter],
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
6 => {
|
||||
Ok((SingleId::from_str(&s[0..3])?,SingleId::from_str(&s[3..6])?))
|
||||
}
|
||||
_ => Err(Error::InvalidLength(s.to_string()))
|
||||
x => Err(Error::InvalidLength{
|
||||
expected: (6, 7),
|
||||
found: x,
|
||||
raw: s.to_string()
|
||||
})
|
||||
}?
|
||||
})
|
||||
}
|
||||
|
|
@ -95,7 +97,10 @@ impl TryFrom<u32> for DoubleId {
|
|||
SingleId::try_from(u16::try_from(value % (SingleId::SIZE as u32)).unwrap())?
|
||||
)})
|
||||
} else {
|
||||
Err(Error::OutsideOfRange(value as u64))
|
||||
Err(Error::OutsideOfRange{
|
||||
expected: Self::SIZE as usize,
|
||||
found: value as usize
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,9 +119,10 @@ mod tests {
|
|||
where
|
||||
R: Rng
|
||||
{
|
||||
let chunk: SingleId = rand.r#gen();
|
||||
let s = chunk.to_string();
|
||||
assert_eq!(chunk,SingleId::from_str(&s).unwrap())
|
||||
let id: DoubleId = rand.r#gen();
|
||||
assert!(id.is_valid());
|
||||
assert_eq!(id,DoubleId::from_str(&id.to_string()).unwrap());
|
||||
assert_eq!(id, DoubleId::try_from(u32::from(id.clone())).unwrap())
|
||||
}
|
||||
#[test]
|
||||
fn random_x10() {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,28 @@
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Outside of range: {0}")]
|
||||
OutsideOfRange(u64),
|
||||
#[error("expected under {expected}, found {found}")]
|
||||
OutsideOfRange{
|
||||
expected: usize,
|
||||
found: usize,
|
||||
},
|
||||
#[error("Invalid chunk: {0}")]
|
||||
InvalidChunk(String),
|
||||
#[error("Invalid delemeter: {0}")]
|
||||
InvalidDelimiter(char),
|
||||
#[error("Invalid length: {0}")]
|
||||
InvalidLength(String)
|
||||
#[error("Length of id expected {} or {} but found {found}: {raw}", .expected.0, .expected.1 )]
|
||||
InvalidLength{
|
||||
expected: (u8, u8),
|
||||
found: usize,
|
||||
raw: String
|
||||
},
|
||||
#[error("Number of chunks expected {expected} but {found}: {raw}")]
|
||||
InvalidLengthOfChunks{
|
||||
expected: u8,
|
||||
found: usize,
|
||||
raw: String,
|
||||
},
|
||||
#[error("Delimiter expected '-' or '_' but '{found:?}' found: {raw}")]
|
||||
InvalidDelimiter{
|
||||
found: Vec<char>,
|
||||
raw: String,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod single;
|
|||
mod double;
|
||||
mod error;
|
||||
mod triple;
|
||||
mod utils;
|
||||
|
||||
use rand::Rng;
|
||||
pub use single::*;
|
||||
|
|
@ -9,7 +10,6 @@ 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);
|
||||
|
||||
|
|
@ -18,4 +18,7 @@ pub trait Id {
|
|||
const NIL: Self;
|
||||
const MAX: Self;
|
||||
const SIZE: Self::SizeType;
|
||||
}
|
||||
#[cfg(test)]
|
||||
fn is_valid(&self) -> bool;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,10 @@ fn str_to_u16(s: &str) -> Result<u16, Error> {
|
|||
}
|
||||
fn u16_to_string(int: u16) -> Result<String, Error> {
|
||||
if int >= CUBED_BASE {
|
||||
return Err(Error::OutsideOfRange(int as u64))
|
||||
return Err(Error::OutsideOfRange{
|
||||
expected: CUBED_BASE as usize,
|
||||
found: int as usize
|
||||
})
|
||||
}
|
||||
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()]);
|
||||
|
|
@ -103,12 +106,6 @@ 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;
|
||||
|
|
@ -135,8 +132,11 @@ impl Id for SingleId {
|
|||
const MAX: SingleId = SingleId{
|
||||
inner: Self::SIZE-1
|
||||
};
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_valid(&self) -> bool {
|
||||
self.inner < Self::SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SingleId {
|
||||
|
|
@ -170,7 +170,10 @@ impl TryFrom<u16> for SingleId {
|
|||
if value < Self::SIZE {
|
||||
Ok(Self{inner: value})
|
||||
} else {
|
||||
Err(Error::OutsideOfRange(value as u64))
|
||||
Err(Error::OutsideOfRange{
|
||||
expected: Self::SIZE as usize,
|
||||
found: value as usize
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,12 +195,11 @@ mod tests {
|
|||
R: Rng
|
||||
{
|
||||
let chunk: SingleId = rand.r#gen();
|
||||
assert!(chunk.validate());
|
||||
assert!(chunk.is_valid());
|
||||
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() {
|
||||
|
|
|
|||
141
id/src/triple.rs
141
id/src/triple.rs
|
|
@ -1,6 +1,145 @@
|
|||
use crate::{Error, SingleId};
|
||||
use crate::{utils::is_delimiter, DoubleId, Error, SingleId};
|
||||
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TripleId {
|
||||
inner: (SingleId, SingleId, SingleId)
|
||||
}
|
||||
|
||||
impl Id for TripleId{
|
||||
type SizeType = u64;
|
||||
const SIZE: Self::SizeType = (SingleId::SIZE as u64).pow(3);
|
||||
/// ```
|
||||
/// use caretta_id::{Id, TripleId};
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// assert_eq!(TripleId::NIL, TripleId::from_str("000-000-000").unwrap());
|
||||
/// assert_eq!(TripleId::NIL, TripleId::try_from(0).unwrap());
|
||||
/// ```
|
||||
const NIL: Self = Self{
|
||||
inner: (SingleId::NIL, SingleId::NIL, SingleId::NIL)
|
||||
};
|
||||
|
||||
/// ```
|
||||
/// use caretta_id::{Id, TripleId};
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// assert_eq!(TripleId::MAX, TripleId::from_str("zzz-zzz-zzz").unwrap());
|
||||
/// assert_eq!(TripleId::MAX, TripleId::try_from(46411484401952).unwrap());
|
||||
/// ```
|
||||
const MAX: Self = Self{
|
||||
inner: (SingleId::MAX, SingleId::MAX, SingleId::MAX)
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_valid(&self) -> bool {
|
||||
self.inner.0.is_valid() && self.inner.1.is_valid() && self.inner.2.is_valid() && (u64::from(self.clone()) < Self::SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TripleId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}-{}-{}", self.inner.0, self.inner.1, self.inner.2)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TripleId {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self {
|
||||
inner : match s.len() {
|
||||
11 => {
|
||||
let delimiter = [
|
||||
s[3..4].chars().next().unwrap(),
|
||||
s[7..8].chars().next().unwrap(),
|
||||
];
|
||||
if is_delimiter(delimiter[0]) && is_delimiter(delimiter[1]) {
|
||||
Ok((SingleId::from_str(&s[0..3])?,SingleId::from_str(&s[4..7])?,SingleId::from_str(&s[8..11])?))
|
||||
} else {
|
||||
Err(Error::InvalidDelimiter{
|
||||
found: Vec::from(delimiter),
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
9 => {
|
||||
Ok((SingleId::from_str(&s[0..3])?,SingleId::from_str(&s[3..6])?,SingleId::from_str(&s[6..9])?))
|
||||
}
|
||||
x => {
|
||||
Err(Self::Err::InvalidLength{
|
||||
expected: (9, 11),
|
||||
found: x,
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
} ?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Distribution<TripleId> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TripleId {
|
||||
TripleId {
|
||||
inner: (rng.r#gen(), rng.r#gen(), rng.r#gen())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for TripleId {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
if value < Self::SIZE {
|
||||
Ok(Self{
|
||||
inner: (
|
||||
SingleId::try_from(u16::try_from(value / (DoubleId::SIZE as u64)).unwrap())?,
|
||||
SingleId::try_from(u16::try_from((value % (DoubleId::SIZE as u64)) /(SingleId::SIZE as u64)).unwrap())?,
|
||||
SingleId::try_from(u16::try_from(value % (SingleId::SIZE as u64)).unwrap())?
|
||||
)})
|
||||
} else {
|
||||
Err(Error::OutsideOfRange{
|
||||
expected: Self::SIZE as usize,
|
||||
found: value as usize
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TripleId> for u64 {
|
||||
fn from(value: TripleId) -> Self {
|
||||
(u16::from(value.inner.0) as u64) * (DoubleId::SIZE as u64)
|
||||
+ (u16::from(value.inner.1) as u64) * (SingleId::SIZE as u64)
|
||||
+ (u16::from(value.inner.2) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_random<R>(rand: &mut R)
|
||||
where
|
||||
R: Rng
|
||||
{
|
||||
let id: TripleId = rand.r#gen();
|
||||
assert!(id.is_valid());
|
||||
assert_eq!(id, TripleId::from_str(&id.to_string()).unwrap());
|
||||
assert_eq!(id, TripleId::try_from(u64::from(id.clone())).unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn random_x10() {
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..10 {
|
||||
assert_random(&mut rng);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
id/src/utils.rs
Normal file
11
id/src/utils.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::SingleId;
|
||||
|
||||
|
||||
pub fn is_delimiter(c: char) -> bool {
|
||||
match c {
|
||||
'-' | '_' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue