use std::time::{SystemTime, UNIX_EPOCH};
use crate::charsets::MAX;
use crate::decode::decode;
use crate::encode::{assert_length, encode};
use crate::errors::{OdoError, UnsupportedLengthError};
#[derive(Debug, Clone)]
pub struct GeneratorConfig {
pub namespace: String,
pub length: usize,
pub epoch: Option<u64>,
}
impl Default for GeneratorConfig {
fn default() -> Self {
Self {
namespace: "default".into(),
length: 6,
epoch: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OdoIDResult {
pub id: String,
pub n: u64,
pub length: usize,
pub namespace: String,
}
pub struct OdoIDGenerator {
pub namespace: String,
pub length: usize,
pub capacity: u64,
epoch: u64,
sequence: u64,
last_tick: u64,
}
fn now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before UNIX epoch")
.as_millis() as u64
}
fn fnv1a32(value: &str) -> u64 {
let mut h: u32 = 2166136261;
for byte in value.bytes() {
h ^= u32::from(byte);
h = h.wrapping_mul(16777619);
}
u64::from(h)
}
impl OdoIDGenerator {
pub fn new(config: GeneratorConfig) -> Result<Self, UnsupportedLengthError> {
assert_length(config.length)?;
let epoch = config.epoch.unwrap_or_else(now_ms);
Ok(Self {
namespace: config.namespace,
length: config.length,
capacity: MAX[config.length],
epoch,
sequence: 0,
last_tick: 0,
})
}
fn tick(&self) -> u64 {
now_ms().saturating_sub(self.epoch)
}
pub fn next_n(&mut self) -> u64 {
let tick = self.tick();
if tick == self.last_tick {
self.sequence += 1;
} else {
self.sequence = 0;
self.last_tick = tick;
}
let key = format!("{}|{}", self.namespace, tick);
let mut seed = fnv1a32(&key);
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
(seed + self.sequence) % self.capacity
}
pub fn next(&mut self) -> Result<OdoIDResult, OdoError> {
let n = self.next_n();
let id = encode(n, self.length)?;
Ok(OdoIDResult {
id,
n,
length: self.length,
namespace: self.namespace.clone(),
})
}
pub fn encode(&self, n: u64) -> Result<String, OdoError> {
encode(n, self.length)
}
pub fn decode(&self, id: &str) -> Result<u64, OdoError> {
decode(id)
}
}