Implement serde feature
This commit is contained in:
parent
65d189c990
commit
2a29f1fc82
11 changed files with 360 additions and 204 deletions
|
|
@ -22,3 +22,6 @@ thiserror.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = {version = "0.14.1", optional = true}
|
prost-build = {version = "0.14.1", optional = true}
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_test = "1.0.177"
|
||||||
|
|
|
||||||
|
|
@ -2,47 +2,49 @@ use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||||
|
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
use crate::DoubleMessage;
|
||||||
use crate::{utils::is_delimiter, Error, TripodId, Single};
|
use crate::{utils::is_delimiter, Error, TripodId, Single};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
|
||||||
pub struct Double{
|
pub struct Double(u32);
|
||||||
inner: (Single, Single)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TripodId for Double{
|
impl TripodId for Double{
|
||||||
|
type Tuple = (Single, Single);
|
||||||
type Integer = u32;
|
type Integer = u32;
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
type Message = DoubleMessage;
|
||||||
const CAPACITY: Self::Integer = (Single::CAPACITY as u32).pow(2);
|
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// ```
|
const NIL: Self = Self(0);
|
||||||
/// use tripod_id::{TripodId, Double};
|
|
||||||
/// use std::str::FromStr;
|
const MAX: Self = Self(Self::CAPACITY -1);
|
||||||
///
|
|
||||||
/// 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)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn validate_inner(self) -> bool {
|
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 {
|
impl Display for Double {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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<Double> 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,8 +52,7 @@ impl FromStr for Double {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(Self {
|
let tuple = match s.len() {
|
||||||
inner : match s.len() {
|
|
||||||
7 => {
|
7 => {
|
||||||
let delimiter = s[3..4].chars().next().unwrap();
|
let delimiter = s[3..4].chars().next().unwrap();
|
||||||
if is_delimiter(delimiter) {
|
if is_delimiter(delimiter) {
|
||||||
|
|
@ -63,26 +64,27 @@ impl FromStr for Double {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
},
|
||||||
6 => {
|
6 => {
|
||||||
Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?))
|
Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?))
|
||||||
}
|
},
|
||||||
x => Err(Error::InvalidLength{
|
x => {
|
||||||
|
Err(Error::InvalidLength{
|
||||||
expected: (6, 7),
|
expected: (6, 7),
|
||||||
found: x,
|
found: x,
|
||||||
raw: s.to_string()
|
raw: s.to_string()
|
||||||
})
|
})
|
||||||
}?
|
}
|
||||||
})
|
}?;
|
||||||
|
Ok(Self::from(tuple))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Distribution<Double> for Standard {
|
impl Distribution<Double> for Standard {
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Double {
|
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Double {
|
||||||
Double {
|
Double(rng.gen_range(0..Double::CAPACITY))
|
||||||
inner: (rng.r#gen(), rng.r#gen())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,11 +93,7 @@ impl TryFrom<u32> for Double {
|
||||||
|
|
||||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
if value < Self::CAPACITY {
|
if value < Self::CAPACITY {
|
||||||
Ok(Self{
|
Ok(Self(value))
|
||||||
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())?
|
|
||||||
)})
|
|
||||||
} else {
|
} else {
|
||||||
Err(Error::OutsideOfRange{
|
Err(Error::OutsideOfRange{
|
||||||
expected: Self::CAPACITY as u64,
|
expected: Self::CAPACITY as u64,
|
||||||
|
|
@ -107,7 +105,7 @@ impl TryFrom<u32> for Double {
|
||||||
|
|
||||||
impl From<Double> for u32 {
|
impl From<Double> for u32 {
|
||||||
fn from(value: Double) -> Self {
|
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<String> for Double {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{assert_id_eq_int, assert_id_eq_str};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[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, 0);
|
||||||
assert_eq!(Double::NIL, "000000".to_string());
|
assert_eq!(Double::NIL, "000000".to_string());
|
||||||
assert_eq!(Double::NIL, "000-000".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, Double::CAPACITY-1);
|
||||||
assert_eq!(Double::MAX, "zzzzzz".to_string());
|
assert_eq!(Double::MAX, "zzzzzz".to_string());
|
||||||
assert_eq!(Double::MAX, "ZZZ-ZZZ".to_string());
|
assert_eq!(Double::MAX, "ZZZ-ZZZ".to_string());
|
||||||
|
assert!(Double::MAX.is_max());
|
||||||
|
assert!(!Double::MAX.is_nil());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ mod rusqlite;
|
||||||
#[cfg(feature="serde")]
|
#[cfg(feature="serde")]
|
||||||
mod serde;
|
mod serde;
|
||||||
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, ops::Sub, str::FromStr};
|
||||||
|
|
||||||
pub use single::*;
|
pub use single::*;
|
||||||
pub use double::*;
|
pub use double::*;
|
||||||
|
|
@ -18,16 +18,83 @@ pub use error::*;
|
||||||
#[cfg(feature="prost")]
|
#[cfg(feature="prost")]
|
||||||
pub mod prost;
|
pub mod prost;
|
||||||
#[cfg(feature="prost")]
|
#[cfg(feature="prost")]
|
||||||
pub use prost::{ SingleMessage, DoubleMessage, TripleMessage };
|
pub use prost::{ SingleMessage, DoubleMessage, TripleMessage ,TripodIdMessage};
|
||||||
|
|
||||||
pub trait TripodId: Copy + Display + TryFrom<Self::Integer, Error=Error> + FromStr<Err=Error> + PartialEq {
|
/// The main trait for the tripod id
|
||||||
type Integer: From<Self>;
|
pub trait TripodId: Copy + Display + TryFrom<Self::Integer, Error=Error> + From<Self::Tuple> + FromStr<Err=Error> + PartialEq + PartialEq<String> {
|
||||||
|
|
||||||
|
/// An associated integer type.
|
||||||
|
/// This type is used to store the actual value of id.
|
||||||
|
type Integer: From<Self> + Sub;
|
||||||
|
|
||||||
|
/// An associated tuple type containing SingleId.
|
||||||
|
/// This type is used to represent the id as the tuple of SingleId.
|
||||||
|
type Tuple: From<Self>;
|
||||||
|
|
||||||
|
/// An associated protobuf message type.
|
||||||
|
/// This type is used for conversion between the protobuf message.
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
type Message: From<Self> + TryInto<Self, Error=Error>;
|
||||||
|
|
||||||
|
/// 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;
|
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;
|
const MAX: Self;
|
||||||
|
|
||||||
|
/// The capacity of the Tripod ID.
|
||||||
const CAPACITY: Self::Integer;
|
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)]
|
#[cfg(test)]
|
||||||
fn validate_inner(self) -> bool;
|
fn validate_inner(self) -> bool;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn validate_parse_strings(self, strings: &[&str]) -> Result<bool, Error> {
|
||||||
|
let mut result: bool = true;
|
||||||
|
for string in strings {
|
||||||
|
result = result && (self == string.to_string())
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn validate_string_convertion(self) -> Result<bool, Error> {
|
fn validate_string_convertion(self) -> Result<bool, Error> {
|
||||||
Ok(self == Self::from_str(&self.to_string())?)
|
Ok(self == Self::from_str(&self.to_string())?)
|
||||||
|
|
@ -37,46 +104,25 @@ pub trait TripodId: Copy + Display + TryFrom<Self::Integer, Error=Error> + FromS
|
||||||
fn validate_integer_conversion(self) -> Result<bool, Error> {
|
fn validate_integer_conversion(self) -> Result<bool, Error> {
|
||||||
Ok(self == Self::try_from(Self::Integer::from(self))?)
|
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<bool, Error> {
|
||||||
|
Ok(self == Self::Message::from(self).try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn validate_all(self) -> Result<bool, Error> {
|
fn validate_all(self) -> Result<bool, Error> {
|
||||||
Ok(self.validate_inner()
|
let mut result = self.validate_inner()
|
||||||
&& self.validate_string_convertion()?
|
&& self.validate_string_convertion()?
|
||||||
&& self.validate_integer_conversion()?
|
&& self.validate_integer_conversion()?;
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::{fmt::Display, fmt::Debug, str::FromStr};
|
|
||||||
|
|
||||||
#[cfg(feature="prost")]
|
#[cfg(feature="prost")]
|
||||||
use crate::prost::TrypodIdMessage;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[cfg(feature="prost")]
|
|
||||||
pub fn assert_valid_message<T, M>(id: &T) where
|
|
||||||
T: TripodId + Debug + Display + FromStr<Err=Error> + PartialEq + TryFrom<M, Error = Error> + Copy,
|
|
||||||
M: TrypodIdMessage<TrypodId = T> + From<T>
|
|
||||||
{
|
{
|
||||||
let message = M::from(*id);
|
result = result && self.validate_message_conversion()?;
|
||||||
assert!(message.is_valid());
|
|
||||||
assert_eq!(*id, T::try_from(message).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assert_id_eq_int<T, I> (id: T, int: I ) where
|
Ok(result)
|
||||||
T: TripodId<Integer = I> + Debug + PartialEq + TryFrom<I, Error = Error> + Copy,
|
|
||||||
I: From<T> + PartialEq + Debug + Copy
|
|
||||||
{
|
|
||||||
assert_eq!(id, T::try_from(int).unwrap());
|
|
||||||
assert_eq!(I::from(id), int);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assert_id_eq_str<T> (id: T, code: &str ) where
|
|
||||||
T: TripodId + Debug + Display + FromStr<Err=Error> + PartialEq + Copy,
|
|
||||||
{
|
|
||||||
assert_eq!(id, T::from_str(code).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
use prost::Name;
|
use prost::Name;
|
||||||
|
|
||||||
use crate::{prost::Double, Error, TripodId};
|
use crate::{prost::{Double, TripodIdMessage}, Error, TripodId};
|
||||||
|
|
||||||
impl Name for Double {
|
impl Name for Double {
|
||||||
const NAME: &'static str = "Double";
|
const NAME: &'static str = "Double";
|
||||||
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Double {
|
impl TripodIdMessage for Double {
|
||||||
#[cfg(test)]
|
type TripodId = crate::Double;
|
||||||
pub fn is_valid(&self) -> bool {
|
|
||||||
use crate::TripodId;
|
|
||||||
|
|
||||||
self.id < u32::from(crate::Double::CAPACITY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::Double> for Double {
|
impl From<crate::Double> for Double {
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,17 @@ mod double;
|
||||||
mod triple;
|
mod triple;
|
||||||
|
|
||||||
pub use generated::*;
|
pub use generated::*;
|
||||||
|
|
||||||
|
use crate::TripodId;
|
||||||
const PACKAGE_NAME: &'static str = "fireturtle.tripod_id";
|
const PACKAGE_NAME: &'static str = "fireturtle.tripod_id";
|
||||||
pub type SingleMessage = Single;
|
pub type SingleMessage = Single;
|
||||||
pub type DoubleMessage = Double;
|
pub type DoubleMessage = Double;
|
||||||
pub type TripleMessage = Triple;
|
pub type TripleMessage = Triple;
|
||||||
|
|
||||||
pub trait TrypodIdMessage: From<Self::TrypodId> {
|
pub trait TripodIdMessage: From<Self::TripodId> {
|
||||||
type TrypodId: crate::TripodId + TryFrom<Self>;
|
type TripodId: TripodId + TryFrom<Self>;
|
||||||
|
|
||||||
#[cfg(test)]
|
fn is_valid(self) -> bool {
|
||||||
fn is_valid(&self) -> bool;
|
Self::TripodId::try_from(self).is_ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
use prost::Name;
|
use prost::Name;
|
||||||
|
|
||||||
use crate::{prost::Single, Error, TripodId};
|
use crate::{prost::{Single, TripodIdMessage}, Error, TripodId};
|
||||||
|
|
||||||
impl Name for Single {
|
impl Name for Single {
|
||||||
const NAME: &'static str = "Single";
|
const NAME: &'static str = "Single";
|
||||||
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Single {
|
impl TripodIdMessage for Single {
|
||||||
#[cfg(test)]
|
type TripodId = crate::Single;
|
||||||
pub fn is_valid(&self) -> bool {
|
|
||||||
use crate::TripodId;
|
|
||||||
|
|
||||||
self.id < u32::from(crate::Single::CAPACITY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::Single> for Single {
|
impl From<crate::Single> for Single {
|
||||||
|
|
@ -35,3 +31,27 @@ impl TryFrom<Single> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
use prost::Name;
|
use prost::Name;
|
||||||
|
|
||||||
use crate::{prost::Triple, Error, TripodId};
|
use crate::{prost::{Triple, TripodIdMessage}, Error, TripodId};
|
||||||
|
|
||||||
impl Name for Triple {
|
impl Name for Triple {
|
||||||
const NAME: &'static str = "Triple";
|
const NAME: &'static str = "Triple";
|
||||||
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Triple {
|
impl TripodIdMessage for Triple{
|
||||||
#[cfg(test)]
|
type TripodId = crate::Triple;
|
||||||
pub fn is_valid(&self) -> bool {
|
|
||||||
use crate::TripodId;
|
|
||||||
|
|
||||||
self.id < crate::Triple::CAPACITY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::Triple> for Triple {
|
impl From<crate::Triple> for Triple {
|
||||||
|
|
|
||||||
|
|
@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer {
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Single {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer {
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Double {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer {
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Triple {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
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")]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||||
|
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
use crate::SingleMessage;
|
||||||
use crate::{error::Error, TripodId};
|
use crate::{error::Error, TripodId};
|
||||||
|
|
||||||
const CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz";
|
const CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz";
|
||||||
|
|
@ -101,41 +103,41 @@ fn u16_to_string(int: u16) -> Result<String, Error> {
|
||||||
Ok(format!("{}{}{}", first_char, second_char, third_char))
|
Ok(format!("{}{}{}", first_char, second_char, third_char))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
/// Single size tripod id.
|
||||||
pub struct Single{
|
///
|
||||||
inner: u16
|
/// # 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 {
|
impl TripodId for Single {
|
||||||
type Integer = u16;
|
type Integer = u16;
|
||||||
|
type Tuple = (Single,);
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
type Message = SingleMessage;
|
||||||
|
|
||||||
const CAPACITY: Self::Integer = CUBED_BASE;
|
const CAPACITY: Self::Integer = CUBED_BASE;
|
||||||
|
|
||||||
|
const NIL: Single = Single(0);
|
||||||
|
|
||||||
const NIL: Single = Single{
|
const MAX: Single = Single(Self::CAPACITY-1);
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn validate_inner(self) -> bool {
|
fn validate_inner(self) -> bool {
|
||||||
self.inner < Self::CAPACITY
|
self.0 < Self::CAPACITY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Single {
|
impl Display for Single {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(Self{
|
Ok(Self(str_to_u16(s)?))
|
||||||
inner: str_to_u16(s)?
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Distribution<Single> for Standard {
|
impl Distribution<Single> for Standard {
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Single {
|
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Single {
|
||||||
Single {
|
Single(rng.gen_range(0..Single::CAPACITY))
|
||||||
inner: rng.gen_range(0..Single::CAPACITY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +160,7 @@ impl TryFrom<u16> for Single {
|
||||||
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
if value < Self::CAPACITY {
|
if value < Self::CAPACITY {
|
||||||
Ok(Self{inner: value})
|
Ok(Self(value))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::OutsideOfRange{
|
Err(Error::OutsideOfRange{
|
||||||
expected: Self::CAPACITY as u64,
|
expected: Self::CAPACITY as u64,
|
||||||
|
|
@ -174,7 +172,18 @@ impl TryFrom<u16> for Single {
|
||||||
|
|
||||||
impl From<Single> for u16 {
|
impl From<Single> for u16 {
|
||||||
fn from(value: Single) -> Self {
|
fn from(value: Single) -> Self {
|
||||||
value.inner
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(Single,)> for Single {
|
||||||
|
fn from(value: (Single,)) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Single> for (Single,) {
|
||||||
|
fn from(value: Single) -> Self {
|
||||||
|
(value,)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,22 +206,23 @@ impl PartialEq<String> for Single {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{assert_id_eq_int, assert_id_eq_str};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn nil() {
|
fn nil() {
|
||||||
assert!(Single::NIL.validate_all().unwrap());
|
assert!(Single::NIL.validate_all().unwrap());
|
||||||
assert_eq!(Single::NIL, 0);
|
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]
|
#[test]
|
||||||
fn max() {
|
fn max() {
|
||||||
assert!(Single::MAX.validate_all().unwrap());
|
assert!(Single::MAX.validate_all().unwrap());
|
||||||
assert_eq!(Single::MAX, Single::CAPACITY - 1);
|
assert_eq!(Single::MAX, Single::CAPACITY - 1);
|
||||||
assert_eq!(Single::MAX, "zzz".to_string());
|
assert!(Single::MAX.validate_parse_strings(&["zzz", "ZZZ"]).unwrap());
|
||||||
assert_eq!(Single::MAX, "ZZZ".to_string());
|
assert!(Single::MAX.is_max());
|
||||||
|
assert!(!Single::MAX.is_nil());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
use crate::TripleMessage;
|
||||||
use crate::{utils::is_delimiter, Double, Error, Single};
|
use crate::{utils::is_delimiter, Double, Error, Single};
|
||||||
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
@ -6,48 +8,40 @@ use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||||
|
|
||||||
use crate::TripodId;
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct Triple {
|
pub struct Triple(u64);
|
||||||
inner: (Single, Single, Single)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TripodId for Triple{
|
impl TripodId for Triple{
|
||||||
type Integer = u64;
|
type Integer = u64;
|
||||||
|
type Tuple = (Single, Single, Single);
|
||||||
|
#[cfg(feature="prost")]
|
||||||
|
type Message = TripleMessage;
|
||||||
const CAPACITY: Self::Integer = (Single::CAPACITY as u64).pow(3);
|
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// ```
|
const NIL: Self = Self(0);
|
||||||
/// use tripod_id::{TripodId, Triple};
|
|
||||||
/// use std::str::FromStr;
|
const MAX: Self = Self(Self::CAPACITY - 1);
|
||||||
///
|
|
||||||
/// 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)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn validate_inner(self) -> bool {
|
fn validate_inner(self) -> bool {
|
||||||
self.inner.0.validate_inner()
|
self.0 < Self::CAPACITY
|
||||||
&& self.inner.1.validate_inner()
|
|
||||||
&& self.inner.2.validate_inner()
|
|
||||||
&& (u64::from(self) < Self::CAPACITY)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Triple {
|
impl Display for Triple {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(Self {
|
Ok(match s.len() {
|
||||||
inner : match s.len() {
|
|
||||||
11 => {
|
11 => {
|
||||||
let delimiter = [
|
let delimiter = [
|
||||||
s[3..4].chars().next().unwrap(),
|
s[3..4].chars().next().unwrap(),
|
||||||
s[7..8].chars().next().unwrap(),
|
s[7..8].chars().next().unwrap(),
|
||||||
];
|
];
|
||||||
if is_delimiter(delimiter[0]) && is_delimiter(delimiter[1]) {
|
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 {
|
} else {
|
||||||
Err(Error::InvalidDelimiter{
|
Err(Error::InvalidDelimiter{
|
||||||
found: Vec::from(delimiter),
|
found: Vec::from(delimiter),
|
||||||
|
|
@ -73,7 +66,7 @@ impl FromStr for Triple {
|
||||||
|
|
||||||
}
|
}
|
||||||
9 => {
|
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 => {
|
x => {
|
||||||
Err(Self::Err::InvalidLength{
|
Err(Self::Err::InvalidLength{
|
||||||
|
|
@ -83,16 +76,15 @@ impl FromStr for Triple {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} ?
|
} ?
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Distribution<Triple> for Standard {
|
impl Distribution<Triple> for Standard {
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Triple {
|
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Triple {
|
||||||
Triple {
|
Triple(rng.gen_range(0..Triple::CAPACITY))
|
||||||
inner: (rng.r#gen(), rng.r#gen(), rng.r#gen())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,12 +93,7 @@ impl TryFrom<u64> for Triple {
|
||||||
|
|
||||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||||
if value < Self::CAPACITY {
|
if value < Self::CAPACITY {
|
||||||
Ok(Self{
|
Ok(Self(value))
|
||||||
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())?
|
|
||||||
)})
|
|
||||||
} else {
|
} else {
|
||||||
Err(Error::OutsideOfRange{
|
Err(Error::OutsideOfRange{
|
||||||
expected: Self::CAPACITY as u64,
|
expected: Self::CAPACITY as u64,
|
||||||
|
|
@ -118,9 +105,27 @@ impl TryFrom<u64> for Triple {
|
||||||
|
|
||||||
impl From<Triple> for u64 {
|
impl From<Triple> for u64 {
|
||||||
fn from(value: Triple) -> Self {
|
fn from(value: Triple) -> Self {
|
||||||
(u16::from(value.inner.0) as u64) * (Double::CAPACITY as u64)
|
value.0
|
||||||
+ (u16::from(value.inner.1) as u64) * (Single::CAPACITY as u64)
|
}
|
||||||
+ (u16::from(value.inner.2) as u64)
|
}
|
||||||
|
|
||||||
|
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<Triple> 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<String> for Triple {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{assert_id_eq_int, assert_id_eq_str};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[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, Triple::CAPACITY-1);
|
||||||
assert_eq!(Triple::MAX, "zzzzzzzzz".to_string());
|
assert_eq!(Triple::MAX, "zzzzzzzzz".to_string());
|
||||||
assert_eq!(Triple::MAX, "ZZZ-ZZZ-ZZZ".to_string());
|
assert_eq!(Triple::MAX, "ZZZ-ZZZ-ZZZ".to_string());
|
||||||
|
assert_eq!((Single::MAX, Single::MAX, Single::MAX), Triple::MAX.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use crate::Single;
|
use crate::Single;
|
||||||
|
|
||||||
|
/// Test if the character is valid delimiter.
|
||||||
pub fn is_delimiter(c: char) -> bool {
|
pub fn is_delimiter(c: char) -> bool {
|
||||||
match c {
|
match c {
|
||||||
'-' | '_' => true,
|
'-' | '_' => true,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue