#![doc(html_root_url = "https://docs.rs/rusty_ulid/2.0.0")]
#![deny(
anonymous_parameters,
bare_trait_objects,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unused_import_braces,
unused_qualifications,
unused_results,
variant_size_differences
)]
#![warn(clippy::all)]
#![forbid(unsafe_code)]
#![allow(unknown_lints)]
#[cfg(feature = "time")]
use time::OffsetDateTime;
#[cfg(feature = "chrono")]
use chrono::prelude::{DateTime, TimeZone, Utc};
use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "rocket")]
mod rocket_;
#[cfg(feature = "schemars")]
mod schemars;
pub mod crockford;
pub use crate::crockford::DecodingError;
#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
fn unix_epoch_ms() -> u64 {
#[cfg(feature = "time")]
{
let now = OffsetDateTime::now_utc();
now.unix_timestamp() as u64 * 1_000 + now.millisecond() as u64
}
#[cfg(all(feature = "chrono", not(feature = "time")))]
{
let now: DateTime<Utc> = Utc::now();
now.timestamp_millis() as u64
}
}
#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
#[must_use]
pub fn generate_ulid_string() -> String {
Ulid::generate().to_string()
}
#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
#[must_use]
pub fn generate_ulid_bytes() -> [u8; 16] {
Ulid::generate().into()
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
pub struct Ulid {
value: (u64, u64),
}
impl Ulid {
#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
#[must_use]
pub fn generate() -> Self {
Self::from_timestamp_with_rng(unix_epoch_ms(), &mut rand::thread_rng())
}
#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
#[must_use]
pub fn next_monotonic(previous_ulid: Self) -> Self {
Self::next_monotonic_from_timestamp_with_rng(
previous_ulid,
unix_epoch_ms(),
&mut rand::thread_rng(),
)
}
#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
#[must_use]
pub fn next_strictly_monotonic(previous_ulid: Self) -> Option<Self> {
Self::next_strictly_monotonic_from_timestamp_with_rng(
previous_ulid,
unix_epoch_ms(),
&mut rand::thread_rng(),
)
}
#[cfg(feature = "rand")]
pub fn from_timestamp_with_rng<R>(timestamp: u64, rng: &mut R) -> Self
where
R: rand::Rng,
{
if (timestamp & 0xFFFF_0000_0000_0000) != 0 {
panic!("ULID does not support timestamps after +10889-08-02T05:31:50.655Z");
}
let high = timestamp << 16 | u64::from(rng.gen::<u16>());
let low = rng.gen::<u64>();
let value = (high, low);
Self { value }
}
#[cfg(feature = "rand")]
pub fn next_monotonic_from_timestamp_with_rng<R>(
previous_ulid: Self,
timestamp: u64,
rng: &mut R,
) -> Self
where
R: rand::Rng,
{
Self::next_monotonic_from_timestamp_with_rng_and_postprocessor(
Some(previous_ulid),
timestamp,
rng,
None,
)
}
#[cfg(feature = "rand")]
pub fn next_monotonic_from_timestamp_with_rng_and_postprocessor<R>(
previous_ulid: Option<Self>,
timestamp: u64,
rng: &mut R,
postprocessor: Option<&dyn Fn(Self) -> Self>,
) -> Self
where
R: rand::Rng,
{
if let Some(previous_ulid) = previous_ulid {
if previous_ulid.timestamp() == timestamp {
return previous_ulid.increment();
}
}
let result = Self::from_timestamp_with_rng(timestamp, rng);
postprocessor.map_or(result, |postprocessor| postprocessor(result))
}
#[cfg(feature = "rand")]
pub fn next_strictly_monotonic_from_timestamp_with_rng<R>(
previous_ulid: Self,
timestamp: u64,
rng: &mut R,
) -> Option<Self>
where
R: rand::Rng,
{
let result = Self::next_monotonic_from_timestamp_with_rng(previous_ulid, timestamp, rng);
if previous_ulid < result {
Some(result)
} else {
None
}
}
#[cfg(feature = "rand")]
pub fn next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor<R>(
previous_ulid: Option<Self>,
timestamp: u64,
rng: &mut R,
postprocessor: Option<&dyn Fn(Self) -> Self>,
) -> Option<Self>
where
R: rand::Rng,
{
let result = Self::next_monotonic_from_timestamp_with_rng_and_postprocessor(
previous_ulid,
timestamp,
rng,
postprocessor,
);
previous_ulid.map_or(Some(result), |previous_ulid| {
if previous_ulid < result {
Some(result)
} else {
None
}
})
}
#[must_use]
pub fn timestamp(&self) -> u64 {
self.value.0 >> 16
}
#[cfg(feature = "chrono")]
#[must_use]
pub fn datetime(&self) -> DateTime<Utc> {
use chrono::LocalResult;
match Utc.timestamp_millis_opt(self.timestamp() as i64) {
LocalResult::Single(dt) => dt,
_ => panic!("Incorrect timestamp_millis"),
}
}
#[cfg(feature = "time")]
#[must_use]
pub fn offsetdatetime(&self) -> OffsetDateTime {
OffsetDateTime::from_unix_timestamp_nanos((self.timestamp() * 1_000_000) as i128)
.expect("invalid or out-of-range datetime")
}
#[must_use]
pub fn increment(self) -> Self {
const TIMESTAMP_PART_MASK: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;
const RANDOM_PART_MASK: u128 = !TIMESTAMP_PART_MASK;
let value: u128 = self.into();
if value & RANDOM_PART_MASK == RANDOM_PART_MASK {
(value & TIMESTAMP_PART_MASK).into()
} else {
(value + 1).into()
}
}
#[allow(clippy::inherent_to_string_shadow_display)]
#[allow(clippy::wrong_self_convention)]
#[must_use]
pub fn to_string(&self) -> String {
let mut string = String::with_capacity(26);
crockford::append_crockford_u64_tuple(self.value, &mut string);
string
}
}
impl fmt::Display for Ulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str(&self.to_string())
}
}
impl FromStr for Ulid {
type Err = DecodingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = crockford::parse_crockford_u64_tuple(s)?;
Ok(Self { value })
}
}
impl From<[u8; 16]> for Ulid {
#[must_use]
fn from(bytes: [u8; 16]) -> Self {
#[rustfmt::skip]
let high = u64::from(bytes[0]) << 56
| u64::from(bytes[1]) << 48
| u64::from(bytes[2]) << 40
| u64::from(bytes[3]) << 32
| u64::from(bytes[4]) << 24
| u64::from(bytes[5]) << 16
| u64::from(bytes[6]) << 8
| u64::from(bytes[7]);
#[rustfmt::skip]
let low = u64::from(bytes[8]) << 56
| u64::from(bytes[9]) << 48
| u64::from(bytes[10]) << 40
| u64::from(bytes[11]) << 32
| u64::from(bytes[12]) << 24
| u64::from(bytes[13]) << 16
| u64::from(bytes[14]) << 8
| u64::from(bytes[15]);
let value = (high, low);
Self { value }
}
}
impl From<Ulid> for [u8; 16] {
#[rustfmt::skip]
#[must_use]
fn from(ulid: Ulid) -> Self {
let value = ulid.value;
[
((value.0 >> 56) & 0xFF) as u8,
((value.0 >> 48) & 0xFF) as u8,
((value.0 >> 40) & 0xFF) as u8,
((value.0 >> 32) & 0xFF) as u8,
((value.0 >> 24) & 0xFF) as u8,
((value.0 >> 16) & 0xFF) as u8,
((value.0 >> 8) & 0xFF) as u8,
(value.0 & 0xFF) as u8,
((value.1 >> 56) & 0xFF) as u8,
((value.1 >> 48) & 0xFF) as u8,
((value.1 >> 40) & 0xFF) as u8,
((value.1 >> 32) & 0xFF) as u8,
((value.1 >> 24) & 0xFF) as u8,
((value.1 >> 16) & 0xFF) as u8,
((value.1 >> 8) & 0xFF) as u8,
(value.1 & 0xFF) as u8,
]
}
}
impl From<(u64, u64)> for Ulid {
#[must_use]
fn from(value: (u64, u64)) -> Self {
Self { value }
}
}
impl From<Ulid> for (u64, u64) {
#[must_use]
fn from(ulid: Ulid) -> Self {
ulid.value
}
}
impl From<u128> for Ulid {
#[must_use]
fn from(value: u128) -> Self {
let value = ((value >> 64) as u64, (value & 0xFFFF_FFFF_FFFF_FFFF) as u64);
Self { value }
}
}
impl From<Ulid> for u128 {
#[must_use]
fn from(ulid: Ulid) -> Self {
Self::from(ulid.value.0) << 64 | Self::from(ulid.value.1)
}
}
impl TryFrom<&[u8]> for Ulid {
type Error = DecodingError;
fn try_from(bytes: &[u8]) -> Result<Self, DecodingError> {
if bytes.len() != 16 {
return Err(DecodingError::InvalidLength);
}
#[rustfmt::skip]
let high = u64::from(bytes[0]) << 56
| u64::from(bytes[1]) << 48
| u64::from(bytes[2]) << 40
| u64::from(bytes[3]) << 32
| u64::from(bytes[4]) << 24
| u64::from(bytes[5]) << 16
| u64::from(bytes[6]) << 8
| u64::from(bytes[7]);
#[rustfmt::skip]
let low = u64::from(bytes[8]) << 56
| u64::from(bytes[9]) << 48
| u64::from(bytes[10]) << 40
| u64::from(bytes[11]) << 32
| u64::from(bytes[12]) << 24
| u64::from(bytes[13]) << 16
| u64::from(bytes[14]) << 8
| u64::from(bytes[15]);
let value = (high, low);
Ok(Self { value })
}
}
#[cfg(feature = "serde")]
impl Serialize for Ulid {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
let bytes: [u8; 16] = (*self).into();
serializer.serialize_bytes(&bytes)
}
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Ulid {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
struct UlidStringVisitor;
impl<'vi> de::Visitor<'vi> for UlidStringVisitor {
type Value = Ulid;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a ULID string")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Ulid, E> {
value.parse::<Ulid>().map_err(E::custom)
}
}
deserializer.deserialize_str(UlidStringVisitor)
} else {
struct UlidBytesVisitor;
impl<'vi> de::Visitor<'vi> for UlidBytesVisitor {
type Value = Ulid;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "16 ULID bytes")
}
fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Ulid, E> {
Ulid::try_from(value).map_err(E::custom)
}
}
deserializer.deserialize_bytes(UlidBytesVisitor)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const PAST_TIMESTAMP: u64 = 1_481_195_424_879;
const PAST_TIMESTAMP_PART: &str = "01B3F2133F";
const MAX_TIMESTAMP: u64 = 0xFFFF_FFFF_FFFF;
const MAX_TIMESTAMP_PART: &str = "7ZZZZZZZZZ";
const MIN_TIMESTAMP: u64 = 0;
const MIN_TIMESTAMP_PART: &str = "0000000000";
#[test]
fn increment() {
single_increment(0x0000_0000_0000_0000_0000_0000_0000_0000, Ulid::from(1));
single_increment(
0x0000_0000_0000_FFFF_FFFF_FFFF_FFFF_FFFE,
Ulid::from(0xFFFF_FFFF_FFFF_FFFF_FFFF),
);
single_increment(0x0000_0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF, Ulid::from(0));
single_increment(
0x0000_0000_0001_0000_0000_0000_0000_0000,
Ulid::from(0x0000_0000_0001_0000_0000_0000_0000_0001),
);
single_increment(
0x0000_0000_0001_FFFF_FFFF_FFFF_FFFF_FFFF,
Ulid::from(0x0000_0000_0001_0000_0000_0000_0000_0000),
);
}
fn single_increment(input: u128, expected_result: Ulid) {
let input_value: Ulid = input.into();
let incremented = input_value.increment();
assert_eq!(incremented, expected_result);
assert_eq!(input_value.timestamp(), incremented.timestamp());
}
#[test]
fn from_string_to_string() {
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "0000000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "ZZZZZZZZZZZZZZZZ"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "123456789ABCDEFG"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "1000000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "1000000000000001"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "0001000000000001"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "0100000000000001"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(PAST_TIMESTAMP_PART.to_owned() + "0000000000000001"),
PAST_TIMESTAMP,
);
single_from_string_to_string(
&(MAX_TIMESTAMP_PART.to_owned() + "123456789ABCDEFG"),
MAX_TIMESTAMP,
);
single_from_string_to_string(
&(MIN_TIMESTAMP_PART.to_owned() + "123456789ABCDEFG"),
MIN_TIMESTAMP,
);
let largest_legal_ulid_string = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
single_from_string_to_string(largest_legal_ulid_string, MAX_TIMESTAMP);
}
fn single_from_string_to_string(s: &str, timestamp: u64) {
let ulid = Ulid::from_str(s).unwrap();
assert_eq!(ulid.timestamp(), timestamp);
assert_eq!(ulid.to_string(), s);
}
#[test]
fn from_string_to_string_special_cases() {
single_from_string_to_string_special_case(
&(PAST_TIMESTAMP_PART.to_owned() + "00i0000000000000"),
&(PAST_TIMESTAMP_PART.to_owned() + "0010000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string_special_case(
&(PAST_TIMESTAMP_PART.to_owned() + "00I0000000000000"),
&(PAST_TIMESTAMP_PART.to_owned() + "0010000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string_special_case(
&(PAST_TIMESTAMP_PART.to_owned() + "00l0000000000000"),
&(PAST_TIMESTAMP_PART.to_owned() + "0010000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string_special_case(
&(PAST_TIMESTAMP_PART.to_owned() + "00L0000000000000"),
&(PAST_TIMESTAMP_PART.to_owned() + "0010000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string_special_case(
&(PAST_TIMESTAMP_PART.to_owned() + "00o0000000000000"),
&(PAST_TIMESTAMP_PART.to_owned() + "0000000000000000"),
PAST_TIMESTAMP,
);
single_from_string_to_string_special_case(
&(PAST_TIMESTAMP_PART.to_owned() + "00O0000000000000"),
&(PAST_TIMESTAMP_PART.to_owned() + "0000000000000000"),
PAST_TIMESTAMP,
);
}
fn single_from_string_to_string_special_case(s: &str, expected: &str, timestamp: u64) {
let ulid = Ulid::from_str(s).unwrap();
assert_eq!(ulid.timestamp(), timestamp);
assert_eq!(ulid.to_string(), expected);
}
#[test]
fn from_str_failure_too_long() {
let result = Ulid::from_str("123456789012345678901234567");
assert_eq!(result, Err(DecodingError::InvalidLength));
}
#[test]
fn from_str_failure_too_short() {
let result = Ulid::from_str("1234567890123456789012345");
assert_eq!(result, Err(DecodingError::InvalidLength));
}
#[test]
fn from_str_failure_invalid_unicode() {
let string = "012345678🦀0123456789012";
let result = Ulid::from_str(string);
assert_eq!(result, Err(DecodingError::InvalidChar('🦀')));
}
#[test]
fn from_str_failure_overflow() {
let smallest_overflowing_ulid_string = "80000000000000000000000000";
let result = Ulid::from_str(smallest_overflowing_ulid_string);
assert_eq!(result, Err(DecodingError::DataTypeOverflow));
}
#[test]
fn eq_cmp_sanity_checks() {
use std::cmp::Ordering;
let ulid_one_low: Ulid = (0, 1).into();
let ulid_two_low: Ulid = (0, 2).into();
let ulid_one_high: Ulid = (1, 0).into();
let ulid_one_low_other: Ulid = (0, 1).into();
assert!(ulid_one_low.eq(&ulid_one_low));
assert_eq!(ulid_one_low.cmp(&ulid_one_low), Ordering::Equal);
assert_eq!(ulid_one_low, ulid_one_low_other);
assert!(ulid_one_low.eq(&ulid_one_low_other));
assert_eq!(ulid_one_low.cmp(&ulid_one_low_other), Ordering::Equal);
assert_ne!(ulid_one_low, ulid_two_low);
assert_ne!(ulid_two_low, ulid_one_low);
assert!(ulid_one_low < ulid_two_low);
assert!(ulid_two_low > ulid_one_low);
assert!(!ulid_one_low.eq(&ulid_two_low));
assert!(!ulid_two_low.eq(&ulid_one_low));
assert_eq!(ulid_one_low.cmp(&ulid_two_low), Ordering::Less);
assert_eq!(ulid_two_low.cmp(&ulid_one_low), Ordering::Greater);
assert_ne!(ulid_one_low, ulid_one_high);
assert_ne!(ulid_one_high, ulid_one_low);
assert!(!ulid_one_low.eq(&ulid_one_high));
assert!(!ulid_one_high.eq(&ulid_one_low));
assert_eq!(ulid_one_low.cmp(&ulid_one_high), Ordering::Less);
assert_eq!(ulid_one_high.cmp(&ulid_one_low), Ordering::Greater);
}
#[test]
fn hash_sanity_checks() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let ulid_one_low: Ulid = (0, 1).into();
let ulid_two_low: Ulid = (0, 2).into();
let ulid_one_high: Ulid = (1, 0).into();
let ulid_one_low_other: Ulid = (0, 1).into();
let mut hasher_one_low = DefaultHasher::new();
ulid_one_low.hash(&mut hasher_one_low);
let hash_one_low = hasher_one_low.finish();
let mut hasher_one_low_other = DefaultHasher::new();
ulid_one_low_other.hash(&mut hasher_one_low_other);
let hash_one_low_other = hasher_one_low_other.finish();
let mut hasher_two_low = DefaultHasher::new();
ulid_two_low.hash(&mut hasher_two_low);
let hash_two_low = hasher_two_low.finish();
let mut hasher_one_high = DefaultHasher::new();
ulid_one_high.hash(&mut hasher_one_high);
let hash_one_high = hasher_one_high.finish();
assert_eq!(hash_one_low, hash_one_low_other);
assert_ne!(hash_one_low, hash_two_low);
assert_ne!(hash_one_low, hash_one_high);
}
#[cfg(not(miri))] #[cfg(feature = "rand")]
#[test]
#[should_panic(expected = "ULID does not support timestamps after +10889-08-02T05:31:50.655Z")]
fn y10889_bug() {
use rand::rngs::mock::StepRng;
let mut mock_rng = StepRng::new(0, 0);
let _ = Ulid::from_timestamp_with_rng(0x0001_0000_0000_0000, &mut mock_rng);
}
#[cfg(feature = "rand")]
#[test]
fn test_from_timestamp_with_rng() {
use rand::rngs::mock::StepRng;
let mut mock_rng = StepRng::new(0, 0);
let ulid = Ulid::from_timestamp_with_rng(0xFFFF_FFFF_FFFF, &mut mock_rng);
let ulid_value: u128 = ulid.into();
assert_eq!(ulid_value, 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000);
let mut mock_rng = StepRng::new(0xF00F, 0);
let ulid = Ulid::from_timestamp_with_rng(0, &mut mock_rng);
let ulid_value: u128 = ulid.into();
assert_eq!(ulid_value, 0x0000_0000_0000_F00F_0000_0000_0000_F00F);
}
#[cfg(feature = "rand")]
#[test]
fn test_next_monotonic_from_timestamp_with_rng_and_postprocessor() {
fn postprocessor_fn(ulid: Ulid) -> Ulid {
Ulid::from(u128::from(ulid) & 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000_0000)
}
let previous_ulid = Ulid::from(0);
let ulid = Ulid::next_monotonic_from_timestamp_with_rng_and_postprocessor(
Some(previous_ulid),
1,
&mut rand::thread_rng(),
Some(&postprocessor_fn),
);
assert_eq!(0, u128::from(ulid) & 0xFFFF_FFFF);
let ulid = Ulid::next_monotonic_from_timestamp_with_rng_and_postprocessor(
None,
1,
&mut rand::thread_rng(),
Some(&postprocessor_fn),
);
assert_eq!(0, u128::from(ulid) & 0xFFFF_FFFF);
assert_ne!(0, u128::from(ulid));
}
#[cfg(feature = "rand")]
#[test]
fn test_next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor_overflow() {
let previous_ulid = Ulid::from(0);
let ulid = Ulid::next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor(
Some(previous_ulid),
0,
&mut rand::thread_rng(),
None,
);
assert_eq!(ulid, Some(Ulid::from(1)));
let previous_ulid = Ulid::from(0x0000_0000_0000_FFFF_FFFF_FFFF_FFFF_FFFE);
let ulid = Ulid::next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor(
Some(previous_ulid),
0,
&mut rand::thread_rng(),
None,
);
assert_eq!(
ulid,
Some(Ulid::from(0x0000_0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF))
);
let previous_ulid = Ulid::from(0x0000_0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF);
let ulid = Ulid::next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor(
Some(previous_ulid),
0,
&mut rand::thread_rng(),
None,
);
assert_eq!(ulid, None);
}
#[cfg(feature = "rand")]
#[test]
fn test_next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor() {
fn postprocessor_fn(ulid: Ulid) -> Ulid {
Ulid::from(u128::from(ulid) & 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_0000_0000)
}
let previous_ulid = Ulid::from(0);
let ulid = Ulid::next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor(
Some(previous_ulid),
1,
&mut rand::thread_rng(),
Some(&postprocessor_fn),
);
let ulid = ulid.unwrap();
assert_eq!(0, u128::from(ulid) & 0xFFFF_FFFF);
let ulid = Ulid::next_strictly_monotonic_from_timestamp_with_rng_and_postprocessor(
None,
1,
&mut rand::thread_rng(),
Some(&postprocessor_fn),
);
let ulid = ulid.unwrap();
assert_eq!(0, u128::from(ulid) & 0xFFFF_FFFF);
assert_ne!(0, u128::from(ulid));
}
}
#[cfg(all(doctest, feature = "rand", feature = "chrono"))]
mod doc_tests {
use doc_comment::doctest;
doctest!("../README.md", readme);
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use super::*;
use serde_test::{assert_de_tokens_error, assert_tokens, Compact, Readable, Token};
#[test]
fn test_serde_readable() {
use serde_test::Configure;
let ulid = Ulid::from_str("7ZZZZZZZZZZZZZZZZZZZZZZZZZ").unwrap();
assert_tokens(
&ulid.readable(),
&[Token::Str("7ZZZZZZZZZZZZZZZZZZZZZZZZZ")],
);
let ulid = Ulid::from(0x1122_3344_5566_7788_99AA_BBCC_DDEE_F00F);
assert_tokens(
&ulid.readable(),
&[Token::Str("0H48SM8NB6EY49KANVSKEYXW0F")],
);
}
#[test]
fn test_serde_compact() {
use serde_test::Configure;
let ulid = Ulid::from_str("7ZZZZZZZZZZZZZZZZZZZZZZZZZ").unwrap();
assert_tokens(
&ulid.compact(),
&[Token::Bytes(&[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF,
])],
);
let ulid = Ulid::from(0x1122_3344_5566_7788_99AA_BBCC_DDEE_F00F);
assert_tokens(
&ulid.compact(),
&[Token::Bytes(&[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
0xF0, 0x0F,
])],
);
}
#[test]
fn test_de_readable_error() {
assert_de_tokens_error::<Readable<Ulid>>(
&[Token::Bytes(&[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
0xF0, 0x0F,
])],
"invalid type: byte array, expected a ULID string",
);
assert_de_tokens_error::<Readable<Ulid>>(
&[Token::Str("0H48SM8NB6EY49KANUSKEYXW0F")],
"invalid character 'U'",
);
assert_de_tokens_error::<Readable<Ulid>>(
&[Token::Str("0H48SM8NB6EY49KANVSKEYXW0FF")],
"invalid length",
);
assert_de_tokens_error::<Readable<Ulid>>(
&[Token::Str("0H48SM8NB6EY49KANVSKEYXW0")],
"invalid length",
);
assert_de_tokens_error::<Readable<Ulid>>(
&[Token::Str("80000000000000000000000000")],
"data type overflow",
);
}
#[test]
fn test_de_compact_error() {
assert_de_tokens_error::<Compact<Ulid>>(
&[Token::Str("0H48SM8NB6EY49KANVSKEYXW0F")],
"invalid type: string \"0H48SM8NB6EY49KANVSKEYXW0F\", expected 16 ULID bytes",
);
assert_de_tokens_error::<Compact<Ulid>>(
&[Token::Bytes(&[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
0xF0, 0x0F, 0xFF,
])],
"invalid length",
);
assert_de_tokens_error::<Compact<Ulid>>(
&[Token::Bytes(&[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
0xF0,
])],
"invalid length",
);
}
}