2023-05-26 10:00:09 +02:00
|
|
|
//! ID generation utility based on [cuid2]
|
|
|
|
|
2023-08-06 08:34:44 +02:00
|
|
|
use basen::BASE36;
|
2023-06-02 17:55:14 +02:00
|
|
|
use cfg_if::cfg_if;
|
2023-07-10 07:39:33 +02:00
|
|
|
use chrono::Utc;
|
2023-05-26 10:00:09 +02:00
|
|
|
use once_cell::sync::OnceCell;
|
2023-07-10 07:39:33 +02:00
|
|
|
use std::cmp;
|
2023-05-26 10:00:09 +02:00
|
|
|
|
2023-06-02 17:55:14 +02:00
|
|
|
use crate::impl_into_napi_error;
|
|
|
|
|
2023-05-26 10:00:09 +02:00
|
|
|
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
|
|
|
#[error("ID generator has not been initialized yet")]
|
|
|
|
pub struct ErrorUninitialized;
|
|
|
|
|
2023-06-02 17:55:14 +02:00
|
|
|
impl_into_napi_error!(ErrorUninitialized);
|
|
|
|
|
|
|
|
static FINGERPRINT: OnceCell<String> = OnceCell::new();
|
|
|
|
static GENERATOR: OnceCell<cuid2::CuidConstructor> = OnceCell::new();
|
2023-05-26 10:00:09 +02:00
|
|
|
|
2023-07-10 07:39:33 +02:00
|
|
|
const TIME_2000: i64 = 946_684_800_000;
|
|
|
|
const TIMESTAMP_LENGTH: u16 = 8;
|
|
|
|
|
2023-06-02 17:55:14 +02:00
|
|
|
/// Initializes Cuid2 generator. Must be called before any [create_id].
|
2024-03-02 06:24:05 +01:00
|
|
|
pub fn init_id(length: u16, fingerprint: &str) {
|
2023-07-10 07:39:33 +02:00
|
|
|
FINGERPRINT.get_or_init(move || format!("{}{}", fingerprint, cuid2::create_id()));
|
2023-06-02 17:55:14 +02:00
|
|
|
GENERATOR.get_or_init(move || {
|
|
|
|
cuid2::CuidConstructor::new()
|
2023-07-10 07:39:33 +02:00
|
|
|
// length to pass shoule be greater than or equal to 8.
|
|
|
|
.with_length(cmp::max(length - TIMESTAMP_LENGTH, 8))
|
2023-06-02 17:55:14 +02:00
|
|
|
.with_fingerprinter(|| FINGERPRINT.get().unwrap().clone())
|
|
|
|
});
|
2023-05-26 10:00:09 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 17:55:14 +02:00
|
|
|
/// Returns Cuid2 with the length specified by [init_id]. Must be called after
|
|
|
|
/// [init_id], otherwise returns [ErrorUninitialized].
|
2023-07-10 07:39:33 +02:00
|
|
|
/// The current timestamp via [chrono::Utc] is used if `date_num` is `0`.
|
|
|
|
pub fn create_id(date_num: i64) -> Result<String, ErrorUninitialized> {
|
2023-05-26 10:00:09 +02:00
|
|
|
match GENERATOR.get() {
|
|
|
|
None => Err(ErrorUninitialized),
|
2023-07-10 07:39:33 +02:00
|
|
|
Some(gen) => {
|
|
|
|
let date_num = if date_num > 0 {
|
|
|
|
date_num
|
|
|
|
} else {
|
|
|
|
Utc::now().timestamp_millis()
|
|
|
|
};
|
|
|
|
let time = cmp::max(date_num - TIME_2000, 0);
|
|
|
|
Ok(format!(
|
|
|
|
"{:0>8}{}",
|
2023-08-06 08:34:44 +02:00
|
|
|
BASE36.encode_var_len(&(time as u64)),
|
2023-07-10 07:39:33 +02:00
|
|
|
gen.create_id()
|
|
|
|
))
|
|
|
|
}
|
2023-05-26 10:00:09 +02:00
|
|
|
}
|
2023-05-25 18:11:59 +02:00
|
|
|
}
|
|
|
|
|
2023-08-06 08:34:44 +02:00
|
|
|
pub fn get_timestamp(id: &str) -> i64 {
|
|
|
|
let n: Option<u64> = BASE36.decode_var_len(&id[0..8]);
|
|
|
|
match n {
|
|
|
|
None => -1,
|
|
|
|
Some(n) => n as i64 + TIME_2000,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-02 17:55:14 +02:00
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(feature = "napi")] {
|
|
|
|
use napi_derive::napi;
|
|
|
|
|
|
|
|
/// Calls [init_id] inside. Must be called before [native_create_id].
|
|
|
|
#[napi]
|
|
|
|
pub fn native_init_id_generator(length: u16, fingerprint: String) {
|
2023-07-10 07:39:33 +02:00
|
|
|
init_id(length, &fingerprint);
|
2023-06-02 17:55:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates
|
|
|
|
#[napi]
|
2023-07-10 07:39:33 +02:00
|
|
|
pub fn native_create_id(date_num: i64) -> String {
|
|
|
|
create_id(date_num).unwrap()
|
2023-06-02 17:55:14 +02:00
|
|
|
}
|
2023-08-06 08:34:44 +02:00
|
|
|
|
|
|
|
#[napi]
|
|
|
|
pub fn native_get_timestamp(id: String) -> i64 {
|
|
|
|
get_timestamp(&id)
|
|
|
|
}
|
2023-06-02 17:55:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-25 18:11:59 +02:00
|
|
|
#[cfg(test)]
|
2023-05-27 12:52:15 +02:00
|
|
|
mod unit_test {
|
2023-06-02 22:10:01 +02:00
|
|
|
use crate::util::id;
|
2023-08-06 08:34:44 +02:00
|
|
|
use chrono::Utc;
|
2023-06-02 13:08:58 +02:00
|
|
|
use pretty_assertions::{assert_eq, assert_ne};
|
2023-05-26 10:00:09 +02:00
|
|
|
use std::thread;
|
2023-05-25 18:11:59 +02:00
|
|
|
|
2023-07-10 07:39:33 +02:00
|
|
|
#[test]
|
2023-08-06 08:34:44 +02:00
|
|
|
fn can_create_and_decode() {
|
2023-07-10 07:39:33 +02:00
|
|
|
assert_eq!(id::create_id(0), Err(id::ErrorUninitialized));
|
|
|
|
id::init_id(16, "");
|
|
|
|
assert_eq!(id::create_id(0).unwrap().len(), 16);
|
|
|
|
assert_ne!(id::create_id(0).unwrap(), id::create_id(0).unwrap());
|
|
|
|
let id1 = thread::spawn(|| id::create_id(0).unwrap());
|
|
|
|
let id2 = thread::spawn(|| id::create_id(0).unwrap());
|
|
|
|
assert_ne!(id1.join().unwrap(), id2.join().unwrap());
|
2023-08-06 08:34:44 +02:00
|
|
|
|
|
|
|
let now = Utc::now().timestamp_millis();
|
|
|
|
let test_id = id::create_id(now).unwrap();
|
|
|
|
let timestamp = id::get_timestamp(&test_id);
|
|
|
|
assert_eq!(now, timestamp);
|
2023-05-25 18:11:59 +02:00
|
|
|
}
|
|
|
|
}
|