diff --git a/tripod-id/Cargo.toml b/tripod-id/Cargo.toml index 334fcc9..7b2055d 100644 --- a/tripod-id/Cargo.toml +++ b/tripod-id/Cargo.toml @@ -22,3 +22,6 @@ thiserror.workspace = true [build-dependencies] prost-build = {version = "0.14.1", optional = true} + +[dev-dependencies] +serde_test = "1.0.177" diff --git a/tripod-id/src/double.rs b/tripod-id/src/double.rs index 4723ece..bf59ec1 100644 --- a/tripod-id/src/double.rs +++ b/tripod-id/src/double.rs @@ -2,47 +2,49 @@ use std::{fmt::Display, str::FromStr}; use rand::{distributions::Standard, prelude::Distribution, Rng}; +#[cfg(feature="prost")] +use crate::DoubleMessage; use crate::{utils::is_delimiter, Error, TripodId, Single}; -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Double{ - inner: (Single, Single) -} +#[derive(Copy, Clone, Debug, Hash, PartialEq)] +pub struct Double(u32); impl TripodId for Double{ + type Tuple = (Single, Single); type Integer = u32; + #[cfg(feature="prost")] + type Message = DoubleMessage; const CAPACITY: Self::Integer = (Single::CAPACITY as u32).pow(2); - /// ``` - /// use tripod_id::{TripodId, Double}; - /// use std::str::FromStr; - /// - /// assert_eq!(Double::NIL, Double::from_str("000-000").unwrap()); - /// assert_eq!(Double::NIL, Double::try_from(0).unwrap()); - /// ``` - const NIL: Self = Self{ - inner: (Single::NIL, Single::NIL) - }; - /// ``` - /// use tripod_id::{TripodId, Double}; - /// use std::str::FromStr; - /// - /// assert_eq!(Double::MAX, Double::from_str("zzz-zzz").unwrap()); - /// assert_eq!(Double::MAX, Double::try_from(1291467968).unwrap()); - /// ``` - const MAX: Self = Self{ - inner: (Single::MAX, Single::MAX) - }; + const NIL: Self = Self(0); + + const MAX: Self = Self(Self::CAPACITY -1); #[cfg(test)] fn validate_inner(self) -> bool { - self.inner.0.validate_inner() && self.inner.1.validate_inner() && (u32::from(self) < Self::CAPACITY) + self.0 < Self::CAPACITY } } impl Display for Double { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}-{}", self.inner.0, self.inner.1) + let tuple: (Single, Single) = (*self).into(); + write!(f, "{}-{}", tuple.0, tuple.1) + } +} + +impl From<(Single, Single)> for Double { + fn from(value: (Single, Single)) -> Self { + Self(u32::from(u16::from(value.0)) * u32::from(Single::CAPACITY) + u32::from(u16::from(value.1))) + } +} + +impl From for (Single, Single) { + fn from(value: Double) -> Self { + ( + Single::try_from(u16::try_from(value.0/(Single::CAPACITY as u32)).unwrap()).unwrap(), + Single::try_from(u16::try_from(value.0 % (Single::CAPACITY as u32)).unwrap()).unwrap() + ) } } @@ -50,39 +52,39 @@ impl FromStr for Double { type Err = Error; fn from_str(s: &str) -> Result { - Ok(Self { - inner : match s.len() { - 7 => { - let delimiter = s[3..4].chars().next().unwrap(); - if is_delimiter(delimiter) { - Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?)) - } else { - Err(Error::InvalidDelimiter{ - found: vec![delimiter], - raw: s.to_string() - }) - } - + let tuple = match s.len() { + 7 => { + let delimiter = s[3..4].chars().next().unwrap(); + if is_delimiter(delimiter) { + Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?)) + } else { + Err(Error::InvalidDelimiter{ + found: vec![delimiter], + raw: s.to_string() + }) } - 6 => { - Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?)) - } - x => Err(Error::InvalidLength{ + + }, + 6 => { + Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?)) + }, + x => { + Err(Error::InvalidLength{ expected: (6, 7), found: x, raw: s.to_string() }) - }? - }) + } + }?; + Ok(Self::from(tuple)) } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Double { - Double { - inner: (rng.r#gen(), rng.r#gen()) - } + Double(rng.gen_range(0..Double::CAPACITY)) + } } @@ -91,11 +93,7 @@ impl TryFrom for Double { fn try_from(value: u32) -> Result { if value < Self::CAPACITY { - Ok(Self{ - inner: ( - Single::try_from(u16::try_from(value/(Single::CAPACITY as u32)).unwrap())?, - Single::try_from(u16::try_from(value % (Single::CAPACITY as u32)).unwrap())? - )}) + Ok(Self(value)) } else { Err(Error::OutsideOfRange{ expected: Self::CAPACITY as u64, @@ -107,7 +105,7 @@ impl TryFrom for Double { impl From for u32 { fn from(value: Double) -> Self { - u32::from(u16::from(value.inner.0)) * u32::from(Single::CAPACITY) + u32::from(u16::from(value.inner.1)) + value.0 } } @@ -130,7 +128,6 @@ impl PartialEq for Double { #[cfg(test)] mod tests { -use crate::tests::{assert_id_eq_int, assert_id_eq_str}; use super::*; #[test] @@ -139,6 +136,8 @@ use crate::tests::{assert_id_eq_int, assert_id_eq_str}; assert_eq!(Double::NIL, 0); assert_eq!(Double::NIL, "000000".to_string()); assert_eq!(Double::NIL, "000-000".to_string()); + assert!(Double::NIL.is_nil()); + assert!(!Double::NIL.is_max()); } @@ -148,6 +147,8 @@ use crate::tests::{assert_id_eq_int, assert_id_eq_str}; assert_eq!(Double::MAX, Double::CAPACITY-1); assert_eq!(Double::MAX, "zzzzzz".to_string()); assert_eq!(Double::MAX, "ZZZ-ZZZ".to_string()); + assert!(Double::MAX.is_max()); + assert!(!Double::MAX.is_nil()); } #[test] diff --git a/tripod-id/src/lib.rs b/tripod-id/src/lib.rs index 9d2d5ee..b986e5e 100644 --- a/tripod-id/src/lib.rs +++ b/tripod-id/src/lib.rs @@ -8,7 +8,7 @@ mod rusqlite; #[cfg(feature="serde")] mod serde; -use std::{fmt::Display, str::FromStr}; +use std::{fmt::Display, ops::Sub, str::FromStr}; pub use single::*; pub use double::*; @@ -18,16 +18,83 @@ pub use error::*; #[cfg(feature="prost")] pub mod prost; #[cfg(feature="prost")] -pub use prost::{ SingleMessage, DoubleMessage, TripleMessage }; +pub use prost::{ SingleMessage, DoubleMessage, TripleMessage ,TripodIdMessage}; -pub trait TripodId: Copy + Display + TryFrom + FromStr + PartialEq { - type Integer: From; +/// The main trait for the tripod id +pub trait TripodId: Copy + Display + TryFrom + From + FromStr + PartialEq + PartialEq { + + /// An associated integer type. + /// This type is used to store the actual value of id. + type Integer: From + Sub; + + /// An associated tuple type containing SingleId. + /// This type is used to represent the id as the tuple of SingleId. + type Tuple: From; + + /// An associated protobuf message type. + /// This type is used for conversion between the protobuf message. + #[cfg(feature="prost")] + type Message: From + TryInto; + + /// The nil Tripod ID. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// # use tripod_id::{TripodId, Single}; + /// let id = Single::NIL; + /// + /// assert_eq!(id, 0); + /// assert_eq!(id, "000".to_string()); + /// ``` const NIL: Self; + + + /// The max Tripod ID. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// # use tripod_id::{TripodId, Double}; + /// let id = Double::MAX; + /// + /// assert_eq!(id, Double::CAPACITY - 1); + /// assert_eq!(id, "ZZZ-ZZZ".to_string()) + /// ``` const MAX: Self; + + /// The capacity of the Tripod ID. const CAPACITY: Self::Integer; + + /// Test if the Tripod ID is nil (=0). + fn is_nil(self) -> bool { + self == Self::NIL + } + + /// Test if the id is max(=Self::CAPACITY-1) + fn is_max(self) -> bool { + self == Self::MAX + } + + /// Validate the internal value has not reached capacity. + /// Fundamentally, the internal value are private, and unvalidated value should not be enterd, + /// so this function is only for testing purpose. #[cfg(test)] fn validate_inner(self) -> bool; + #[cfg(test)] + fn validate_parse_strings(self, strings: &[&str]) -> Result { + let mut result: bool = true; + for string in strings { + result = result && (self == string.to_string()) + } + Ok(result) + } + #[cfg(test)] fn validate_string_convertion(self) -> Result { Ok(self == Self::from_str(&self.to_string())?) @@ -37,46 +104,25 @@ pub trait TripodId: Copy + Display + TryFrom + FromS fn validate_integer_conversion(self) -> Result { Ok(self == Self::try_from(Self::Integer::from(self))?) } + #[cfg(test)] + fn validate_tuple_conversion(self) -> bool { + self == Self::from(Self::Tuple::from(self)) + } + #[cfg(all(test, feature="prost"))] + fn validate_message_conversion(self) -> Result { + Ok(self == Self::Message::from(self).try_into()?) + } #[cfg(test)] fn validate_all(self) -> Result { - Ok(self.validate_inner() + let mut result = self.validate_inner() && self.validate_string_convertion()? - && self.validate_integer_conversion()? - ) + && self.validate_integer_conversion()?; + #[cfg(feature="prost")] + { + result = result && self.validate_message_conversion()?; + } + + Ok(result) } } -#[cfg(test)] -mod tests { - use std::{fmt::Display, fmt::Debug, str::FromStr}; - - #[cfg(feature="prost")] -use crate::prost::TrypodIdMessage; - - use super::*; - - #[cfg(feature="prost")] - pub fn assert_valid_message(id: &T) where - T: TripodId + Debug + Display + FromStr + PartialEq + TryFrom + Copy, - M: TrypodIdMessage + From - { - let message = M::from(*id); - assert!(message.is_valid()); - assert_eq!(*id, T::try_from(message).unwrap()); - } - - pub fn assert_id_eq_int (id: T, int: I ) where - T: TripodId + Debug + PartialEq + TryFrom + Copy, - I: From + PartialEq + Debug + Copy - { - assert_eq!(id, T::try_from(int).unwrap()); - assert_eq!(I::from(id), int); - } - - pub fn assert_id_eq_str (id: T, code: &str ) where - T: TripodId + Debug + Display + FromStr + PartialEq + Copy, - { - assert_eq!(id, T::from_str(code).unwrap()); - } - -} \ No newline at end of file diff --git a/tripod-id/src/prost/double.rs b/tripod-id/src/prost/double.rs index 06f977a..8e34f0b 100644 --- a/tripod-id/src/prost/double.rs +++ b/tripod-id/src/prost/double.rs @@ -1,19 +1,14 @@ use prost::Name; -use crate::{prost::Double, Error, TripodId}; +use crate::{prost::{Double, TripodIdMessage}, Error, TripodId}; impl Name for Double { const NAME: &'static str = "Double"; const PACKAGE: &'static str = super::PACKAGE_NAME; } -impl Double { - #[cfg(test)] - pub fn is_valid(&self) -> bool { - use crate::TripodId; - - self.id < u32::from(crate::Double::CAPACITY) - } +impl TripodIdMessage for Double { + type TripodId = crate::Double; } impl From for Double { diff --git a/tripod-id/src/prost/mod.rs b/tripod-id/src/prost/mod.rs index 81fe490..faacd6b 100644 --- a/tripod-id/src/prost/mod.rs +++ b/tripod-id/src/prost/mod.rs @@ -7,14 +7,17 @@ mod double; mod triple; pub use generated::*; + +use crate::TripodId; const PACKAGE_NAME: &'static str = "fireturtle.tripod_id"; pub type SingleMessage = Single; pub type DoubleMessage = Double; pub type TripleMessage = Triple; -pub trait TrypodIdMessage: From { - type TrypodId: crate::TripodId + TryFrom; +pub trait TripodIdMessage: From { + type TripodId: TripodId + TryFrom; - #[cfg(test)] - fn is_valid(&self) -> bool; + fn is_valid(self) -> bool { + Self::TripodId::try_from(self).is_ok() + } } \ No newline at end of file diff --git a/tripod-id/src/prost/single.rs b/tripod-id/src/prost/single.rs index 5aa618c..ccfa393 100644 --- a/tripod-id/src/prost/single.rs +++ b/tripod-id/src/prost/single.rs @@ -1,19 +1,15 @@ use prost::Name; -use crate::{prost::Single, Error, TripodId}; +use crate::{prost::{Single, TripodIdMessage}, Error, TripodId}; impl Name for Single { const NAME: &'static str = "Single"; const PACKAGE: &'static str = super::PACKAGE_NAME; } -impl Single { - #[cfg(test)] - pub fn is_valid(&self) -> bool { - use crate::TripodId; +impl TripodIdMessage for Single { + type TripodId = crate::Single; - self.id < u32::from(crate::Single::CAPACITY) - } } impl From for Single { @@ -35,3 +31,27 @@ impl TryFrom for crate::Single { ) } } + +#[cfg(test)] +mod tests { + use crate::{Single, SingleMessage, TripodId}; + + #[test] + fn nil() { + let nil = SingleMessage{id: 0}; + assert_eq!(Single::NIL, Single::try_from(nil).unwrap()); + } + + #[test] + fn max() { + let max = SingleMessage{id: u32::from(Single::CAPACITY)-1}; + assert_eq!(Single::MAX, Single::try_from(max).unwrap()); + } + + #[test] + #[should_panic] + fn oversized () { + let oversized = SingleMessage{id: u32::from(Single::CAPACITY)}; + let _ = Single::try_from(oversized).unwrap(); + } +} \ No newline at end of file diff --git a/tripod-id/src/prost/triple.rs b/tripod-id/src/prost/triple.rs index f807c48..9da6fc6 100644 --- a/tripod-id/src/prost/triple.rs +++ b/tripod-id/src/prost/triple.rs @@ -1,19 +1,14 @@ use prost::Name; -use crate::{prost::Triple, Error, TripodId}; +use crate::{prost::{Triple, TripodIdMessage}, Error, TripodId}; impl Name for Triple { const NAME: &'static str = "Triple"; const PACKAGE: &'static str = super::PACKAGE_NAME; } -impl Triple { - #[cfg(test)] - pub fn is_valid(&self) -> bool { - use crate::TripodId; - - self.id < crate::Triple::CAPACITY - } +impl TripodIdMessage for Triple{ + type TripodId = crate::Triple; } impl From for Triple { diff --git a/tripod-id/src/serde.rs b/tripod-id/src/serde.rs index e69de29..4d01ed1 100644 --- a/tripod-id/src/serde.rs +++ b/tripod-id/src/serde.rs @@ -0,0 +1,78 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize, de::Error}; + +use crate::{Double, Single, Triple}; + +impl Serialize for Single { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Single { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + let s = String::deserialize(deserializer)?; + Single::from_str(&s).map_err(|e| D::Error::custom(e)) + } +} + +impl Serialize for Double { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Double { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + let s = String::deserialize(deserializer)?; + Double::from_str(&s).map_err(|e| D::Error::custom(e)) + } +} + +impl Serialize for Triple { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Triple { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + let s = String::deserialize(deserializer)?; + Triple::from_str(&s).map_err(|e| D::Error::custom(e)) + } +} + +#[cfg(test)] +mod tests { + use serde_test::{assert_tokens, Token}; + + use crate::TripodId; + + + #[test] + fn single() { + assert_tokens(&crate::Single::NIL, &[Token::Str("000")]); + } + + #[test] + fn double() { + assert_tokens(&crate::Double::NIL, &[Token::Str("000-000")]); + } + #[test] + fn triple() { + assert_tokens(&crate::Triple::NIL, &[Token::Str("000-000-000")]); + } +} \ No newline at end of file diff --git a/tripod-id/src/single.rs b/tripod-id/src/single.rs index bac434f..39587d6 100644 --- a/tripod-id/src/single.rs +++ b/tripod-id/src/single.rs @@ -2,6 +2,8 @@ use std::{fmt::Display, str::FromStr}; use rand::{distributions::Standard, prelude::Distribution, Rng}; +#[cfg(feature="prost")] +use crate::SingleMessage; use crate::{error::Error, TripodId}; const CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz"; @@ -101,41 +103,41 @@ fn u16_to_string(int: u16) -> Result { Ok(format!("{}{}{}", first_char, second_char, third_char)) } -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct Single{ - inner: u16 -} +/// Single size tripod id. +/// +/// # Examples +/// +/// ``` +/// use std::str::FromStr; +/// use tripod_id::{TripodId,Single}; +/// +/// assert_eq!(Single::from_str("012").unwrap(), Single::try_from(35).unwrap()); +/// ``` +#[derive(Copy, Clone, Debug, Hash, PartialEq)] +pub struct Single(u16); impl TripodId for Single { type Integer = u16; + type Tuple = (Single,); + #[cfg(feature="prost")] + type Message = SingleMessage; + const CAPACITY: Self::Integer = CUBED_BASE; + const NIL: Single = Single(0); - const NIL: Single = Single{ - inner: 0 - }; - - /// ``` - /// use tripod_id::{TripodId, Single}; - /// use std::str::FromStr; - /// - /// assert_eq!(Single::MAX, Single::from_str("zzz").unwrap()); - /// assert_eq!(Single::MAX, Single::try_from(35936).unwrap()); - /// ``` - const MAX: Single = Single{ - inner: Self::CAPACITY-1 - }; + const MAX: Single = Single(Self::CAPACITY-1); #[cfg(test)] fn validate_inner(self) -> bool { - self.inner < Self::CAPACITY + self.0 < Self::CAPACITY } } impl Display for Single { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", u16_to_string(self.inner).unwrap()) + write!(f, "{}", u16_to_string(self.0).unwrap()) } } @@ -143,17 +145,13 @@ impl FromStr for Single { type Err = Error; fn from_str(s: &str) -> Result { - Ok(Self{ - inner: str_to_u16(s)? - }) + Ok(Self(str_to_u16(s)?)) } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Single { - Single { - inner: rng.gen_range(0..Single::CAPACITY) - } + Single(rng.gen_range(0..Single::CAPACITY)) } } @@ -162,7 +160,7 @@ impl TryFrom for Single { fn try_from(value: u16) -> Result { if value < Self::CAPACITY { - Ok(Self{inner: value}) + Ok(Self(value)) } else { Err(Error::OutsideOfRange{ expected: Self::CAPACITY as u64, @@ -174,7 +172,18 @@ impl TryFrom for Single { impl From for u16 { fn from(value: Single) -> Self { - value.inner + value.0 + } +} + +impl From<(Single,)> for Single { + fn from(value: (Single,)) -> Self { + value.0 + } +} +impl From for (Single,) { + fn from(value: Single) -> Self { + (value,) } } @@ -197,22 +206,23 @@ impl PartialEq for Single { #[cfg(test)] mod tests { - use crate::tests::{assert_id_eq_int, assert_id_eq_str}; - use super::*; #[test] fn nil() { assert!(Single::NIL.validate_all().unwrap()); assert_eq!(Single::NIL, 0); - assert_eq!(Single::NIL, "000".to_string()); + assert!(Single::NIL.validate_parse_strings(&["000"]).unwrap()); + assert!(Single::NIL.is_nil()); + assert!(!Single::NIL.is_max()) } #[test] fn max() { assert!(Single::MAX.validate_all().unwrap()); - assert_eq!(Single::MAX, Single::CAPACITY-1); - assert_eq!(Single::MAX, "zzz".to_string()); - assert_eq!(Single::MAX, "ZZZ".to_string()); + assert_eq!(Single::MAX, Single::CAPACITY - 1); + assert!(Single::MAX.validate_parse_strings(&["zzz", "ZZZ"]).unwrap()); + assert!(Single::MAX.is_max()); + assert!(!Single::MAX.is_nil()); } #[test] diff --git a/tripod-id/src/triple.rs b/tripod-id/src/triple.rs index 35644b2..3ce610d 100644 --- a/tripod-id/src/triple.rs +++ b/tripod-id/src/triple.rs @@ -1,3 +1,5 @@ +#[cfg(feature="prost")] +use crate::TripleMessage; use crate::{utils::is_delimiter, Double, Error, Single}; use std::{fmt::Display, str::FromStr}; @@ -6,48 +8,40 @@ use rand::{distributions::Standard, prelude::Distribution, Rng}; use crate::TripodId; +/// Triple length tripod id. +/// +/// # Examples +/// ``` +/// # use tripod_id::{TripodId, Triple}; +/// # use std::str::FromStr; +/// +/// let _ = tripod_id::from_str("123-abc"); +/// ``` #[derive(Copy, Clone, Debug, PartialEq)] -pub struct Triple { - inner: (Single, Single, Single) -} +pub struct Triple(u64); impl TripodId for Triple{ type Integer = u64; + type Tuple = (Single, Single, Single); + #[cfg(feature="prost")] + type Message = TripleMessage; const CAPACITY: Self::Integer = (Single::CAPACITY as u64).pow(3); - /// ``` - /// use tripod_id::{TripodId, Triple}; - /// use std::str::FromStr; - /// - /// assert_eq!(Triple::NIL, Triple::from_str("000-000-000").unwrap()); - /// assert_eq!(Triple::NIL, Triple::try_from(0).unwrap()); - /// ``` - const NIL: Self = Self{ - inner: (Single::NIL, Single::NIL, Single::NIL) - }; - /// ``` - /// use tripod_id::{TripodId, Triple}; - /// use std::str::FromStr; - /// - /// assert_eq!(Triple::MAX, Triple::from_str("zzz-zzz-zzz").unwrap()); - /// assert_eq!(Triple::MAX, Triple::try_from(46411484401952).unwrap()); - /// ``` - const MAX: Self = Self{ - inner: (Single::MAX, Single::MAX, Single::MAX) - }; + const NIL: Self = Self(0); + + const MAX: Self = Self(Self::CAPACITY - 1); #[cfg(test)] fn validate_inner(self) -> bool { - self.inner.0.validate_inner() - && self.inner.1.validate_inner() - && self.inner.2.validate_inner() - && (u64::from(self) < Self::CAPACITY) + self.0 < Self::CAPACITY } } impl Display for Triple { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}-{}-{}", self.inner.0, self.inner.1, self.inner.2) + + let tuple: (Single, Single, Single) = (*self).into(); + write!(f, "{}-{}-{}", tuple.0, tuple.1, tuple.2) } } @@ -55,15 +49,14 @@ impl FromStr for Triple { type Err = Error; fn from_str(s: &str) -> Result { - Ok(Self { - inner : match s.len() { + Ok(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((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?,Single::from_str(&s[8..11])?)) + Ok(Self::from((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?,Single::from_str(&s[8..11])?))) } else { Err(Error::InvalidDelimiter{ found: Vec::from(delimiter), @@ -73,7 +66,7 @@ impl FromStr for Triple { } 9 => { - Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?,Single::from_str(&s[6..9])?)) + Ok(Self::from((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?,Single::from_str(&s[6..9])?))) } x => { Err(Self::Err::InvalidLength{ @@ -83,16 +76,15 @@ impl FromStr for Triple { }) } } ? - }) + ) } } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Triple { - Triple { - inner: (rng.r#gen(), rng.r#gen(), rng.r#gen()) - } + Triple(rng.gen_range(0..Triple::CAPACITY)) + } } @@ -101,12 +93,7 @@ impl TryFrom for Triple { fn try_from(value: u64) -> Result { if value < Self::CAPACITY { - Ok(Self{ - inner: ( - Single::try_from(u16::try_from(value / (Double::CAPACITY as u64)).unwrap())?, - Single::try_from(u16::try_from((value % (Double::CAPACITY as u64)) /(Single::CAPACITY as u64)).unwrap())?, - Single::try_from(u16::try_from(value % (Single::CAPACITY as u64)).unwrap())? - )}) + Ok(Self(value)) } else { Err(Error::OutsideOfRange{ expected: Self::CAPACITY as u64, @@ -118,9 +105,27 @@ impl TryFrom for Triple { impl From for u64 { fn from(value: Triple) -> Self { - (u16::from(value.inner.0) as u64) * (Double::CAPACITY as u64) - + (u16::from(value.inner.1) as u64) * (Single::CAPACITY as u64) - + (u16::from(value.inner.2) as u64) + value.0 + } +} + +impl From<(Single, Single, Single)> for Triple { + fn from(value: (Single, Single, Single)) -> Self { + Self( + (u16::from(value.0) as u64) * (Double::CAPACITY as u64) + + (u16::from(value.1) as u64) * (Single::CAPACITY as u64) + + (u16::from(value.2) as u64) + ) + } +} + +impl From for (Single, Single, Single) { + fn from(value: Triple) -> Self { + ( + Single::try_from(u16::try_from(value.0 / (Double::CAPACITY as u64)).unwrap()).unwrap(), + Single::try_from(u16::try_from((value.0 % (Double::CAPACITY as u64)) /(Single::CAPACITY as u64)).unwrap()).unwrap(), + Single::try_from(u16::try_from(value.0 % (Single::CAPACITY as u64)).unwrap()).unwrap() + ) } } @@ -142,7 +147,6 @@ impl PartialEq for Triple { #[cfg(test)] mod tests { -use crate::tests::{assert_id_eq_int, assert_id_eq_str}; use super::*; #[test] @@ -160,6 +164,7 @@ use crate::tests::{assert_id_eq_int, assert_id_eq_str}; assert_eq!(Triple::MAX, Triple::CAPACITY-1); assert_eq!(Triple::MAX, "zzzzzzzzz".to_string()); assert_eq!(Triple::MAX, "ZZZ-ZZZ-ZZZ".to_string()); + assert_eq!((Single::MAX, Single::MAX, Single::MAX), Triple::MAX.into()) } #[test] diff --git a/tripod-id/src/utils.rs b/tripod-id/src/utils.rs index a24b908..99a0800 100644 --- a/tripod-id/src/utils.rs +++ b/tripod-id/src/utils.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use crate::Single; - +/// Test if the character is valid delimiter. pub fn is_delimiter(c: char) -> bool { match c { '-' | '_' => true,