Implement DoubleId
This commit is contained in:
parent
99fdb12712
commit
6fb909cd07
8 changed files with 367 additions and 184 deletions
|
|
@ -1,3 +0,0 @@
|
||||||
pub struct IdBuilder {
|
|
||||||
|
|
||||||
}
|
|
||||||
174
id/src/chunk.rs
174
id/src/chunk.rs
|
|
@ -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<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 {
|
|
||||||
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<R: Rng + ?Sized>(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<Self, Self::Err> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u16> for IdChunk {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<IdChunk> 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<R>(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
pub struct Config {
|
|
||||||
pub enable_nil: bool,
|
|
||||||
pub enable_max: bool,
|
|
||||||
}
|
|
||||||
129
id/src/double.rs
129
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<Self, Self::Err> {
|
||||||
|
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<DoubleId> for Standard {
|
||||||
|
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DoubleId {
|
||||||
|
DoubleId {
|
||||||
|
inner: (rng.r#gen(), rng.r#gen())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for DoubleId {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
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<DoubleId> 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<R>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Outside of range: {0}")]
|
#[error("Outside of range: {0}")]
|
||||||
OutsideOfRange(u16),
|
OutsideOfRange(u64),
|
||||||
#[error("Invalid chunk: {0}")]
|
#[error("Invalid chunk: {0}")]
|
||||||
InvalidChunk(String),
|
InvalidChunk(String),
|
||||||
|
#[error("Invalid delemeter: {0}")]
|
||||||
|
InvalidDelimiter(char),
|
||||||
|
#[error("Invalid length: {0}")]
|
||||||
|
InvalidLength(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,21 @@
|
||||||
mod chunk;
|
mod single;
|
||||||
mod double;
|
mod double;
|
||||||
mod error;
|
mod error;
|
||||||
mod single;
|
|
||||||
mod triple;
|
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;
|
||||||
|
}
|
||||||
210
id/src/single.rs
210
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<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 {
|
||||||
|
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<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 {
|
||||||
|
Err(Error::OutsideOfRange(value as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SingleId> for u16 {
|
||||||
|
fn from(value: SingleId) -> Self {
|
||||||
|
value.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn assert_random<R>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
use crate::{Error, SingleId};
|
||||||
|
|
||||||
|
pub struct TripleId {
|
||||||
|
inner: (SingleId, SingleId, SingleId)
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue