use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct TenantId(pub u32);
impl TenantId {
pub const SYSTEM: Self = TenantId(0);
pub const SINGLE: Self = TenantId(1);
pub const FIRST_ASSIGNED: u32 = 2;
pub const UNREGISTERED: Self = TenantId(u32::MAX);
#[must_use]
pub fn single() -> Self {
Self::SINGLE
}
#[must_use]
pub const fn from_ordinal(ordinal: u64) -> Self {
assert!(ordinal <= u32::MAX as u64, "tenant ordinal exceeds u32");
#[expect(clippy::cast_possible_truncation, reason = "range checked above")]
TenantId(ordinal as u32)
}
#[must_use]
pub const fn to_be_bytes(self) -> [u8; 4] {
self.0.to_be_bytes()
}
}
impl fmt::Display for TenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct TenantScope(pub [u8; 32]);
impl TenantScope {
#[must_use]
pub fn of(scope: &str) -> Self {
TenantScope(Sha256::digest(scope.as_bytes()).into())
}
#[must_use]
pub fn single() -> Self {
Self::of("rushdb:single-tenant")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arbitrary_scope_values_are_stable_and_distinct() {
assert_ne!(TenantScope::of("scope-a"), TenantScope::of("scope-b"));
assert_eq!(TenantScope::of("not-a-uuid"), TenantScope::of("not-a-uuid"));
}
}
macro_rules! dense_id {
($name:ident) => {
#[doc = concat!("Dense monotonic ", stringify!($name), " identifier.")]
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Default,
)]
pub struct $name(#[doc = "Wrapped ordinal value."] pub u64);
impl $name {
#[must_use]
pub fn next(self) -> Self {
$name(self.0 + 1)
}
}
};
}
dense_id!(RecordOrdinal);
dense_id!(RelationshipOrdinal);
dense_id!(OccurrenceOrdinal);
dense_id!(FamilyId);
dense_id!(PropertyId);
dense_id!(ValueSeq);
dense_id!(SpaceId);
pub type TokenId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct RecordId(pub [u8; 16]);
impl RecordId {
#[must_use]
pub fn generate() -> Self {
RecordId(*Uuid::now_v7().as_bytes())
}
#[must_use]
pub fn nil() -> Self {
RecordId([0u8; 16])
}
#[must_use]
pub fn to_uuid(&self) -> Uuid {
Uuid::from_bytes(self.0)
}
}
impl From<Uuid> for RecordId {
fn from(u: Uuid) -> Self {
RecordId(*u.as_bytes())
}
}
impl fmt::Display for RecordId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_uuid().simple())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct RelationshipId(pub [u8; 16]);
impl RelationshipId {
#[must_use]
pub fn generate() -> Self {
RelationshipId(*Uuid::now_v7().as_bytes())
}
#[must_use]
pub fn to_uuid(&self) -> Uuid {
Uuid::from_bytes(self.0)
}
}
impl From<Uuid> for RelationshipId {
fn from(u: Uuid) -> Self {
RelationshipId(*u.as_bytes())
}
}
impl fmt::Display for RelationshipId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_uuid().simple())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum OwnerKind {
Record,
Relationship,
}
impl OwnerKind {
#[must_use]
pub fn as_byte(self) -> u8 {
match self {
OwnerKind::Record => b'r',
OwnerKind::Relationship => b'e',
}
}
#[must_use]
pub fn from_byte(b: u8) -> Option<Self> {
match b {
b'r' => Some(OwnerKind::Record),
b'e' => Some(OwnerKind::Relationship),
_ => None,
}
}
}