2025-09-23 22:20:18 +09:00
|
|
|
use crate::{utils::is_delimiter, Double, Error, Single};
|
2025-09-13 08:39:06 +09:00
|
|
|
|
2025-09-13 12:29:59 +09:00
|
|
|
use std::{fmt::Display, str::FromStr};
|
|
|
|
|
|
|
|
|
|
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
use crate::TripodId;
|
2025-09-13 12:29:59 +09:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2025-09-23 22:20:18 +09:00
|
|
|
pub struct Triple {
|
|
|
|
|
inner: (Single, Single, Single)
|
2025-09-13 08:39:06 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
impl TripodId for Triple{
|
2025-09-13 12:29:59 +09:00
|
|
|
type SizeType = u64;
|
2025-09-23 22:20:18 +09:00
|
|
|
const SIZE: Self::SizeType = (Single::SIZE as u64).pow(3);
|
2025-09-13 12:29:59 +09:00
|
|
|
/// ```
|
2025-09-23 22:20:18 +09:00
|
|
|
/// use tripod_id::{TripodId, Triple};
|
2025-09-13 12:29:59 +09:00
|
|
|
/// use std::str::FromStr;
|
|
|
|
|
///
|
2025-09-23 22:20:18 +09:00
|
|
|
/// assert_eq!(Triple::NIL, Triple::from_str("000-000-000").unwrap());
|
|
|
|
|
/// assert_eq!(Triple::NIL, Triple::try_from(0).unwrap());
|
2025-09-13 12:29:59 +09:00
|
|
|
/// ```
|
|
|
|
|
const NIL: Self = Self{
|
2025-09-23 22:20:18 +09:00
|
|
|
inner: (Single::NIL, Single::NIL, Single::NIL)
|
2025-09-13 12:29:59 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// ```
|
2025-09-23 22:20:18 +09:00
|
|
|
/// use tripod_id::{TripodId, Triple};
|
2025-09-13 12:29:59 +09:00
|
|
|
/// use std::str::FromStr;
|
|
|
|
|
///
|
2025-09-23 22:20:18 +09:00
|
|
|
/// assert_eq!(Triple::MAX, Triple::from_str("zzz-zzz-zzz").unwrap());
|
|
|
|
|
/// assert_eq!(Triple::MAX, Triple::try_from(46411484401952).unwrap());
|
2025-09-13 12:29:59 +09:00
|
|
|
/// ```
|
|
|
|
|
const MAX: Self = Self{
|
2025-09-23 22:20:18 +09:00
|
|
|
inner: (Single::MAX, Single::MAX, Single::MAX)
|
2025-09-13 12:29:59 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn is_valid(&self) -> bool {
|
2025-09-15 17:05:30 +09:00
|
|
|
self.inner.0.is_valid() && self.inner.1.is_valid() && self.inner.2.is_valid() && (u64::from(self) < Self::SIZE)
|
2025-09-13 12:29:59 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
impl Display for Triple {
|
2025-09-13 12:29:59 +09:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}-{}-{}", self.inner.0, self.inner.1, self.inner.2)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
impl FromStr for Triple {
|
2025-09-13 12:29:59 +09:00
|
|
|
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]) {
|
2025-09-23 22:20:18 +09:00
|
|
|
Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?,Single::from_str(&s[8..11])?))
|
2025-09-13 12:29:59 +09:00
|
|
|
} else {
|
|
|
|
|
Err(Error::InvalidDelimiter{
|
|
|
|
|
found: Vec::from(delimiter),
|
|
|
|
|
raw: s.to_string()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
9 => {
|
2025-09-23 22:20:18 +09:00
|
|
|
Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?,Single::from_str(&s[6..9])?))
|
2025-09-13 12:29:59 +09:00
|
|
|
}
|
|
|
|
|
x => {
|
|
|
|
|
Err(Self::Err::InvalidLength{
|
|
|
|
|
expected: (9, 11),
|
|
|
|
|
found: x,
|
|
|
|
|
raw: s.to_string()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} ?
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
impl Distribution<Triple> for Standard {
|
|
|
|
|
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Triple {
|
|
|
|
|
Triple {
|
2025-09-13 12:29:59 +09:00
|
|
|
inner: (rng.r#gen(), rng.r#gen(), rng.r#gen())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
impl TryFrom<u64> for Triple {
|
2025-09-13 12:29:59 +09:00
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
|
|
|
|
if value < Self::SIZE {
|
|
|
|
|
Ok(Self{
|
|
|
|
|
inner: (
|
2025-09-23 22:20:18 +09:00
|
|
|
Single::try_from(u16::try_from(value / (Double::SIZE as u64)).unwrap())?,
|
|
|
|
|
Single::try_from(u16::try_from((value % (Double::SIZE as u64)) /(Single::SIZE as u64)).unwrap())?,
|
|
|
|
|
Single::try_from(u16::try_from(value % (Single::SIZE as u64)).unwrap())?
|
2025-09-13 12:29:59 +09:00
|
|
|
)})
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::OutsideOfRange{
|
|
|
|
|
expected: Self::SIZE as usize,
|
|
|
|
|
found: value as usize
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 22:20:18 +09:00
|
|
|
impl From<&Triple> for u64 {
|
|
|
|
|
fn from(value: &Triple) -> Self {
|
|
|
|
|
(u16::from(&value.inner.0) as u64) * (Double::SIZE as u64)
|
|
|
|
|
+ (u16::from(&value.inner.1) as u64) * (Single::SIZE as u64)
|
2025-09-15 17:05:30 +09:00
|
|
|
+ (u16::from(&value.inner.2) as u64)
|
2025-09-13 12:29:59 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
fn assert_random<R>(rand: &mut R)
|
|
|
|
|
where
|
|
|
|
|
R: Rng
|
|
|
|
|
{
|
2025-09-23 22:20:18 +09:00
|
|
|
let id: Triple = rand.r#gen();
|
2025-09-13 12:29:59 +09:00
|
|
|
assert!(id.is_valid());
|
2025-09-23 22:20:18 +09:00
|
|
|
assert_eq!(id, Triple::from_str(&id.to_string()).unwrap());
|
|
|
|
|
assert_eq!(id, Triple::try_from(u64::from(&id)).unwrap());
|
2025-09-13 12:29:59 +09:00
|
|
|
}
|
|
|
|
|
#[test]
|
|
|
|
|
fn random_x10() {
|
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
for _ in 0..10 {
|
|
|
|
|
assert_random(&mut rng);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|