use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use core::fmt;
use core::ops::Deref;
use core::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, de::Error};
use smol_str::{SmolStr, SmolStrBuilder};
use super::Lazy;
use crate::CowStr;
use crate::types::integer::LimitedU32;
use crate::types::string::{AtStrError, StrParseKind};
#[cfg(all(not(target_arch = "wasm32"), feature = "std"))]
use regex::Regex;
#[cfg(all(not(target_arch = "wasm32"), not(feature = "std")))]
use regex_automata::meta::Regex;
#[cfg(target_arch = "wasm32")]
use regex_lite::Regex;
const S32_CHAR: &str = "234567abcdefghijklmnopqrstuvwxyz";
fn s32_encode(mut i: u64) -> SmolStr {
let mut s = SmolStrBuilder::new();
for _ in 0..13 {
let c = i & 0x1F;
s.push(S32_CHAR.chars().nth(c as usize).unwrap());
i >>= 5;
}
let mut builder = SmolStrBuilder::new();
for c in s.finish().chars().rev() {
builder.push(c);
}
builder.finish()
}
static TID_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$").unwrap()
});
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
#[serde(transparent)]
#[repr(transparent)]
pub struct Tid(SmolStr);
impl Tid {
pub fn new(tid: impl AsRef<str>) -> Result<Self, AtStrError> {
let tid = tid.as_ref();
if tid.len() != 13 {
let kind = if tid.len() > 13 {
StrParseKind::TooLong {
max: 13,
actual: tid.len(),
}
} else {
StrParseKind::TooShort {
min: 13,
actual: tid.len(),
}
};
Err(AtStrError::new("tid", tid.to_string(), kind))
} else if !TID_REGEX.is_match(&tid.as_ref()) {
let kind = StrParseKind::RegexFail {
span: None,
message: SmolStr::new_static("didn't match schema"),
};
Err(AtStrError::new("tid", tid.to_string(), kind))
} else {
Ok(Self(SmolStr::new_inline(&tid)))
}
}
pub fn raw(tid: impl AsRef<str>) -> Self {
let tid = tid.as_ref();
if tid.len() != 13 {
panic!("TID must be 13 characters")
} else if !TID_REGEX.is_match(&tid) {
panic!("Invalid TID")
} else {
Self(SmolStr::new_inline(tid))
}
}
pub unsafe fn unchecked(tid: impl AsRef<str>) -> Self {
let tid = tid.as_ref();
Self(SmolStr::new_inline(tid))
}
pub fn from_datetime(clkid: LimitedU32<1023>, time: chrono::DateTime<chrono::Utc>) -> Self {
let time = time.timestamp_micros() as u64;
let tid = (time << 10) & 0x7FFF_FFFF_FFFF_FC00 | (Into::<u32>::into(clkid) as u64 & 0x3FF);
Self(s32_encode(tid))
}
pub fn from_time(timestamp: u64, clkid: u32) -> Self {
let tid = (timestamp << 10) & 0x7FFF_FFFF_FFFF_FC00 | (clkid as u64 & 0x3FF);
Self(s32_encode(tid))
}
pub fn timestamp(&self) -> u64 {
s32decode(self.0[0..11].to_owned())
}
pub fn compare_to(&self, other: &Tid) -> i8 {
if self.0 > other.0 {
return 1;
}
if self.0 < other.0 {
return -1;
}
0
}
pub fn newer_than(&self, other: &Tid) -> bool {
self.compare_to(other) > 0
}
pub fn older_than(&self, other: &Tid) -> bool {
self.compare_to(other) < 0
}
pub fn next_str(prev: Option<Tid>) -> Result<Self, AtStrError> {
let prev = match prev {
None => None,
Some(prev) => Some(Tid::new(prev)?),
};
Ok(Ticker::new().next(prev))
}
pub fn now(clkid: LimitedU32<1023>) -> Self {
Self::from_datetime(clkid, chrono::Utc::now())
}
pub fn now_0() -> Self {
Self::from_datetime(LimitedU32::from_str("0").unwrap(), chrono::Utc::now())
}
pub fn as_str(&self) -> &str {
{
let this = &self.0;
this
}
}
}
pub fn s32decode(s: String) -> u64 {
let mut i: usize = 0;
for c in s.chars() {
i = i * 32 + S32_CHAR.chars().position(|x| x == c).unwrap();
}
i as u64
}
impl FromStr for Tid {
type Err = AtStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl<'de> Deserialize<'de> for Tid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: &str = Deserialize::deserialize(deserializer)?;
Self::new(value).map_err(D::Error::custom)
}
}
impl fmt::Display for Tid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<Tid> for String {
fn from(value: Tid) -> Self {
value.0.to_string()
}
}
impl From<Tid> for SmolStr {
fn from(value: Tid) -> Self {
value.0
}
}
impl crate::IntoStatic for Tid {
type Output = Tid;
fn into_static(self) -> Self::Output {
self
}
}
impl From<String> for Tid {
fn from(value: String) -> Self {
if value.len() != 13 {
panic!("TID must be 13 characters")
} else if !TID_REGEX.is_match(&value) {
panic!("Invalid TID")
} else {
Self(SmolStr::new_inline(&value))
}
}
}
impl<'t> From<CowStr<'t>> for Tid {
fn from(value: CowStr<'t>) -> Self {
if value.len() != 13 {
panic!("TID must be 13 characters")
} else if !TID_REGEX.is_match(&value) {
panic!("Invalid TID")
} else {
Self(SmolStr::new_inline(&value))
}
}
}
impl AsRef<str> for Tid {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for Tid {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
pub struct Ticker {
last_timestamp: u64,
clock_id: u32,
}
impl Ticker {
pub fn new() -> Self {
let mut ticker = Self {
last_timestamp: 0,
clock_id: rand::random::<u32>() & 0x03FF,
};
ticker.next(None);
ticker
}
pub fn next(&mut self, prev: Option<Tid>) -> Tid {
let now = chrono::Utc::now().timestamp_micros() as u64;
let now = now & 0x001FFFFFFFFFFFFF;
if now > self.last_timestamp {
self.last_timestamp = now;
} else {
self.last_timestamp += 1;
}
let micros = self.last_timestamp & 0x001FFFFFFFFFFFFF;
let clock_id = self.clock_id & 0x03FF;
let tid = Tid::from_time(micros, clock_id as u32);
match prev {
Some(ref prev) if tid.newer_than(prev) => tid,
Some(prev) => Tid::from_time(prev.timestamp() + 1, clock_id as u32),
None => tid,
}
}
}
impl Default for Ticker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_tids() {
assert!(Tid::new("3jzfcijpj2z2a").is_ok());
assert!(Tid::new("2222222222222").is_ok());
assert!(Tid::new("j7777777777777").is_err()); }
#[test]
fn exact_length() {
assert!(Tid::new("3jzfcijpj2z2a").is_ok());
assert!(Tid::new("3jzfcijpj2z2").is_err()); assert!(Tid::new("3jzfcijpj2z2aa").is_err()); }
#[test]
fn first_char_constraint() {
assert!(Tid::new("2222222222222").is_ok());
assert!(Tid::new("7777777777777").is_ok());
assert!(Tid::new("a222222222222").is_ok());
assert!(Tid::new("j222222222222").is_ok());
assert!(Tid::new("k222222222222").is_err());
assert!(Tid::new("z222222222222").is_err());
}
#[test]
fn remaining_chars_constraint() {
assert!(Tid::new("3abcdefghijkl").is_ok());
assert!(Tid::new("3zzzzzzzzzzzz").is_ok());
assert!(Tid::new("3222222222222").is_ok());
assert!(Tid::new("3777777777777").is_ok());
}
#[test]
fn disallowed_characters() {
assert!(Tid::new("3jzfcijpj2z2A").is_err()); assert!(Tid::new("3jzfcijpj2z21").is_err()); assert!(Tid::new("3jzfcijpj2z28").is_err()); assert!(Tid::new("3jzfcijpj2z2-").is_err()); }
#[test]
fn generation_and_comparison() {
let tid1 = Tid::now_0();
std::thread::sleep(std::time::Duration::from_micros(10));
let tid2 = Tid::now_0();
assert!(tid1.as_str().len() == 13);
assert!(tid2.as_str().len() == 13);
assert!(tid2.newer_than(&tid1));
assert!(tid1.older_than(&tid2));
}
#[test]
fn ticker_monotonic() {
let mut ticker = Ticker::new();
let tid1 = ticker.next(None);
let tid2 = ticker.next(Some(tid1.clone()));
let tid3 = ticker.next(Some(tid2.clone()));
assert!(tid2.newer_than(&tid1));
assert!(tid3.newer_than(&tid2));
}
}