use std::fmt;
use entelix_core::TenantId;
use twox_hash::XxHash64;
const ADVISORY_NAMESPACE: &str = "entelix:lock";
const ADVISORY_SEED: u64 = 0x656e_7465_6c69_785f;
const REDIS_KEY_LEN: usize = 29;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct AdvisoryKey {
raw: i64,
redis_key: [u8; REDIS_KEY_LEN],
}
impl AdvisoryKey {
#[allow(clippy::cast_possible_wrap)]
pub fn from_strings(parts: &[&str]) -> Self {
use std::hash::Hasher;
let mut hasher = XxHash64::with_seed(ADVISORY_SEED);
for part in parts {
hasher.write(part.as_bytes());
hasher.write(&[0u8]);
}
let hash = hasher.finish();
let raw = hash as i64;
let redis_key = encode_redis_key(hash);
Self { raw, redis_key }
}
pub fn for_session(tenant_id: &TenantId, thread_id: &str) -> Self {
Self::from_strings(&[ADVISORY_NAMESPACE, "session", tenant_id.as_str(), thread_id])
}
pub const fn raw(&self) -> i64 {
self.raw
}
#[allow(clippy::cast_possible_truncation)]
pub const fn halves(&self) -> (i32, i32) {
let high = (self.raw >> 32) as i32;
let low = (self.raw & 0xFFFF_FFFF) as i32;
(high, low)
}
pub fn redis_key(&self) -> &str {
std::str::from_utf8(&self.redis_key).expect("redis key is ASCII")
}
}
impl fmt::Display for AdvisoryKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.redis_key())
}
}
#[allow(clippy::cast_possible_truncation)]
fn encode_redis_key(hash: u64) -> [u8; REDIS_KEY_LEN] {
const PREFIX: &[u8; 13] = b"entelix:lock:";
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = [0u8; REDIS_KEY_LEN];
out[..13].copy_from_slice(PREFIX);
let bytes = hash.to_be_bytes();
let mut idx = 13;
for byte in bytes {
out[idx] = HEX[((byte >> 4) & 0x0f) as usize];
out[idx + 1] = HEX[(byte & 0x0f) as usize];
idx += 2;
}
debug_assert_eq!(idx, REDIS_KEY_LEN);
out
}