diff --git a/Cargo.lock b/Cargo.lock index 272b761..a6f637e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "autocfg" version = "1.0.1" @@ -21,28 +23,21 @@ dependencies = [ "libc", "num-integer", "num-traits", - "serde", "time", "winapi", ] [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.82" @@ -51,13 +46,12 @@ checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" [[package]] name = "nrid" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", "rand", "serde", "thiserror", - "tracing", ] [[package]] @@ -79,12 +73,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "pin-project-lite" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" - [[package]] name = "ppv-lite86" version = "0.2.10" @@ -111,11 +99,10 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ - "getrandom", "libc", "rand_chacha", "rand_core", @@ -124,9 +111,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", "rand_core", @@ -134,18 +121,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ "rand_core", ] @@ -194,54 +181,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", "winapi", ] -[[package]] -name = "tracing" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" -dependencies = [ - "lazy_static", -] - [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 7f1a7e5..5d091b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,16 @@ [package] name = "nrid" -version = "0.1.1" +version = "0.2.0" authors = ["Michael Pfaff "] edition = "2018" license = "MIT OR Apache-2.0" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["serde"] [dependencies] -chrono = { version = "0.4.11", features = ["serde"] } -rand = { version = "0.7.3" } -#regex = { version = "1.3" } -serde = { version = "1" } -thiserror = { version = "1" } -tracing = { version = "0.1.15" } +chrono = "0.4.19" +rand = "0.8.3" +serde = { version = "1", optional = true } +thiserror = "1" diff --git a/src/lib.rs b/src/lib.rs index c2a7e30..7fa58e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,16 +4,24 @@ * Made of a 64-bit seconds, 32-bit nanoseconds, and 32-bit secure-randomness, the NRID is suitable for cases where you want a secure-random, unique identifier like a UUID, but you also want the identifier to be correlated with the time of creation. */ -#[macro_use] -extern crate tracing; - use core::cmp::max; use core::cmp::min; use core::convert::TryFrom; -mod sentence; +/// rules: consist of 12 hex characters, a hyphen, 9 hex characters, a hyphen, and 9 hex characters +#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] +pub enum NridError { + #[error("must be a total of 32 characters long")] + WrongLength, -/// Nano Unique Identifier. Stores the seconds since the epoch and nanoseconds since the second, along with 4 bytes of randomness. + #[error("must consist of hexadecimal characters and hyphens")] + IllegalCharacters, + + #[error("segment {0} must be {1} hex characters long")] + WrongSegmentLength(u8, u8), +} + +/// Nano-Random IDentifier. Stores the seconds since the epoch and nanoseconds since the second, along with 4 bytes of randomness. #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[repr(C)] pub struct Nrid { @@ -23,7 +31,7 @@ pub struct Nrid { } impl Nrid { - /// Returns a new `ObjectKey` with its timestamp taken from [`Utc::now`]. + /// Returns a new `Nrid` with its timestamp taken from [`Utc::now`]. #[inline] pub fn now() -> Self { let now = chrono::Utc::now(); @@ -63,6 +71,7 @@ impl From for String { } } +#[cfg(feature = "serde")] impl serde::Serialize for Nrid { fn serialize(&self, serializer: S) -> std::result::Result where @@ -72,14 +81,16 @@ impl serde::Serialize for Nrid { } } +#[cfg(feature = "serde")] struct NridVisitor; + +#[cfg(feature = "serde")] impl<'de> serde::de::Visitor<'de> for NridVisitor { type Value = Nrid; fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - use crate::sentence::ToSentence; - formatter.write_str(&format!("a string that must {}", NRID_RULES.to_sentence())) + formatter.write_str(&format!("a string that must consist of 12 hex characters, a hyphen, 9 hex characters, a hyphen, and 9 hex characters")) } fn visit_str(self, value: &str) -> Result @@ -93,6 +104,7 @@ impl<'de> serde::de::Visitor<'de> for NridVisitor { } } +#[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Nrid { fn deserialize(deserializer: D) -> Result where @@ -115,28 +127,6 @@ impl From for chrono::DateTime { } } -// commented out because it can't be deterministic due to the randomness in a Nrid not being representable in a DateTime. -// impl From<&DateTime> for Nrid { -// #[inline] -// fn from(value: &DateTime) -> Self { -// // let value = value.borrow(); -// Self( -// // we ensure that the seconds value is not negative before casting out of an abundance of safety. -// max(value.timestamp(), 0) as u64, -// value.timestamp_subsec_nanos(), -// ) -// } -// } - -const NRID_RULES: &'static [&'static str] = - &["consist of 12 hex characters, a hyphen, 9 hex characters, a hyphen, and 9 hex characters"]; -const NRID_LEN_RULES: &'static [&'static str] = &["be 32 characters long"]; -const NRID_CHARS_RULES: &'static [&'static str] = &["consist of hexadecimal characters and hyphens"]; - -const SECONDS_RULES: &'static [&'static str] = &["be 12 hex characters"]; -const NANOSECONDS_RULES: &'static [&'static str] = &["be 9 hex characters"]; -const RANDOMNESS_RULES: &'static [&'static str] = &["be 9 hex characters"]; - const SECONDS_LEN: usize = 12; const NANOSECONDS_LEN: usize = 9; const RANDOMNESS_LEN: usize = 9; @@ -146,35 +136,23 @@ const NANOSECONDS_OFFSET: usize = SECONDS_OFFSET+SECONDS_LEN+1; const RANDOMNESS_OFFSET: usize = NANOSECONDS_OFFSET+NANOSECONDS_LEN+1; impl TryFrom<&str> for Nrid { - type Error = ValidationError; + type Error = NridError; fn try_from(value: &str) -> std::result::Result { let value_len = value.len(); if value_len != (SECONDS_LEN + 1 + NANOSECONDS_LEN + 1 + RANDOMNESS_LEN) { - return Err(ValidationError::Rules { - rules: NRID_LEN_RULES, - offending_segment: value.to_string(), - } - .into()); + return Err(NridError::WrongLength); } for (i, c) in value.chars().enumerate() { if i == NANOSECONDS_OFFSET-1 || i == RANDOMNESS_OFFSET-1 { if c != '-' { - return Err(ValidationError::Rules { - rules: NRID_CHARS_RULES, - offending_segment: c.to_string(), - } - .into()); + return Err(NridError::IllegalCharacters); } } else { if !char::is_ascii_hexdigit(&c) { - return Err(ValidationError::Rules { - rules: NRID_CHARS_RULES, - offending_segment: c.to_string(), - } - .into()); + return Err(NridError::IllegalCharacters); } } } @@ -192,110 +170,31 @@ impl TryFrom<&str> for Nrid { nanoseconds: nsec, randomness: rand, }), - Err(err) => { - trace!(error = ?err, "parse randomness failed"); - return Err(ValidationError::Rules { - rules: RANDOMNESS_RULES, - offending_segment: rand.to_string(), - } - .into()); + Err(_) => { + return Err(NridError::WrongSegmentLength(3, 9)); } } } - Err(err) => { - trace!(error = ?err, "parse nanoseconds failed"); - return Err(ValidationError::Rules { - rules: NANOSECONDS_RULES, - offending_segment: nsec.to_string(), - } - .into()); + Err(_) => { + return Err(NridError::WrongSegmentLength(2, 9)); } } } - Err(err) => { - trace!(error = ?err, "parse seconds failed"); - return Err(ValidationError::Rules { - rules: SECONDS_RULES, - offending_segment: sec.to_string(), - } - .into()); + Err(_) => { + return Err(NridError::WrongSegmentLength(1, 12)); } } } } impl core::str::FromStr for Nrid { - type Err = ValidationError; + type Err = NridError; fn from_str(value: &str) -> std::result::Result { Self::try_from(value) } } -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] -pub enum ValidationError { - /// Length of input was not as expected. - LengthEqual { - /// The expected input length. - expected: usize, - /// The input segment that failed validation. - offending_segment: String, - }, - /// Length of input was greater than expected. - LengthMax { - /// The maximum expected input length. - max: usize, - /// The input segment that failed validation. - offending_segment: String, - }, - /// Length of input was less than expected - LengthMin { - /// The minimum expected input length. - min: usize, - - /// The input segment that failed validation. - offending_segment: String, - }, - - /// Input failed to match rules. - Rules { - /// The rules the input must follow. - rules: &'static [&'static str], - - /// The input segment that failed validation. - offending_segment: String, - }, -} - -impl std::fmt::Display for ValidationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - use crate::sentence::ToSentence; - match self { - Self::LengthEqual { - expected, - offending_segment, - } => write!(f, "`{}` is not equal to {}", offending_segment, expected), - Self::LengthMax { - max, - offending_segment, - } => write!(f, "`{}` is longer than {}", offending_segment, max), - Self::LengthMin { - min, - offending_segment, - } => write!(f, "`{}` is shorter than {}", offending_segment, min), - Self::Rules { - rules, - offending_segment, - } => write!( - f, - "`{}` must {} and fails one or more", - offending_segment, - rules.to_sentence() - ), - } - } -} - #[cfg(test)] mod test { use core::convert::TryFrom; diff --git a/src/sentence.rs b/src/sentence.rs deleted file mode 100644 index cfec9ea..0000000 --- a/src/sentence.rs +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Provides traits and helpers for generating and manipulating written lanuage constructs - */ - -/// A type that can be compiled into a sentence. -pub trait ToSentence { - /// Sentencifies self. The result should be suitable to be placed within a sentence. - fn to_sentence(&self) -> String; - - /// Sentencifies self, upper-casing the first letter and adding a period to the end. Using this is discouraged due to its lack of flexibility (i.e. needing alternate punctuation or a lowercase first letter). - fn to_sentence_finalize(&self) -> String { - let s = self.to_sentence(); - let mut c = s.chars(); - match s.len() { - 0 => format!(""), - 1 => format!("{}.", s.to_uppercase()), - _ => format!("{}{}.", c.nth(0).unwrap().to_uppercase(), c.as_str()), - } - } -} - -impl<'a, T> ToSentence for T where T: AsRef<[&'a str]> { - fn to_sentence(&self) -> String { - let s = self.as_ref(); - let len = s.len(); - let mut str = String::new(); - for (i, seg) in s.iter().map(|s| s.as_ref()).enumerate() { - if i == 0 { - str.push_str(seg); - } else if i == len-1 { - if len == 2 { - str.push_str(&format!(" and {}", seg)); - } else { - str.push_str(&format!(", and {}", seg)); - } - } else { - str.push_str(&format!(", {}", seg)); - } - } - str - } -} - -// impl ToSentence for T where T: AsRef<[String]> { -// fn to_sentence(&self) -> String { -// let s = self.as_ref(); -// let len = s.len(); -// let mut str = String::new(); -// for (i, seg) in s.iter().map(|s| s.as_ref()).enumerate() { -// if i == 0 { -// str.push_str(seg); -// } else if i == len { -// if len == 2 { -// str.push_str(&format!(" and {}", seg)); -// } else { -// str.push_str(&format!(", and {}", seg)); -// } -// } else { -// str.push_str(&format!(", {}", seg)); -// } -// } -// str -// } -// } - -#[cfg(test)] -mod test { - use super::ToSentence; - - const STRING_SLICE_A: &'static [&'static str] = &[ - "a", - ]; - - const STRING_SLICE_B: &'static [&'static str] = &[ - "a", - "b", - ]; - - const STRING_SLICE_C: &'static [&'static str] = &[ - "a", - "b", - "c", - "d", - "e", - "f", - "g", - ]; - - #[test] - fn str_slice_to_sentence() { - assert_eq!(STRING_SLICE_A.to_sentence(), "a"); - assert_eq!(STRING_SLICE_B.to_sentence(), "a and b"); - assert_eq!(STRING_SLICE_C.to_sentence(), "a, b, c, d, e, f, and g"); - } - - #[test] - fn str_slice_to_sentence_finalize() { - assert_eq!(STRING_SLICE_A.to_sentence_finalize(), "A."); - assert_eq!(STRING_SLICE_B.to_sentence_finalize(), "A and b."); - assert_eq!(STRING_SLICE_C.to_sentence_finalize(), "A, b, c, d, e, f, and g."); - } - - #[test] - fn string_vec_to_sentence() { - let vec: Vec = vec!{ - "a".to_owned(), - }; - - assert_eq!(vec.iter().map(|e| e.as_ref()).collect::>().to_sentence(), "a"); - - let vec: Vec = vec!{ - "a".to_owned(), - "b".to_owned(), - }; - - assert_eq!(vec.iter().map(|e| e.as_ref()).collect::>().to_sentence(), "a and b"); - - let vec: Vec = vec!{ - "a".to_owned(), - "b".to_owned(), - "c".to_owned(), - "d".to_owned(), - "e".to_owned(), - "f".to_owned(), - "g".to_owned(), - }; - - assert_eq!(vec.iter().map(|e| e.as_ref()).collect::>().to_sentence(), "a, b, c, d, e, f, and g"); - } - - #[test] - fn string_vec_to_sentence_finalize() { - let vec: Vec = vec!{ - "a".to_owned(), - }; - - assert_eq!(vec.iter().map(|e| e.as_ref()).collect::>().to_sentence_finalize(), "A."); - - let vec: Vec = vec!{ - "a".to_owned(), - "b".to_owned(), - }; - - assert_eq!(vec.iter().map(|e| e.as_ref()).collect::>().to_sentence_finalize(), "A and b."); - - let vec: Vec = vec!{ - "a".to_owned(), - "b".to_owned(), - "c".to_owned(), - "d".to_owned(), - "e".to_owned(), - "f".to_owned(), - "g".to_owned(), - }; - - assert_eq!(vec.iter().map(|e| e.as_ref()).collect::>().to_sentence_finalize(), "A, b, c, d, e, f, and g."); - } -} -