mod b32;
#[cfg(feature = "uuid")]
mod uuid;
pub use crate::b32::{DecodeError, ENCODE};
use std::fmt;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use rand::RngExt;
const VERSION: &str = "a";
fn now() -> std::time::SystemTime {
std::time::SystemTime::now()
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Upid(pub u128);
impl Upid {
pub fn new(prefix: &str) -> Upid {
Upid::from_prefix(prefix)
}
pub fn from_prefix(prefix: &str) -> Upid {
Upid::from_prefix_and_datetime(prefix, now())
}
pub fn from_prefix_and_datetime(prefix: &str, datetime: SystemTime) -> Upid {
let milliseconds = datetime
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_millis();
Upid::from_prefix_and_milliseconds(prefix, milliseconds)
}
pub fn from_prefix_and_milliseconds(prefix: &str, milliseconds: u128) -> Upid {
let time_bits = milliseconds >> 8;
let mut source = rand::rng();
let random = source.random::<u64>() as u128;
let prefix = format!("{:z<4}", prefix);
let prefix: String = prefix.chars().take(4).collect();
let prefix = format!("{}{}", prefix, VERSION);
let p = b32::decode_prefix(prefix.as_bytes())
.expect("decode_prefix failed with version character overflow");
let res = (time_bits << 88)
| (random << 24)
| ((p[0] as u128) << 16)
| ((p[1] as u128) << 8)
| p[2] as u128;
Upid(res)
}
pub fn from_string(encoded: &str) -> Result<Upid, DecodeError> {
match b32::decode(encoded) {
Ok(int_val) => Ok(Upid(int_val)),
Err(err) => Err(err),
}
}
pub fn datetime(&self) -> SystemTime {
let stamp = self.milliseconds();
SystemTime::UNIX_EPOCH + Duration::from_millis(stamp)
}
pub fn prefix(&self) -> String {
let bytes: [u8; 16] = self.0.to_be_bytes();
let (prefix, _) = b32::encode_prefix(&bytes[b32::END_RANDO_BIN..]);
prefix
}
pub const fn milliseconds(&self) -> u64 {
((self.0 >> 88) << 8) as u64
}
#[allow(clippy::inherent_to_string_shadow_display)] pub fn to_string(&self) -> String {
b32::encode(self.0)
}
pub const fn from_bytes(bytes: [u8; 16]) -> Upid {
Self(u128::from_be_bytes(bytes))
}
pub const fn to_bytes(&self) -> [u8; 16] {
self.0.to_be_bytes()
}
}
impl Default for Upid {
fn default() -> Self {
Upid::new("")
}
}
impl From<Upid> for String {
fn from(upid: Upid) -> String {
upid.to_string()
}
}
impl From<u128> for Upid {
fn from(value: u128) -> Upid {
Upid(value)
}
}
impl From<Upid> for u128 {
fn from(upid: Upid) -> u128 {
upid.0
}
}
impl FromStr for Upid {
type Err = DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Upid::from_string(s)
}
}
impl fmt::Display for Upid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: u128 = 257;
#[test]
fn can_into_thing() {
let want = Upid::from_str("user_aaccvpp5guht4dts56je5a").unwrap();
let s: String = want.into();
let u: u128 = want.into();
assert_eq!(Upid::from_str(&s).unwrap(), want);
assert_eq!(Upid::from(u), want);
}
#[test]
fn can_display_things() {
println!("{}", DecodeError::InvalidLength);
println!("{}", DecodeError::InvalidChar);
}
#[test]
fn test_dynamic() {
let upid = Upid::new("user");
let encoded = upid.to_string();
let upid2 = Upid::from_string(&encoded).expect("failed to deserialize");
assert_eq!(upid, upid2);
}
#[test]
fn test_order() {
let dt = SystemTime::now();
let upid1 = Upid::from_prefix_and_datetime("user", dt);
let upid2 = Upid::from_prefix_and_datetime("user", dt + Duration::from_millis(EPS as u64));
assert!(upid1 < upid2);
}
#[test]
fn test_timestamp() {
let dt = SystemTime::now();
let want = dt
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis();
let upid = Upid::from_prefix_and_milliseconds("user", want);
let got = u128::from(upid.milliseconds());
assert!(want - got < EPS);
}
#[test]
fn test_datetime() {
let dt = SystemTime::now();
let upid = Upid::from_prefix_and_datetime("user", dt);
assert!(upid.datetime() <= dt);
assert!(upid.datetime() + Duration::from_millis(EPS as u64) >= dt);
}
#[test]
fn test_invalid_prefix() {
let want = "zzzz";
let got = Upid::from_prefix("[0#/]]1,").prefix();
assert_eq!(got, want);
let got = Upid::from_prefix("[0").prefix();
assert_eq!(got, want);
}
}